import { beforeEach, describe, expect, it } from "bun:test";
import { Hono } from "hono";
import {
	globalItems,
	globalItemTags,
	items,
	tags,
} from "../../src/db/schema.ts";
import { globalItemRoutes } from "../../src/server/routes/global-items.ts";
import { createTestDb } from "../helpers/db.ts";

type TestDb = Awaited<ReturnType<typeof createTestDb>>;

async function createTestApp() {
	const { db, userId } = await createTestDb();
	const app = new Hono();

	app.use("*", async (c, next) => {
		c.set("db", db);
		c.set("userId", userId);
		await next();
	});

	app.route("/api/global-items", globalItemRoutes);
	return { app, db, userId };
}

async function insertGlobalItem(
	db: TestDb["db"],
	brand: string,
	model: string,
) {
	const [row] = await db
		.insert(globalItems)
		.values({ brand, model, category: "bags" })
		.returning();
	return row;
}

async function insertItem(
	db: TestDb["db"],
	name: string,
	userId: number,
	opts?: { globalItemId?: number },
) {
	const [row] = await db
		.insert(items)
		.values({ name, categoryId: 1, userId, globalItemId: opts?.globalItemId })
		.returning();
	return row;
}

describe("Global Item Routes", () => {
	let app: Hono;
	let db: TestDb["db"];
	let userId: number;

	beforeEach(async () => {
		const testApp = await createTestApp();
		app = testApp.app;
		db = testApp.db;
		userId = testApp.userId;
	});

	describe("GET /api/global-items", () => {
		it("returns 200 with all global items", async () => {
			await insertGlobalItem(db, "Revelate Designs", "Terrapin System");
			await insertGlobalItem(db, "Apidura", "Handlebar Pack");

			const res = await app.request("/api/global-items");
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body).toHaveLength(2);
		});

		it("filters results by query parameter", async () => {
			await insertGlobalItem(db, "Revelate Designs", "Terrapin System");
			await insertGlobalItem(db, "Apidura", "Handlebar Pack");

			const res = await app.request("/api/global-items?q=revelate");
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body).toHaveLength(1);
			expect(body[0].brand).toBe("Revelate Designs");
		});

		it("filters results by tags parameter", async () => {
			const gi1 = await insertGlobalItem(
				db,
				"Revelate Designs",
				"Terrapin System",
			);
			await insertGlobalItem(db, "Apidura", "Handlebar Pack");

			const [tag] = await db
				.insert(tags)
				.values({ name: "ultralight" })
				.returning();
			await db
				.insert(globalItemTags)
				.values({ globalItemId: gi1.id, tagId: tag.id });

			const res = await app.request("/api/global-items?tags=ultralight");
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body).toHaveLength(1);
			expect(body[0].brand).toBe("Revelate Designs");
		});
	});

	describe("POST /api/global-items", () => {
		it("returns 200 with item and created=true on new item", async () => {
			const res = await app.request("/api/global-items", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					brand: "Revelate Designs",
					model: "Terrapin System",
				}),
			});
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body.item.brand).toBe("Revelate Designs");
			expect(body.item.model).toBe("Terrapin System");
			expect(body.created).toBe(true);
		});

		it("returns 200 with created=false when upserting existing item", async () => {
			await insertGlobalItem(db, "Revelate Designs", "Terrapin System");

			const res = await app.request("/api/global-items", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					brand: "Revelate Designs",
					model: "Terrapin System",
					description: "Updated description",
				}),
			});
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body.created).toBe(false);
			expect(body.item.description).toBe("Updated description");
		});

		it("returns 400 when brand is missing", async () => {
			const res = await app.request("/api/global-items", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ model: "Terrapin System" }),
			});
			expect(res.status).toBe(400);
		});

		it("returns 400 when model is missing", async () => {
			const res = await app.request("/api/global-items", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ brand: "Revelate Designs" }),
			});
			expect(res.status).toBe(400);
		});
	});

	describe("POST /api/global-items/bulk", () => {
		it("returns 200 with created/updated counts", async () => {
			const res = await app.request("/api/global-items/bulk", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					items: [
						{ brand: "Revelate Designs", model: "Terrapin System" },
						{ brand: "Apidura", model: "Handlebar Pack" },
					],
				}),
			});
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body.created).toBe(2);
			expect(body.updated).toBe(0);
			expect(body.items).toHaveLength(2);
		});

		it("returns correct counts for mix of new and existing items", async () => {
			await insertGlobalItem(db, "Revelate Designs", "Terrapin System");

			const res = await app.request("/api/global-items/bulk", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					items: [
						{ brand: "Revelate Designs", model: "Terrapin System" },
						{ brand: "Apidura", model: "Handlebar Pack" },
					],
				}),
			});
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body.created).toBe(1);
			expect(body.updated).toBe(1);
		});

		it("returns 400 when items array is empty", async () => {
			const res = await app.request("/api/global-items/bulk", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ items: [] }),
			});
			expect(res.status).toBe(400);
		});

		it("returns 400 when items array exceeds 100", async () => {
			const items = Array.from({ length: 101 }, (_, i) => ({
				brand: `Brand${i}`,
				model: `Model${i}`,
			}));
			const res = await app.request("/api/global-items/bulk", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ items }),
			});
			expect(res.status).toBe(400);
		});

		it("returns 400 for invalid item in array (missing brand)", async () => {
			const res = await app.request("/api/global-items/bulk", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					items: [
						{ brand: "Revelate Designs", model: "Terrapin System" },
						{ model: "Invalid Item without brand" },
					],
				}),
			});
			expect(res.status).toBe(400);
		});
	});

	describe("GET /api/global-items/:id", () => {
		it("returns item with ownerCount", async () => {
			const gi = await insertGlobalItem(db, "MSR", "PocketRocket 2");

			const res = await app.request(`/api/global-items/${gi.id}`);
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body.brand).toBe("MSR");
			expect(body.ownerCount).toBe(0);
		});

		it("returns ownerCount from items.globalItemId", async () => {
			const gi = await insertGlobalItem(db, "MSR", "PocketRocket 2");
			await insertItem(db, "My Stove", userId, {
				globalItemId: gi.id,
			});

			const res = await app.request(`/api/global-items/${gi.id}`);
			expect(res.status).toBe(200);

			const body = await res.json();
			expect(body.ownerCount).toBe(1);
		});

		it("returns 404 for non-existent id", async () => {
			const res = await app.request("/api/global-items/999");
			expect(res.status).toBe(404);
		});

		it("returns 400 for invalid id", async () => {
			const res = await app.request("/api/global-items/abc");
			expect(res.status).toBe(400);
		});
	});
});
