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";
import { setupTestDatabaseWithCollections } from "../../utils/test-db.js";

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

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

	afterEach(async () => {
		// Close the database connection
		await db.destroy();
	});

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

		// Verify all tables exist by querying them
		// Note: No generic "content" table - collections create ec_* tables dynamically
		const tables = [
			"revisions",
			"taxonomies",
			"content_taxonomies",
			"media",
			"users",
			"options",
			"audit_logs",
			"_emdash_migrations",
			"_emdash_collections",
			"_emdash_fields",
			"_plugin_storage",
			"_plugin_state",
			"_plugin_indexes",
			"_emdash_sections",
			"_emdash_bylines",
			"_emdash_content_bylines",
		];

		for (const table of tables) {
			// Query table to verify it exists
			const result = await db
				.selectFrom(table as keyof Database)
				.selectAll()
				.execute();
			expect(Array.isArray(result)).toBe(true);
		}
	});

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

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

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

	it("should be idempotent (running twice is safe)", async () => {
		await runMigrations(db);
		await runMigrations(db);

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

		// Should still only have the same number of migration records
		expect(migrations).toHaveLength(MIGRATION_COUNT);
	});

	it("should re-run migrations 034 and 035 when schema changes were partially applied", async () => {
		await db.destroy();
		db = await setupTestDatabaseWithCollections();

		await db
			.deleteFrom("_emdash_migrations")
			.where("name", "in", ["034_published_at_index", "035_bounded_404_log"])
			.execute();

		const { applied } = await runMigrations(db);

		expect(applied).toContain("034_published_at_index");
		expect(applied).toContain("035_bounded_404_log");

		const migrations = await db.selectFrom("_emdash_migrations").selectAll().execute();
		expect(migrations).toHaveLength(MIGRATION_COUNT);
	});

	it("should report correct migration status", async () => {
		const statusBefore = await getMigrationStatus(db);
		expect(statusBefore.pending).toContain("001_initial");
		expect(statusBefore.pending).toContain("002_media_status");
		expect(statusBefore.applied).toHaveLength(0);

		await runMigrations(db);

		const statusAfter = await getMigrationStatus(db);
		expect(statusAfter.applied).toContain("001_initial");
		expect(statusAfter.applied).toContain("002_media_status");
		expect(statusAfter.pending).toHaveLength(0);
	});

	it("should create schema registry tables", async () => {
		await runMigrations(db);

		// Test collections table
		const testId = "test-collection";
		await db
			.insertInto("_emdash_collections")
			.values({
				id: testId,
				slug: "posts",
				label: "Posts",
				label_singular: "Post",
			})
			.execute();

		const collection = await db
			.selectFrom("_emdash_collections")
			.selectAll()
			.where("id", "=", testId)
			.executeTakeFirst();

		expect(collection).toBeDefined();
		expect(collection?.slug).toBe("posts");
		expect(collection?.label).toBe("Posts");
		expect(collection?.created_at).toBeDefined();
	});

	it("should enforce unique constraint on collection slug", async () => {
		await runMigrations(db);

		await db
			.insertInto("_emdash_collections")
			.values({
				id: "id1",
				slug: "posts",
				label: "Posts",
			})
			.execute();

		// Attempting to insert duplicate slug should fail
		await expect(
			db
				.insertInto("_emdash_collections")
				.values({
					id: "id2",
					slug: "posts",
					label: "Posts Again",
				})
				.execute(),
		).rejects.toThrow();
	});

	it("should create fields table with foreign key to collections", async () => {
		await runMigrations(db);

		// Create collection first
		const collectionId = "collection-1";
		await db
			.insertInto("_emdash_collections")
			.values({
				id: collectionId,
				slug: "posts",
				label: "Posts",
			})
			.execute();

		// Create field
		await db
			.insertInto("_emdash_fields")
			.values({
				id: "field-1",
				collection_id: collectionId,
				slug: "title",
				label: "Title",
				type: "string",
				column_type: "TEXT",
				required: 0,
				unique: 0,
				sort_order: 0,
			})
			.execute();

		const fields = await db
			.selectFrom("_emdash_fields")
			.selectAll()
			.where("collection_id", "=", collectionId)
			.execute();

		expect(fields).toHaveLength(1);
		expect(fields[0]?.slug).toBe("title");
	});

	it("should create revisions table with collection+entry_id", async () => {
		await runMigrations(db);

		// Create revision for a content entry
		await db
			.insertInto("revisions")
			.values({
				id: "rev-1",
				collection: "posts",
				entry_id: "entry-1",
				data: JSON.stringify({ title: "Revised" }),
			})
			.execute();

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

		expect(revisions).toHaveLength(1);
		expect(revisions[0]?.collection).toBe("posts");
	});

	it("should create users table with unique email constraint", async () => {
		await runMigrations(db);

		await db
			.insertInto("users")
			.values({
				id: "user-1",
				email: "test@example.com",
				name: "Test User",
				role: 50, // ADMIN
				email_verified: 1,
			})
			.execute();

		// Duplicate email should fail
		await expect(
			db
				.insertInto("users")
				.values({
					id: "user-2",
					email: "test@example.com",
					role: 10, // SUBSCRIBER
					email_verified: 1,
				})
				.execute(),
		).rejects.toThrow();
	});

	it("should create taxonomies table with hierarchical support", async () => {
		await runMigrations(db);

		// Create parent category
		const parentId = "cat-parent";
		await db
			.insertInto("taxonomies")
			.values({
				id: parentId,
				name: "category",
				slug: "parent",
				label: "Parent Category",
			})
			.execute();

		// Create child category
		await db
			.insertInto("taxonomies")
			.values({
				id: "cat-child",
				name: "category",
				slug: "child",
				label: "Child Category",
				parent_id: parentId,
			})
			.execute();

		const child = await db
			.selectFrom("taxonomies")
			.selectAll()
			.where("id", "=", "cat-child")
			.executeTakeFirst();

		expect(child?.parent_id).toBe(parentId);
	});

	it("should create content_taxonomies junction table", async () => {
		await runMigrations(db);

		const taxonomyId = "tax-1";

		// Create taxonomy
		await db
			.insertInto("taxonomies")
			.values({
				id: taxonomyId,
				name: "category",
				slug: "tech",
				label: "Technology",
			})
			.execute();

		// Assign taxonomy to content entry (collection + entry_id)
		await db
			.insertInto("content_taxonomies")
			.values({
				collection: "posts",
				entry_id: "entry-1",
				taxonomy_id: taxonomyId,
			})
			.execute();

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

		expect(assignments).toHaveLength(1);
		expect(assignments[0]?.taxonomy_id).toBe(taxonomyId);
	});

	it("should create media table", async () => {
		await runMigrations(db);

		await db
			.insertInto("media")
			.values({
				id: "media-1",
				filename: "photo.jpg",
				mime_type: "image/jpeg",
				size: 1024000,
				width: 1920,
				height: 1080,
				alt: "Test photo",
				storage_key: "uploads/photo.jpg",
				status: "ready",
			})
			.execute();

		const media = await db
			.selectFrom("media")
			.selectAll()
			.where("id", "=", "media-1")
			.executeTakeFirst();

		expect(media).toBeDefined();
		expect(media?.width).toBe(1920);
		expect(media?.height).toBe(1080);
	});

	it("should create options table for key-value storage", async () => {
		await runMigrations(db);

		await db
			.insertInto("options")
			.values({
				name: "site_title",
				value: JSON.stringify("My Site"),
			})
			.execute();

		const option = await db
			.selectFrom("options")
			.selectAll()
			.where("name", "=", "site_title")
			.executeTakeFirst();

		expect(option).toBeDefined();
		expect(JSON.parse(option!.value)).toBe("My Site");
	});

	it("should create audit_logs table with indexes", async () => {
		await runMigrations(db);

		await db
			.insertInto("audit_logs")
			.values({
				id: "log-1",
				actor_id: "user-1",
				actor_ip: "192.168.1.1",
				action: "content:create",
				resource_type: "content",
				resource_id: "post-1",
				status: "success",
			})
			.execute();

		const logs = await db
			.selectFrom("audit_logs")
			.selectAll()
			.where("actor_id", "=", "user-1")
			.execute();

		expect(logs).toHaveLength(1);
		expect(logs[0]?.action).toBe("content:create");
	});
});
