/**
 * MCP content lifecycle tests.
 *
 * Covers two contracts that callers rely on:
 *
 * - `content_unpublish` clears `published_at` so a missing/null timestamp
 *   unambiguously means the item is not currently live. Re-publishing
 *   assigns a fresh timestamp.
 * - `schema_create_collection` applies its documented default of
 *   `['drafts', 'revisions']` for `supports` when the caller omits it.
 *   Explicit `[]` is preserved as an opt-out.
 */

import { Role } from "@emdash-cms/auth";
import type { Kysely } from "kysely";
import { afterEach, beforeEach, describe, expect, it } from "vitest";

import type { Database } from "../../../src/database/types.js";
import {
	connectMcpHarness,
	extractJson,
	extractText,
	type McpHarness,
} from "../../utils/mcp-runtime.js";
import {
	setupTestDatabaseWithCollections,
	teardownTestDatabase,
	setupTestDatabase,
} from "../../utils/test-db.js";

const ADMIN_ID = "user_admin";

// ---------------------------------------------------------------------------
// Bug #10: unpublish publishedAt
// ---------------------------------------------------------------------------

describe("MCP content_unpublish — publishedAt clearing (bug #10)", () => {
	let db: Kysely<Database>;
	let harness: McpHarness;

	beforeEach(async () => {
		db = await setupTestDatabaseWithCollections();
		harness = await connectMcpHarness({ db, userId: ADMIN_ID, userRole: Role.ADMIN });
	});

	afterEach(async () => {
		if (harness) await harness.cleanup();
		await teardownTestDatabase(db);
	});

	it("unpublish clears publishedAt so 'currently live' is unambiguous", async () => {
		const created = await harness.client.callTool({
			name: "content_create",
			arguments: { collection: "post", data: { title: "Will publish" } },
		});
		const id = extractJson<{ item: { id: string } }>(created).item.id;

		// Publish — populates publishedAt
		const published = await harness.client.callTool({
			name: "content_publish",
			arguments: { collection: "post", id },
		});
		const publishedItem = extractJson<{ item: { publishedAt: string | null } }>(published);
		expect(publishedItem.item.publishedAt).toBeTruthy();

		// Unpublish — should clear publishedAt
		const unpublished = await harness.client.callTool({
			name: "content_unpublish",
			arguments: { collection: "post", id },
		});
		const unpubItem = extractJson<{
			item: { publishedAt: string | null; status: string };
		}>(unpublished);

		expect(unpubItem.item.status).toBe("draft");
		// Bug #10: today, publishedAt is still the old timestamp.
		expect(unpubItem.item.publishedAt).toBeNull();
	});

	it("content_get after unpublish reflects null publishedAt and status=draft", async () => {
		const created = await harness.client.callTool({
			name: "content_create",
			arguments: { collection: "post", data: { title: "T" } },
		});
		const id = extractJson<{ item: { id: string } }>(created).item.id;
		await harness.client.callTool({
			name: "content_publish",
			arguments: { collection: "post", id },
		});
		await harness.client.callTool({
			name: "content_unpublish",
			arguments: { collection: "post", id },
		});

		const got = await harness.client.callTool({
			name: "content_get",
			arguments: { collection: "post", id },
		});
		const gotItem = extractJson<{
			item: { publishedAt: string | null; status: string };
		}>(got);
		expect(gotItem.item.status).toBe("draft");
		expect(gotItem.item.publishedAt).toBeNull();
	});

	it("re-publish after unpublish gets a fresh publishedAt timestamp", async () => {
		const created = await harness.client.callTool({
			name: "content_create",
			arguments: { collection: "post", data: { title: "T" } },
		});
		const id = extractJson<{ item: { id: string } }>(created).item.id;

		const firstPub = await harness.client.callTool({
			name: "content_publish",
			arguments: { collection: "post", id },
		});
		const firstTs = extractJson<{ item: { publishedAt: string } }>(firstPub).item.publishedAt;
		expect(firstTs).toBeTruthy();

		await harness.client.callTool({
			name: "content_unpublish",
			arguments: { collection: "post", id },
		});

		// Wait briefly so the new timestamp is distinguishable
		await new Promise((r) => setTimeout(r, 5));

		const secondPub = await harness.client.callTool({
			name: "content_publish",
			arguments: { collection: "post", id },
		});
		const secondTs = extractJson<{ item: { publishedAt: string } }>(secondPub).item.publishedAt;
		expect(secondTs).toBeTruthy();
		// Should be a new timestamp, not the old one.
		expect(secondTs).not.toBe(firstTs);
	});
});

// ---------------------------------------------------------------------------
// Bug #11: schema_create_collection supports default
// ---------------------------------------------------------------------------

describe("MCP schema_create_collection — supports default (bug #11)", () => {
	let db: Kysely<Database>;
	let harness: McpHarness;

	beforeEach(async () => {
		db = await setupTestDatabase();
		harness = await connectMcpHarness({ db, userId: ADMIN_ID, userRole: Role.ADMIN });
	});

	afterEach(async () => {
		if (harness) await harness.cleanup();
		await teardownTestDatabase(db);
	});

	it("creating a collection without `supports` uses documented default ['drafts', 'revisions']", async () => {
		const result = await harness.client.callTool({
			name: "schema_create_collection",
			arguments: { slug: "article", label: "Articles" },
		});
		expect(result.isError, extractText(result)).toBeFalsy();
		const created = extractJson<{ supports: string[] }>(result);

		// Bug #11: today this is [] or null. After fix: ['drafts', 'revisions'].
		expect(created.supports).toEqual(expect.arrayContaining(["drafts", "revisions"]));
	});

	it("explicit empty supports array is preserved (regression guard — opt-out)", async () => {
		const result = await harness.client.callTool({
			name: "schema_create_collection",
			arguments: { slug: "minimal", label: "Minimal", supports: [] },
		});
		expect(result.isError, extractText(result)).toBeFalsy();
		const created = extractJson<{ supports: string[] }>(result);
		expect(created.supports).toEqual([]);
	});

	it("explicit supports list is preserved exactly (regression guard)", async () => {
		const result = await harness.client.callTool({
			name: "schema_create_collection",
			arguments: {
				slug: "blog",
				label: "Blog",
				supports: ["drafts", "revisions", "scheduling"],
			},
		});
		expect(result.isError, extractText(result)).toBeFalsy();
		const created = extractJson<{ supports: string[] }>(result);
		expect(created.supports.toSorted()).toEqual(["drafts", "revisions", "scheduling"].toSorted());
	});

	it("default-supports collection accepts publish/unpublish/revision flows immediately", async () => {
		// Default supports should include drafts + revisions, so the standard
		// publish/unpublish lifecycle should work without further config.
		await harness.client.callTool({
			name: "schema_create_collection",
			arguments: { slug: "story", label: "Stories" },
		});
		await harness.client.callTool({
			name: "schema_create_field",
			arguments: { collection: "story", slug: "title", label: "Title", type: "string" },
		});

		const created = await harness.client.callTool({
			name: "content_create",
			arguments: { collection: "story", data: { title: "T" } },
		});
		expect(created.isError, extractText(created)).toBeFalsy();
		const id = extractJson<{ item: { id: string } }>(created).item.id;

		// Update should create a draft revision (only meaningful if 'revisions' is in supports)
		await harness.client.callTool({
			name: "content_update",
			arguments: { collection: "story", id, data: { title: "Updated" } },
		});

		const revs = await harness.client.callTool({
			name: "revision_list",
			arguments: { collection: "story", id },
		});
		// If supports doesn't include 'revisions', revision_list returns empty
		// or fails. After fix: revisions exist.
		expect(revs.isError, extractText(revs)).toBeFalsy();
		const items = extractJson<{ items: unknown[] }>(revs).items;
		expect(items.length).toBeGreaterThan(0);
	});
});
