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

import {
	handleRevisionList,
	handleRevisionGet,
	handleRevisionRestore,
} from "../../../src/api/index.js";
import { ContentRepository } from "../../../src/database/repositories/content.js";
import { RevisionRepository } from "../../../src/database/repositories/revision.js";
import type { Database } from "../../../src/database/types.js";
import { createPostFixture } from "../../utils/fixtures.js";
import { setupTestDatabaseWithCollections, teardownTestDatabase } from "../../utils/test-db.js";

describe("Revision Handlers", () => {
	let db: Kysely<Database>;
	let contentRepo: ContentRepository;
	let revisionRepo: RevisionRepository;

	beforeEach(async () => {
		db = await setupTestDatabaseWithCollections();
		contentRepo = new ContentRepository(db);
		revisionRepo = new RevisionRepository(db);
	});

	afterEach(async () => {
		await teardownTestDatabase(db);
	});

	describe("handleRevisionList", () => {
		it("should return empty list when no revisions exist", async () => {
			const content = await contentRepo.create(createPostFixture());

			const result = await handleRevisionList(db, "post", content.id, {});

			expect(result.success).toBe(true);
			expect(result.data?.items).toEqual([]);
			expect(result.data?.total).toBe(0);
		});

		it("should return revisions for a content entry", async () => {
			const content = await contentRepo.create(createPostFixture());

			// Create some revisions with small delay to ensure distinct ULIDs
			await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "Version 1", content: "First version" },
			});
			// Small delay to ensure ULID timestamp differs
			await new Promise((resolve) => setTimeout(resolve, 2));
			await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "Version 2", content: "Second version" },
			});

			const result = await handleRevisionList(db, "post", content.id, {});

			expect(result.success).toBe(true);
			expect(result.data?.items).toHaveLength(2);
			expect(result.data?.total).toBe(2);
			// Should be newest first
			expect(result.data?.items[0].data.title).toBe("Version 2");
			expect(result.data?.items[1].data.title).toBe("Version 1");
		});

		it("should respect limit parameter", async () => {
			const content = await contentRepo.create(createPostFixture());

			// Create 5 revisions
			for (let i = 1; i <= 5; i++) {
				await revisionRepo.create({
					collection: "post",
					entryId: content.id,
					data: { title: `Version ${i}` },
				});
			}

			const result = await handleRevisionList(db, "post", content.id, {
				limit: 3,
			});

			expect(result.success).toBe(true);
			expect(result.data?.items).toHaveLength(3);
			expect(result.data?.total).toBe(5); // Total still reflects all revisions
		});

		it("should not return revisions from other entries", async () => {
			const content1 = await contentRepo.create(createPostFixture());
			const content2 = await contentRepo.create({
				...createPostFixture(),
				slug: "another-post",
			});

			await revisionRepo.create({
				collection: "post",
				entryId: content1.id,
				data: { title: "Content 1 revision" },
			});
			await revisionRepo.create({
				collection: "post",
				entryId: content2.id,
				data: { title: "Content 2 revision" },
			});

			const result = await handleRevisionList(db, "post", content1.id, {});

			expect(result.success).toBe(true);
			expect(result.data?.items).toHaveLength(1);
			expect(result.data?.items[0].data.title).toBe("Content 1 revision");
		});
	});

	describe("handleRevisionGet", () => {
		it("should return a revision by ID", async () => {
			const content = await contentRepo.create(createPostFixture());
			const revision = await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "Test Revision" },
			});

			const result = await handleRevisionGet(db, revision.id);

			expect(result.success).toBe(true);
			expect(result.data?.item.id).toBe(revision.id);
			expect(result.data?.item.data.title).toBe("Test Revision");
		});

		it("should return NOT_FOUND for non-existent revision", async () => {
			const result = await handleRevisionGet(db, "nonexistent-id");

			expect(result.success).toBe(false);
			expect(result.error?.code).toBe("NOT_FOUND");
		});
	});

	describe("handleRevisionRestore", () => {
		const callerUserId = "user_caller_123";

		it("should restore content to a previous revision", async () => {
			const content = await contentRepo.create({
				...createPostFixture(),
				data: { title: "Original", content: "Original content" },
			});

			// Create a revision with the original state
			const originalRevision = await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "Original", content: "Original content" },
			});

			// Update the content
			await contentRepo.update("post", content.id, {
				data: { title: "Updated", content: "Updated content" },
			});

			// Restore to original revision
			const result = await handleRevisionRestore(db, originalRevision.id, callerUserId);

			expect(result.success).toBe(true);
			expect(result.data?.item.data.title).toBe("Original");
			expect(result.data?.item.data.content).toBe("Original content");
		});

		it("should create a new revision when restoring", async () => {
			const content = await contentRepo.create(createPostFixture());

			const revision = await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "To restore" },
			});

			const beforeCount = await revisionRepo.countByEntry("post", content.id);

			await handleRevisionRestore(db, revision.id, callerUserId);

			const afterCount = await revisionRepo.countByEntry("post", content.id);
			expect(afterCount).toBe(beforeCount + 1);
		});

		it("should attribute the new revision to the caller", async () => {
			const content = await contentRepo.create(createPostFixture());

			const revision = await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "To restore" },
				authorId: "original_author",
			});

			await handleRevisionRestore(db, revision.id, callerUserId);

			// The newest revision (restore record) should be attributed to the caller
			const latestRevision = await revisionRepo.findLatest("post", content.id);
			expect(latestRevision).not.toBeNull();
			expect(latestRevision!.authorId).toBe(callerUserId);
		});

		it("should handle revision data containing _slug", async () => {
			const content = await contentRepo.create({
				...createPostFixture(),
				data: { title: "Original" },
			});

			// Revision data includes _slug (added by runtime when slug changes)
			const revision = await revisionRepo.create({
				collection: "post",
				entryId: content.id,
				data: { title: "With slug change", _slug: "new-slug" },
			});

			const result = await handleRevisionRestore(db, revision.id, callerUserId);

			expect(result.success).toBe(true);
			expect(result.data?.item.data.title).toBe("With slug change");
			expect(result.data?.item.slug).toBe("new-slug");
		});

		it("should return NOT_FOUND for non-existent revision", async () => {
			const result = await handleRevisionRestore(db, "nonexistent-id", callerUserId);

			expect(result.success).toBe(false);
			expect(result.error?.code).toBe("NOT_FOUND");
		});
	});
});
