import type { Kysely } from "kysely";
import { ulid } from "ulidx";
import { describe, it, expect, beforeEach, afterEach } from "vitest";

import { createDatabase } from "../../../src/database/connection.js";
import { runMigrations } from "../../../src/database/migrations/runner.js";
import type { Database } from "../../../src/database/types.js";
import { getMenuWithDb, getMenusWithDb } from "../../../src/menus/index.js";

describe("Navigation Menus", () => {
	let db: Kysely<Database>;

	beforeEach(async () => {
		// Fresh in-memory database for each test
		db = createDatabase({ url: ":memory:" });
		await runMigrations(db);
	});

	afterEach(async () => {
		await db.destroy();
	});

	describe("migration", () => {
		it("should create _emdash_menus table", async () => {
			const tables = await db.introspection.getTables();
			const menusTable = tables.find((t) => t.name === "_emdash_menus");
			expect(menusTable).toBeDefined();

			const columns = menusTable!.columns.map((c) => c.name);
			expect(columns).toContain("id");
			expect(columns).toContain("name");
			expect(columns).toContain("label");
			expect(columns).toContain("created_at");
			expect(columns).toContain("updated_at");
		});

		it("should create _emdash_menu_items table", async () => {
			const tables = await db.introspection.getTables();
			const itemsTable = tables.find((t) => t.name === "_emdash_menu_items");
			expect(itemsTable).toBeDefined();

			const columns = itemsTable!.columns.map((c) => c.name);
			expect(columns).toContain("id");
			expect(columns).toContain("menu_id");
			expect(columns).toContain("parent_id");
			expect(columns).toContain("sort_order");
			expect(columns).toContain("type");
			expect(columns).toContain("reference_collection");
			expect(columns).toContain("reference_id");
			expect(columns).toContain("custom_url");
			expect(columns).toContain("label");
			expect(columns).toContain("target");
			expect(columns).toContain("css_classes");
		});

		it("should enforce unique constraint on menu name", async () => {
			const id1 = ulid();
			const id2 = ulid();

			await db
				.insertInto("_emdash_menus")
				.values({
					id: id1,
					name: "primary",
					label: "Primary Navigation",
				})
				.execute();

			await expect(
				db
					.insertInto("_emdash_menus")
					.values({
						id: id2,
						name: "primary",
						label: "Primary Again",
					})
					.execute(),
			).rejects.toThrow();
		});

		it("should cascade delete menu items when menu is deleted", async () => {
			const menuId = ulid();
			const itemId = ulid();

			// Create menu
			await db
				.insertInto("_emdash_menus")
				.values({
					id: menuId,
					name: "test-menu",
					label: "Test Menu",
				})
				.execute();

			// Create menu item
			await db
				.insertInto("_emdash_menu_items")
				.values({
					id: itemId,
					menu_id: menuId,
					sort_order: 0,
					type: "custom",
					custom_url: "https://example.com",
					label: "Test Link",
				})
				.execute();

			// Delete menu
			await db.deleteFrom("_emdash_menus").where("id", "=", menuId).execute();

			// Verify item was deleted
			const items = await db
				.selectFrom("_emdash_menu_items")
				.where("menu_id", "=", menuId)
				.selectAll()
				.execute();

			expect(items).toHaveLength(0);
		});
	});

	describe("getMenus", () => {
		it("should return empty array when no menus exist", async () => {
			const menus = await getMenusWithDb(db);
			expect(menus).toEqual([]);
		});

		it("should return all menus ordered by name", async () => {
			await db
				.insertInto("_emdash_menus")
				.values([
					{ id: ulid(), name: "footer", label: "Footer Links" },
					{ id: ulid(), name: "primary", label: "Primary Navigation" },
					{ id: ulid(), name: "social", label: "Social Links" },
				])
				.execute();

			const menus = await getMenusWithDb(db);
			expect(menus).toHaveLength(3);
			expect(menus[0].name).toBe("footer");
			expect(menus[1].name).toBe("primary");
			expect(menus[2].name).toBe("social");
		});
	});

	describe("getMenu", () => {
		it("should return null for non-existent menu", async () => {
			const menu = await getMenuWithDb("nonexistent", db);
			expect(menu).toBeNull();
		});

		it("should return menu with empty items array", async () => {
			const menuId = ulid();
			await db
				.insertInto("_emdash_menus")
				.values({
					id: menuId,
					name: "primary",
					label: "Primary Navigation",
				})
				.execute();

			const menu = await getMenuWithDb("primary", db);
			expect(menu).toMatchObject({
				id: menuId,
				name: "primary",
				label: "Primary Navigation",
				items: [],
			});
		});

		it("should resolve custom URLs correctly", async () => {
			const menuId = ulid();
			const itemId = ulid();

			await db
				.insertInto("_emdash_menus")
				.values({
					id: menuId,
					name: "primary",
					label: "Primary Navigation",
				})
				.execute();

			await db
				.insertInto("_emdash_menu_items")
				.values({
					id: itemId,
					menu_id: menuId,
					sort_order: 0,
					type: "custom",
					custom_url: "https://github.com",
					label: "GitHub",
					target: "_blank",
				})
				.execute();

			const menu = await getMenuWithDb("primary", db);
			expect(menu).not.toBeNull();
			expect(menu!.items).toHaveLength(1);
			expect(menu!.items[0]).toMatchObject({
				id: itemId,
				label: "GitHub",
				url: "https://github.com",
				target: "_blank",
			});
		});

		it("should skip items with deleted content references", async () => {
			const menuId = ulid();
			const itemId = ulid();

			// Create menu with item referencing non-existent content
			await db
				.insertInto("_emdash_menus")
				.values({
					id: menuId,
					name: "primary",
					label: "Primary Navigation",
				})
				.execute();

			await db
				.insertInto("_emdash_menu_items")
				.values({
					id: itemId,
					menu_id: menuId,
					sort_order: 0,
					type: "page",
					reference_collection: "pages",
					reference_id: "nonexistent",
					label: "Deleted Page",
				})
				.execute();

			const menu = await getMenuWithDb("primary", db);
			expect(menu).not.toBeNull();
			// Item should be filtered out because the page doesn't exist
			expect(menu!.items).toHaveLength(0);
		});

		it("should build nested tree structure", async () => {
			const menuId = ulid();
			const parentId = ulid();
			const childId = ulid();

			await db
				.insertInto("_emdash_menus")
				.values({
					id: menuId,
					name: "primary",
					label: "Primary Navigation",
				})
				.execute();

			// Create parent item
			await db
				.insertInto("_emdash_menu_items")
				.values({
					id: parentId,
					menu_id: menuId,
					sort_order: 0,
					type: "custom",
					custom_url: "/about",
					label: "About",
				})
				.execute();

			// Create child item
			await db
				.insertInto("_emdash_menu_items")
				.values({
					id: childId,
					menu_id: menuId,
					parent_id: parentId,
					sort_order: 0,
					type: "custom",
					custom_url: "/about/team",
					label: "Team",
				})
				.execute();

			const menu = await getMenuWithDb("primary", db);
			expect(menu).not.toBeNull();
			expect(menu!.items).toHaveLength(1);
			expect(menu!.items[0].label).toBe("About");
			expect(menu!.items[0].children).toHaveLength(1);
			expect(menu!.items[0].children[0].label).toBe("Team");
		});

		it("should order items by sort_order", async () => {
			const menuId = ulid();

			await db
				.insertInto("_emdash_menus")
				.values({
					id: menuId,
					name: "primary",
					label: "Primary Navigation",
				})
				.execute();

			await db
				.insertInto("_emdash_menu_items")
				.values([
					{
						id: ulid(),
						menu_id: menuId,
						sort_order: 2,
						type: "custom",
						custom_url: "/contact",
						label: "Contact",
					},
					{
						id: ulid(),
						menu_id: menuId,
						sort_order: 0,
						type: "custom",
						custom_url: "/home",
						label: "Home",
					},
					{
						id: ulid(),
						menu_id: menuId,
						sort_order: 1,
						type: "custom",
						custom_url: "/about",
						label: "About",
					},
				])
				.execute();

			const menu = await getMenuWithDb("primary", db);
			expect(menu).not.toBeNull();
			expect(menu!.items).toHaveLength(3);
			expect(menu!.items[0].label).toBe("Home");
			expect(menu!.items[1].label).toBe("About");
			expect(menu!.items[2].label).toBe("Contact");
		});
	});
});
