import { describe, expect, test } from "bun:test";
import { getCollectionSummary } from "../../src/server/mcp/resources/collection.ts";
import { registerCatalogTools } from "../../src/server/mcp/tools/catalog.ts";
import { registerCategoryTools } from "../../src/server/mcp/tools/categories.ts";
import { registerItemTools } from "../../src/server/mcp/tools/items.ts";
import { registerSetupTools } from "../../src/server/mcp/tools/setups.ts";
import { registerThreadTools } from "../../src/server/mcp/tools/threads.ts";
import { createSecondTestUser, createTestDb } from "../helpers/db.ts";

function parseResult(result: {
	content: Array<{ type: string; text: string }>;
}) {
	return JSON.parse(result.content[0].text);
}

describe("MCP Item Tools", () => {
	test("list_items returns array", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerItemTools(db, userId);
		const result = await tools.list_items({});
		const data = parseResult(result);
		expect(Array.isArray(data)).toBe(true);
	});

	test("create_item creates and returns item", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerItemTools(db, userId);
		const result = await tools.create_item({
			name: "Test Tent",
			categoryId: 1,
			weightGrams: 1200,
			priceCents: 35000,
		});
		const data = parseResult(result);
		expect(data.name).toBe("Test Tent");
		expect(data.weightGrams).toBe(1200);
		expect(data.priceCents).toBe(35000);
		expect(data.id).toBeDefined();
	});

	test("get_item retrieves by ID", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerItemTools(db, userId);
		const created = parseResult(
			await tools.create_item({ name: "Sleeping Bag", categoryId: 1 }),
		);
		const result = await tools.get_item({ id: created.id });
		const data = parseResult(result);
		expect(data.name).toBe("Sleeping Bag");
		expect(data.id).toBe(created.id);
	});

	test("get_item returns error for missing item", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerItemTools(db, userId);
		const result = await tools.get_item({ id: 999 });
		const data = parseResult(result);
		expect(data.error).toContain("not found");
	});

	test("delete_item removes item", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerItemTools(db, userId);
		const created = parseResult(
			await tools.create_item({ name: "To Delete", categoryId: 1 }),
		);
		const deleteResult = await tools.delete_item({ id: created.id });
		const data = parseResult(deleteResult);
		expect(data.deleted).toBe(true);

		// Verify it's gone
		const getResult = await tools.get_item({ id: created.id });
		const getData = parseResult(getResult);
		expect(getData.error).toContain("not found");
	});
});

describe("MCP Category Tools", () => {
	test("list_categories returns array with Uncategorized", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerCategoryTools(db, userId);
		const result = await tools.list_categories();
		const data = parseResult(result);
		expect(Array.isArray(data)).toBe(true);
		expect(data.length).toBeGreaterThanOrEqual(1);
		expect(data.some((c: any) => c.name === "Uncategorized")).toBe(true);
	});

	test("create_category creates a new category", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerCategoryTools(db, userId);
		const result = await tools.create_category({
			name: "Shelter",
			icon: "tent",
		});
		const data = parseResult(result);
		expect(data.name).toBe("Shelter");
		expect(data.icon).toBe("tent");
	});
});

describe("MCP Thread Tools", () => {
	test("create_thread starts a thread with status active", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerThreadTools(db, userId);
		const result = await tools.create_thread({
			name: "Handlebar Bag",
			categoryId: 1,
		});
		const data = parseResult(result);
		expect(data.name).toBe("Handlebar Bag");
		expect(data.status).toBe("active");
	});

	test("add_candidate adds to thread", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerThreadTools(db, userId);
		const thread = parseResult(
			await tools.create_thread({ name: "Saddle Bag", categoryId: 1 }),
		);
		const result = await tools.add_candidate({
			threadId: thread.id,
			name: "Apidura Racing",
			categoryId: 1,
			priceCents: 8500,
			pros: "Lightweight",
			cons: "Expensive",
		});
		const data = parseResult(result);
		expect(data.name).toBe("Apidura Racing");
		expect(data.threadId).toBe(thread.id);
		expect(data.pros).toBe("Lightweight");
	});

	test("resolve_thread picks winner and creates item", async () => {
		const { db, userId } = await createTestDb();
		const threadTools = registerThreadTools(db, userId);
		const itemTools = registerItemTools(db, userId);

		// Create thread with two candidates
		const thread = parseResult(
			await threadTools.create_thread({ name: "Frame Bag", categoryId: 1 }),
		);
		const candidate1 = parseResult(
			await threadTools.add_candidate({
				threadId: thread.id,
				name: "Revelate Tangle",
				categoryId: 1,
				priceCents: 12000,
			}),
		);
		await threadTools.add_candidate({
			threadId: thread.id,
			name: "Ortlieb Frame Pack",
			categoryId: 1,
			priceCents: 9000,
		});

		// Resolve with first candidate
		const resolveResult = await threadTools.resolve_thread({
			threadId: thread.id,
			candidateId: candidate1.id,
		});
		const resolveData = parseResult(resolveResult);
		expect(resolveData.success).toBe(true);
		expect(resolveData.item.name).toBe("Revelate Tangle");

		// Check item was added to collection
		const items = parseResult(await itemTools.list_items({}));
		expect(items.some((i: any) => i.name === "Revelate Tangle")).toBe(true);

		// Check thread is now resolved
		const threadList = parseResult(
			await threadTools.list_threads({ includeResolved: true }),
		);
		const resolved = threadList.find((t: any) => t.id === thread.id);
		expect(resolved.status).toBe("resolved");
	});
});

describe("MCP Setup Tools", () => {
	test("create_setup and list_setups", async () => {
		const { db, userId } = await createTestDb();
		const tools = registerSetupTools(db, userId);
		await tools.create_setup({ name: "Weekend Trip" });
		const result = await tools.list_setups();
		const data = parseResult(result);
		expect(data.length).toBe(1);
		expect(data[0].name).toBe("Weekend Trip");
	});

	test("get_setup returns setup with items", async () => {
		const { db, userId } = await createTestDb();
		const setupTools = registerSetupTools(db, userId);
		const itemTools = registerItemTools(db, userId);

		const setup = parseResult(
			await setupTools.create_setup({ name: "Overnighter" }),
		);
		const item = parseResult(
			await itemTools.create_item({ name: "Bivvy", categoryId: 1 }),
		);
		await setupTools.update_setup({ id: setup.id, itemIds: [item.id] });

		const result = await setupTools.get_setup({ id: setup.id });
		const data = parseResult(result);
		expect(data.name).toBe("Overnighter");
		expect(data.items.length).toBe(1);
		expect(data.items[0].name).toBe("Bivvy");
	});
});

describe("MCP Collection Summary Resource", () => {
	test("returns overview with correct counts", async () => {
		const { db, userId } = await createTestDb();

		const summary = await getCollectionSummary(db, userId);
		expect(summary.overview).toBeDefined();
		expect(summary.overview.totalItems).toBe(0);
		expect(summary.overview.categoryCount).toBe(1); // Uncategorized
		expect(summary.itemsByCategory).toBeDefined();
		expect(summary.activeThreads).toBeDefined();
		expect(Array.isArray(summary.activeThreads)).toBe(true);
	});

	test("reflects items and threads after creation", async () => {
		const { db, userId } = await createTestDb();
		const itemTools = registerItemTools(db, userId);
		const threadTools = registerThreadTools(db, userId);

		await itemTools.create_item({
			name: "Tent",
			categoryId: 1,
			weightGrams: 1500,
		});
		await itemTools.create_item({
			name: "Sleeping Pad",
			categoryId: 1,
			weightGrams: 500,
		});
		await threadTools.create_thread({
			name: "Cook System",
			categoryId: 1,
		});

		const summary = await getCollectionSummary(db, userId);
		expect(summary.overview.totalItems).toBe(2);
		expect(summary.overview.totalWeightGrams).toBe(2000);
		expect(summary.overview.activeThreadCount).toBe(1);
		expect(summary.itemsByCategory.Uncategorized).toBe(2);
		expect(summary.activeThreads.length).toBe(1);
		expect(summary.activeThreads[0].name).toBe("Cook System");
	});
});

describe("MCP Catalog Tools", () => {
	test("upsert_catalog_item creates a new global item with created=true", async () => {
		const { db } = await createTestDb();
		const tools = registerCatalogTools(db);
		const result = await tools.upsert_catalog_item({
			brand: "Revelate Designs",
			model: "Terrapin System",
			weightGrams: 235,
			priceCents: 16500,
		});
		const data = parseResult(result);
		expect(data.brand).toBe("Revelate Designs");
		expect(data.model).toBe("Terrapin System");
		expect(data.created).toBe(true);
		expect(data.id).toBeDefined();
	});

	test("upsert_catalog_item updates existing item on brand+model match", async () => {
		const { db } = await createTestDb();
		const tools = registerCatalogTools(db);

		// Create initial item
		await tools.upsert_catalog_item({
			brand: "Apidura",
			model: "Handlebar Pack",
		});

		// Update it
		const result = await tools.upsert_catalog_item({
			brand: "Apidura",
			model: "Handlebar Pack",
			description: "Updated description",
			weightGrams: 120,
		});
		const data = parseResult(result);
		expect(data.created).toBe(false);
		expect(data.description).toBe("Updated description");
		expect(data.weightGrams).toBe(120);
	});

	test("upsert_catalog_item includes attribution fields in result (SEED-03)", async () => {
		const { db } = await createTestDb();
		const tools = registerCatalogTools(db);

		const result = await tools.upsert_catalog_item({
			brand: "MSR",
			model: "PocketRocket 2",
			sourceUrl: "https://www.cascadedesigns.com/msr/pocket-rocket-2",
			imageCredit: "MSR Photography",
			imageSourceUrl:
				"https://cdn.cascadedesigns.com/images/pocket-rocket-2.jpg",
		});
		const data = parseResult(result);
		expect(data.sourceUrl).toBe(
			"https://www.cascadedesigns.com/msr/pocket-rocket-2",
		);
		expect(data.imageCredit).toBe("MSR Photography");
		expect(data.imageSourceUrl).toBe(
			"https://cdn.cascadedesigns.com/images/pocket-rocket-2.jpg",
		);
	});

	test("bulk_upsert_catalog processes array and returns created/updated counts", async () => {
		const { db } = await createTestDb();
		const tools = registerCatalogTools(db);

		const result = await tools.bulk_upsert_catalog({
			items: [
				{ brand: "Revelate Designs", model: "Terrapin System" },
				{ brand: "Apidura", model: "Handlebar Pack" },
				{ brand: "MSR", model: "PocketRocket 2" },
			],
		});
		const data = parseResult(result);
		expect(data.created).toBe(3);
		expect(data.updated).toBe(0);
		expect(data.totalProcessed).toBe(3);
		expect(data.items).toHaveLength(3);
	});

	test("bulk_upsert_catalog returns totalProcessed matching input length", async () => {
		const { db } = await createTestDb();
		const tools = registerCatalogTools(db);

		// Pre-create one item
		await tools.upsert_catalog_item({
			brand: "Revelate Designs",
			model: "Terrapin System",
		});

		const result = await tools.bulk_upsert_catalog({
			items: [
				{ brand: "Revelate Designs", model: "Terrapin System" },
				{ brand: "Apidura", model: "Handlebar Pack" },
			],
		});
		const data = parseResult(result);
		expect(data.totalProcessed).toBe(2);
		expect(data.created).toBe(1);
		expect(data.updated).toBe(1);
	});

	test("catalog tool definitions include attribution fields in inputSchema", () => {
		const {
			catalogToolDefinitions,
		} = require("../../src/server/mcp/tools/catalog.ts");
		const upsertDef = catalogToolDefinitions.find(
			(d: { name: string }) => d.name === "upsert_catalog_item",
		);
		expect(upsertDef).toBeDefined();
		expect(upsertDef.inputSchema.sourceUrl).toBeDefined();
		expect(upsertDef.inputSchema.imageCredit).toBeDefined();
		expect(upsertDef.inputSchema.imageSourceUrl).toBeDefined();
	});
});

describe("MCP Cross-User Isolation", () => {
	test("user 2 cannot see user 1's items via MCP tools", async () => {
		const { db, userId } = await createTestDb();
		const userId2 = await createSecondTestUser(db);

		const user1Tools = registerItemTools(db, userId);
		const user2Tools = registerItemTools(db, userId2);

		// User 1 creates an item
		await user1Tools.create_item({
			name: "User 1 Tent",
			categoryId: 1,
			weightGrams: 1200,
		});

		// User 2 creates an item
		await user2Tools.create_item({
			name: "User 2 Sleeping Bag",
			categoryId: 1,
			weightGrams: 800,
		});

		// Each user only sees their own items
		const user1Items = parseResult(await user1Tools.list_items({}));
		const user2Items = parseResult(await user2Tools.list_items({}));

		expect(user1Items).toHaveLength(1);
		expect(user1Items[0].name).toBe("User 1 Tent");
		expect(user2Items).toHaveLength(1);
		expect(user2Items[0].name).toBe("User 2 Sleeping Bag");
	});

	test("user 2 cannot access user 1's item by ID", async () => {
		const { db, userId } = await createTestDb();
		const userId2 = await createSecondTestUser(db);

		const user1Tools = registerItemTools(db, userId);
		const user2Tools = registerItemTools(db, userId2);

		const created = parseResult(
			await user1Tools.create_item({
				name: "Private Item",
				categoryId: 1,
			}),
		);

		// User 2 tries to get user 1's item
		const result = await user2Tools.get_item({ id: created.id });
		const data = parseResult(result);
		expect(data.error).toContain("not found");
	});

	test("user 2 cannot see user 1's threads via MCP tools", async () => {
		const { db, userId } = await createTestDb();
		const userId2 = await createSecondTestUser(db);

		const user1Tools = registerThreadTools(db, userId);
		const user2Tools = registerThreadTools(db, userId2);

		await user1Tools.create_thread({
			name: "User 1 Thread",
			categoryId: 1,
		});

		const user1Threads = parseResult(
			await user1Tools.list_threads({ includeResolved: false }),
		);
		const user2Threads = parseResult(
			await user2Tools.list_threads({ includeResolved: false }),
		);

		expect(user1Threads).toHaveLength(1);
		expect(user1Threads[0].name).toBe("User 1 Thread");
		expect(user2Threads).toHaveLength(0);
	});

	test("collection summary is scoped to user", async () => {
		const { db, userId } = await createTestDb();
		const userId2 = await createSecondTestUser(db);

		const user1Tools = registerItemTools(db, userId);
		await user1Tools.create_item({
			name: "User 1 Item",
			categoryId: 1,
			weightGrams: 500,
		});

		const user1Summary = await getCollectionSummary(db, userId);
		const user2Summary = await getCollectionSummary(db, userId2);

		expect(user1Summary.overview.totalItems).toBe(1);
		expect(user2Summary.overview.totalItems).toBe(0);
	});
});
