import { beforeAll, describe, expect, it } from "bun:test";
import { eq } from "drizzle-orm";
import { expectStatus } from "tests/utils";
import { db } from "~/db";
import { shows, showTranslations, videos } from "~/db/schema";
import { bubble } from "~/models/examples";
import { dune, duneVideo } from "~/models/examples/dune-2021";
import { createMovie, createVideo } from "../helpers";

beforeAll(async () => {
	await db.delete(shows);
	await db.delete(videos);
});

describe("Movie seeding", () => {
	it("Can create a movie", async () => {
		// create video beforehand to test linking
		await db.insert(videos).values(duneVideo);

		const [resp, body] = await createMovie(dune);
		expectStatus(resp, body).toBe(201);
		expect(body.id).toBeString();
		expect(body.slug).toBe("dune");
		expect(body.videos).toContainEqual({ slug: "dune" });
	});

	it("Update existing movie", async () => {
		// confirm that db is in the correct state (from previous tests)
		const [existing] = await db
			.select()
			.from(shows)
			.where(eq(shows.slug, dune.slug))
			.limit(1);
		expect(existing).toMatchObject({ slug: dune.slug, startAir: dune.airDate });

		const [resp, body] = await createMovie({
			...dune,
			runtime: 200_000,
			translations: {
				...dune.translations,
				en: { ...dune.translations.en, description: "edited translation" },
				fr: {
					name: "dune-but-in-french",
					description: null,
					tagline: null,
					aliases: [],
					tags: [],
					poster: null,
					thumbnail: null,
					banner: null,
					logo: null,
					trailer: null,
				},
			},
		});
		const [edited] = await db
			.select()
			.from(shows)
			.where(eq(shows.slug, dune.slug))
			.limit(1);
		const translations = await db
			.select()
			.from(showTranslations)
			.where(eq(showTranslations.pk, edited.pk));

		expectStatus(resp, body).toBe(200);
		expect(body.id).toBeString();
		expect(body.slug).toBe("dune");
		expect(body.videos).toBeArrayOfSize(0);
		expect(edited.runtime).toBe(200_000);
		expect(edited.status).toBe(dune.status);
		expect(translations.find((x) => x.language === "en")).toMatchObject({
			name: dune.translations.en.name,
			description: "edited translation",
		});
		expect(translations.find((x) => x.language === "fr")).toMatchObject({
			name: "dune-but-in-french",
			description: null,
		});
	});

	it("Conflicting slug auto-correct", async () => {
		// confirm that db is in the correct state (from previous tests)
		const [existing] = await db
			.select()
			.from(shows)
			.where(eq(shows.slug, dune.slug))
			.limit(1);
		expect(existing).toMatchObject({ slug: dune.slug, startAir: dune.airDate });

		const [resp, body] = await createMovie({ ...dune, airDate: "2158-12-13" });
		expectStatus(resp, body).toBe(201);
		expect(body.id).toBeString();
		expect(body.slug).toBe("dune-2158");
	});

	it("Conflict in slug w/out year fails", async () => {
		// confirm that db is in the correct state (from conflict auto-correct test)
		const [existing] = await db
			.select()
			.from(shows)
			.where(eq(shows.slug, dune.slug))
			.limit(1);
		expect(existing).toMatchObject({ slug: dune.slug, startAir: dune.airDate });

		const [resp, body] = await createMovie({ ...dune, airDate: null });
		expectStatus(resp, body).toBe(409);
		expect(body.id).toBe(existing.id);
		expect(body.slug).toBe(existing.slug);
	});

	it("Missing videos send info", async () => {
		const vid = "a0ddf0ce-3258-4452-a670-aff36c76d524";
		const [existing] = await db
			.select()
			.from(videos)
			.where(eq(videos.id, vid))
			.limit(1);
		expect(existing).toBeUndefined();

		const [resp, body] = await createMovie({
			...dune,
			videos: [vid],
		});

		expectStatus(resp, body).toBe(200);
		expect(body.videos).toBeArrayOfSize(0);
	});

	it("Schema error (missing fields)", async () => {
		const [resp, body] = await createMovie({
			name: "dune",
		} as any);

		expectStatus(resp, body).toBe(422);
		expect(body.status).toBe(422);
		expect(body.message).toBeString();
		expect(body.details).toBeObject();
		// TODO: handle additional fields too
	});

	it("Invalid translation name", async () => {
		const [resp, body] = await createMovie({
			...dune,
			translations: {
				...dune.translations,
				test: {
					name: "foo",
					description: "bar",
					tags: [],
					aliases: [],
					tagline: "toto",
					banner: null,
					poster: null,
					thumbnail: null,
					logo: null,
					trailer: null,
				},
			},
		});

		expectStatus(resp, body).toBe(422);
		expect(body.status).toBe(422);
		expect(body.message).toBe("Invalid translation name: 'test'.");
	});

	it("Correct translations casing.", async () => {
		const [resp, body] = await createMovie({
			...bubble,
			slug: "casing-test",
			originalLanguage: "en-us",
			translations: {
				"en-us": {
					name: "foo",
					description: "bar",
					tags: [],
					aliases: [],
					tagline: "toto",
					banner: null,
					poster: null,
					thumbnail: null,
					logo: null,
					trailer: null,
				},
			},
		});

		expect(resp.status).toBeWithin(200, 299);
		expect(body.id).toBeString();
		const ret = await db.query.shows.findFirst({
			where: eq(shows.id, body.id),
			with: { translations: true },
		});
		expect(ret!.original.language).toBe("en-US");
		expect(ret!.translations).toBeArrayOfSize(2);
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					language: "en-US",
					name: "foo",
				}),
			]),
		);
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					language: "en",
					name: "foo",
				}),
			]),
		);
	});

	it("Refuses random as a slug", async () => {
		const [resp, body] = await createMovie({
			...bubble,
			slug: "random",
			airDate: null,
		});
		expectStatus(resp, body).toBe(422);
	});
	it("Refuses random as a slug but fallback w/ airDate", async () => {
		const [resp, body] = await createMovie({ ...bubble, slug: "random" });
		expectStatus(resp, body).toBe(201);
		expect(body.slug).toBe("random-2022");
	});

	it("Handle fallback translations", async () => {
		const [resp, body] = await createMovie({
			...bubble,
			slug: "bubble-translation-test",
			translations: {
				"en-us": bubble.translations.en,
				ja: bubble.translations.ja,
			},
		});
		expectStatus(resp, body).toBe(201);

		const ret = await db.query.shows.findFirst({
			where: eq(shows.id, body.id),
			with: { translations: true },
		});
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					name: bubble.translations.en.name,
					language: "en",
				}),
			]),
		);
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					name: bubble.translations.en.name,
					language: "en-US",
				}),
			]),
		);
	});
	it("No fallback if explicit", async () => {
		const [resp, body] = await createMovie({
			...bubble,
			slug: "bubble-translation-test-2",
			translations: {
				"en-us": bubble.translations.en,
				"en-au": { ...bubble.translations.en, name: "australian thing" },
				en: { ...bubble.translations.en, name: "Generic" },
				ja: bubble.translations.ja,
			},
		});
		expectStatus(resp, body).toBe(201);

		const ret = await db.query.shows.findFirst({
			where: eq(shows.id, body.id),
			with: { translations: true },
		});
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					name: bubble.translations.en.name,
					language: "en-US",
				}),
			]),
		);
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					name: "australian thing",
					description: bubble.translations.en.description,
					language: "en-AU",
				}),
			]),
		);
		expect(ret!.translations).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					name: "Generic",
					description: bubble.translations.en.description,
					language: "en",
				}),
			]),
		);
	});

	it("Create correct video slug", async () => {
		const [vresp, video] = await createVideo({
			path: "/video/bubble.mkv",
			part: null,
			version: 1,
			rendering: "oeunhtoeuth",
			guess: { title: "bubble", from: "test", history: [] },
		});
		expectStatus(vresp, video).toBe(201);

		const [resp, body] = await createMovie({
			...bubble,
			slug: "video-slug-test1",
			videos: [video[0].id],
		});
		expectStatus(resp, body).toBe(201);

		const ret = await db.query.videos.findFirst({
			where: eq(videos.id, video[0].id),
			with: { evj: { with: { entry: true } } },
		});
		expect(ret).not.toBe(undefined);
		expect(ret!.evj).toBeArrayOfSize(1);
		expect(ret!.evj[0].slug).toBe("video-slug-test1");
	});

	it("Create correct video slug (version)", async () => {
		const [vresp, video] = await createVideo({
			path: "/video/bubble2.mkv",
			part: null,
			version: 2,
			rendering: "oeunhtoeuth",
			guess: { title: "bubble", from: "test", history: [] },
		});
		expectStatus(vresp, video).toBe(201);

		const [resp, body] = await createMovie({
			...bubble,
			slug: "bubble-vtest",
			videos: [video[0].id],
		});
		expectStatus(resp, body).toBe(201);

		const ret = await db.query.videos.findFirst({
			where: eq(videos.id, video[0].id),
			with: { evj: { with: { entry: true } } },
		});
		expect(ret).not.toBe(undefined);
		expect(ret!.evj).toBeArrayOfSize(1);
		expect(ret!.evj[0].slug).toBe("bubble-vtest-v2");
	});
	it("Create correct video slug (part)", async () => {
		const [vresp, video] = await createVideo({
			path: "/video/bubble5.mkv",
			part: 1,
			version: 2,
			rendering: "oaoeueunhtoeuth",
			guess: { title: "bubble", from: "test", history: [] },
		});
		expectStatus(vresp, video).toBe(201);

		const [resp, body] = await createMovie({
			...bubble,
			slug: "bubble-ptest",
			videos: [video[0].id],
		});
		expectStatus(resp, body).toBe(201);

		const ret = await db.query.videos.findFirst({
			where: eq(videos.id, video[0].id),
			with: { evj: { with: { entry: true } } },
		});
		expect(ret).not.toBe(undefined);
		expect(ret!.evj).toBeArrayOfSize(1);
		expect(ret!.evj[0].slug).toBe("bubble-ptest-p1-v2");
	});
	it("Create correct video slug (rendering)", async () => {
		const [vresp, video] = await createVideo([
			{
				path: "/video/bubble3.mkv",
				part: null,
				version: 1,
				rendering: "oeunhtoeuthoeu",
				guess: { title: "bubble", from: "test", history: [] },
			},
			{
				path: "/video/bubble4.mkv",
				part: null,
				version: 1,
				rendering: "aoeuaoeu",
				guess: { title: "bubble", from: "test", history: [] },
			},
		]);
		expectStatus(vresp, video).toBe(201);

		const [resp, body] = await createMovie({
			...bubble,
			slug: "bubble-rtest",
			videos: [video[0].id, video[1].id],
		});
		expectStatus(resp, body).toBe(201);

		const ret = await db.query.shows.findFirst({
			where: eq(shows.id, body.id),
			with: { entries: { with: { evj: { with: { entry: true } } } } },
		});
		expect(ret).not.toBe(undefined);
		expect(ret!.entries).toBeArrayOfSize(1);
		expect(ret!.entries[0].slug).toBe("bubble-rtest");
		expect(ret!.entries[0].evj).toBeArrayOfSize(2);
		expect(ret!.entries[0].evj).toContainValues([
			expect.objectContaining({ slug: "bubble-rtest" }),
			expect.objectContaining({ slug: "bubble-rtest-aoeuaoeu" }),
		]);
	});
});
