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

import { createDatabase } from "../../src/database/connection.js";
import {
	runMigrations,
	getMigrationStatus,
	MIGRATION_COUNT,
} from "../../src/database/migrations/runner.js";
import type { Database } from "../../src/database/types.js";

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

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

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

	describe("getMigrationStatus", () => {
		it("should return all migrations as pending for fresh database", async () => {
			const status = await getMigrationStatus(db);

			expect(status.applied).toEqual([]);
			expect(status.pending).toContain("001_initial");
		});

		it("should create migrations tracking table when running migrations", async () => {
			// Note: getMigrationStatus doesn't create the table, runMigrations does
			await runMigrations(db);

			// Verify table was created
			const tables = await db.introspection.getTables();
			const migrationTable = tables.find((t) => t.name === "_emdash_migrations");
			expect(migrationTable).toBeDefined();
		});
	});

	describe("runMigrations", () => {
		it("should run all pending migrations on fresh database", async () => {
			await runMigrations(db);

			const status = await getMigrationStatus(db);
			expect(status.pending).toEqual([]);
			expect(status.applied).toContain("001_initial");
		});

		it("should create all tables from initial migration", async () => {
			await runMigrations(db);

			const tables = await db.introspection.getTables();
			const tableNames = tables.map((t) => t.name);

			// Core system tables (no generic "content" table - collections create ec_* tables)
			expect(tableNames).toContain("revisions");
			expect(tableNames).toContain("taxonomies");
			expect(tableNames).toContain("content_taxonomies");
			expect(tableNames).toContain("media");
			expect(tableNames).toContain("users");
			expect(tableNames).toContain("options");
			expect(tableNames).toContain("audit_logs");
			expect(tableNames).toContain("_emdash_migrations");
			// Schema registry tables
			expect(tableNames).toContain("_emdash_collections");
			expect(tableNames).toContain("_emdash_fields");
		});

		it("should be idempotent - running twice should not error", async () => {
			await runMigrations(db);
			await expect(runMigrations(db)).resolves.not.toThrow();

			const status = await getMigrationStatus(db);
			expect(status.applied).toHaveLength(MIGRATION_COUNT); // derived from MIGRATIONS map in runner.ts
		});

		it("should record migration in tracking table", async () => {
			await runMigrations(db);

			const records = await db.selectFrom("_emdash_migrations").selectAll().execute();

			expect(records).toHaveLength(MIGRATION_COUNT);
			expect(records[0].name).toBe("001_initial");
			expect(records[0].timestamp).toBeDefined();
			expect(records[1].name).toBe("002_media_status");
			expect(records[1].timestamp).toBeDefined();
			expect(records[2].name).toBe("003_schema_registry");
			expect(records[2].timestamp).toBeDefined();
			expect(records[3].name).toBe("004_plugins");
			expect(records[3].timestamp).toBeDefined();
			expect(records[4].name).toBe("005_menus");
			expect(records[4].timestamp).toBeDefined();
			expect(records[5].name).toBe("006_taxonomy_defs");
			expect(records[5].timestamp).toBeDefined();
			expect(records[6].name).toBe("007_widgets");
			expect(records[6].timestamp).toBeDefined();
			expect(records[7].name).toBe("008_auth");
			expect(records[7].timestamp).toBeDefined();
			expect(records[8].name).toBe("009_user_disabled");
			expect(records[8].timestamp).toBeDefined();
			expect(records[9].name).toBe("011_sections");
			expect(records[9].timestamp).toBeDefined();
			expect(records[10].name).toBe("012_search");
			expect(records[10].timestamp).toBeDefined();
			expect(records[11].name).toBe("013_scheduled_publishing");
			expect(records[11].timestamp).toBeDefined();
			expect(records[12].name).toBe("014_draft_revisions");
			expect(records[12].timestamp).toBeDefined();
			expect(records[13].name).toBe("015_indexes");
			expect(records[13].timestamp).toBeDefined();
			expect(records[14].name).toBe("016_api_tokens");
			expect(records[14].timestamp).toBeDefined();
			expect(records[15].name).toBe("017_authorization_codes");
			expect(records[15].timestamp).toBeDefined();
		});
	});

	describe("schema registry tables", () => {
		beforeEach(async () => {
			await runMigrations(db);
		});

		it("should have _emdash_collections table with correct columns", async () => {
			const tables = await db.introspection.getTables();
			const collectionsTable = tables.find((t) => t.name === "_emdash_collections");

			expect(collectionsTable).toBeDefined();
			const columns = collectionsTable!.columns.map((c) => c.name);

			expect(columns).toContain("id");
			expect(columns).toContain("slug");
			expect(columns).toContain("label");
			expect(columns).toContain("label_singular");
			expect(columns).toContain("description");
			expect(columns).toContain("icon");
			expect(columns).toContain("supports");
			expect(columns).toContain("source");
			expect(columns).toContain("created_at");
			expect(columns).toContain("updated_at");
		});

		it("should have _emdash_fields table with correct columns", async () => {
			const tables = await db.introspection.getTables();
			const fieldsTable = tables.find((t) => t.name === "_emdash_fields");

			expect(fieldsTable).toBeDefined();
			const columns = fieldsTable!.columns.map((c) => c.name);

			expect(columns).toContain("id");
			expect(columns).toContain("collection_id");
			expect(columns).toContain("slug");
			expect(columns).toContain("label");
			expect(columns).toContain("type");
			expect(columns).toContain("column_type");
			expect(columns).toContain("required");
			expect(columns).toContain("unique");
			expect(columns).toContain("default_value");
			expect(columns).toContain("validation");
			expect(columns).toContain("widget");
			expect(columns).toContain("options");
			expect(columns).toContain("sort_order");
			expect(columns).toContain("created_at");
		});

		it("should enforce unique constraint on collection slug", async () => {
			await db
				.insertInto("_emdash_collections")
				.values({
					id: "1",
					slug: "posts",
					label: "Posts",
				})
				.execute();

			await expect(
				db
					.insertInto("_emdash_collections")
					.values({
						id: "2",
						slug: "posts",
						label: "Posts Again",
					})
					.execute(),
			).rejects.toThrow();
		});
	});

	describe("users table schema", () => {
		beforeEach(async () => {
			await runMigrations(db);
		});

		it("should enforce unique constraint on email", async () => {
			await db
				.insertInto("users")
				.values({
					id: "1",
					email: "test@example.com",
					role: 50, // ADMIN
				})
				.execute();

			await expect(
				db
					.insertInto("users")
					.values({
						id: "2",
						email: "test@example.com",
						role: 40, // EDITOR
					})
					.execute(),
			).rejects.toThrow();
		});

		it("should have auth-related tables", async () => {
			const tables = await db.introspection.getTables();
			const tableNames = tables.map((t) => t.name);

			expect(tableNames).toContain("credentials");
			expect(tableNames).toContain("auth_tokens");
			expect(tableNames).toContain("oauth_accounts");
			expect(tableNames).toContain("allowed_domains");
		});
	});

	describe("revisions table", () => {
		beforeEach(async () => {
			await runMigrations(db);
		});

		it("should have correct columns for per-collection architecture", async () => {
			const tables = await db.introspection.getTables();
			const revisionsTable = tables.find((t) => t.name === "revisions");

			expect(revisionsTable).toBeDefined();
			const columns = revisionsTable!.columns.map((c) => c.name);

			// Revisions now reference collection + entry_id instead of content_id
			expect(columns).toContain("id");
			expect(columns).toContain("collection");
			expect(columns).toContain("entry_id");
			expect(columns).toContain("data");
			expect(columns).toContain("created_at");
		});

		it("should store revision data", async () => {
			await db
				.insertInto("revisions")
				.values({
					id: "rev-1",
					collection: "posts",
					entry_id: "entry-1",
					data: JSON.stringify({ title: "Original Title" }),
				})
				.execute();

			const revisions = await db
				.selectFrom("revisions")
				.where("collection", "=", "posts")
				.where("entry_id", "=", "entry-1")
				.selectAll()
				.execute();

			expect(revisions).toHaveLength(1);
			expect(JSON.parse(revisions[0].data)).toEqual({
				title: "Original Title",
			});
		});
	});

	describe("media table", () => {
		beforeEach(async () => {
			await runMigrations(db);
		});

		it("should have correct columns", async () => {
			const tables = await db.introspection.getTables();
			const mediaTable = tables.find((t) => t.name === "media");

			expect(mediaTable).toBeDefined();
			const columns = mediaTable!.columns.map((c) => c.name);

			expect(columns).toContain("id");
			expect(columns).toContain("filename");
			expect(columns).toContain("mime_type");
			expect(columns).toContain("size");
			expect(columns).toContain("storage_key");
		});
	});
});
