import { beforeEach, describe, expect, it } from "bun:test";
import { eq } from "drizzle-orm";
import * as schema from "../../src/db/schema.ts";
import {
	getPublicProfile,
	getPublicSetupWithItems,
	updateProfile,
} from "../../src/server/services/profile.service.ts";
import {
	createSetup,
	getAllSetups,
	getSetupWithItems,
	updateSetup,
} from "../../src/server/services/setup.service.ts";
import { createTestDb } from "../helpers/db.ts";

type Db = Awaited<ReturnType<typeof createTestDb>>["db"];

describe("Profile Service", () => {
	let db: Db;
	let userId: number;

	beforeEach(async () => {
		const testData = await createTestDb();
		db = testData.db;
		userId = testData.userId;
	});

	describe("updateProfile", () => {
		it("updates displayName and returns updated user", async () => {
			const result = await updateProfile(db, userId, {
				displayName: "Alice",
			});
			expect(result).not.toBeNull();
			expect(result!.displayName).toBe("Alice");
		});

		it("updates bio only, leaves other fields untouched", async () => {
			await updateProfile(db, userId, { displayName: "Alice" });
			const result = await updateProfile(db, userId, { bio: "Bikepacker" });
			expect(result).not.toBeNull();
			expect(result!.bio).toBe("Bikepacker");
			expect(result!.displayName).toBe("Alice");
		});

		it("handles empty update without error", async () => {
			const result = await updateProfile(db, userId, {});
			expect(result).not.toBeNull();
			expect(result!.id).toBe(userId);
		});

		it("returns null for non-existent user", async () => {
			const result = await updateProfile(db, 99999, {
				displayName: "Ghost",
			});
			expect(result).toBeNull();
		});
	});

	describe("getPublicProfile", () => {
		it("returns user profile with empty setups when none exist", async () => {
			await updateProfile(db, userId, {
				displayName: "Alice",
				bio: "Bikepacker",
			});
			const profile = await getPublicProfile(db, userId);
			expect(profile).not.toBeNull();
			expect(profile!.displayName).toBe("Alice");
			expect(profile!.bio).toBe("Bikepacker");
			expect(profile!.setups).toEqual([]);
		});

		it("returns only public setups, not private ones", async () => {
			// Create one public and one private setup
			const _pub = await createSetup(db, userId, {
				name: "Public Setup",
				visibility: "public",
			});
			const _priv = await createSetup(db, userId, { name: "Private Setup" });

			const profile = await getPublicProfile(db, userId);
			expect(profile).not.toBeNull();
			expect(profile!.setups).toHaveLength(1);
			expect(profile!.setups[0].name).toBe("Public Setup");
		});

		it("returns null for non-existent user", async () => {
			const profile = await getPublicProfile(db, 99999);
			expect(profile).toBeNull();
		});
	});

	describe("getPublicSetupWithItems", () => {
		it("returns setup with items when visibility is public", async () => {
			const setup = await createSetup(db, userId, {
				name: "Public Setup",
				visibility: "public",
			});

			// Create an item and add to setup
			const [cat] = await db
				.select()
				.from(schema.categories)
				.where(eq(schema.categories.userId, userId));
			const [item] = await db
				.insert(schema.items)
				.values({
					name: "Tent",
					categoryId: cat.id,
					userId,
					weightGrams: 1200,
					priceCents: 30000,
				})
				.returning();

			await db.insert(schema.setupItems).values({
				setupId: setup.id,
				itemId: item.id,
			});

			const result = await getPublicSetupWithItems(db, setup.id);
			expect(result).not.toBeNull();
			expect(result!.name).toBe("Public Setup");
			expect(result!.items).toHaveLength(1);
			expect(result!.items[0].name).toBe("Tent");
		});

		it("returns null when visibility is private", async () => {
			const setup = await createSetup(db, userId, {
				name: "Private Setup",
			});
			const result = await getPublicSetupWithItems(db, setup.id);
			expect(result).toBeNull();
		});

		it("returns null for non-existent setup", async () => {
			const result = await getPublicSetupWithItems(db, 99999);
			expect(result).toBeNull();
		});
	});
});

describe("Setup Service - visibility", () => {
	let db: Db;
	let userId: number;

	beforeEach(async () => {
		const testData = await createTestDb();
		db = testData.db;
		userId = testData.userId;
	});

	it("createSetup persists visibility when public", async () => {
		const setup = await createSetup(db, userId, {
			name: "Public",
			visibility: "public",
		});
		expect(setup.visibility).toBe("public");
	});

	it("createSetup defaults visibility to private", async () => {
		const setup = await createSetup(db, userId, { name: "Private" });
		expect(setup.visibility).toBe("private");
	});

	it("updateSetup can change visibility", async () => {
		const setup = await createSetup(db, userId, { name: "Test" });
		expect(setup.visibility).toBe("private");

		const updated = await updateSetup(db, userId, setup.id, {
			name: "Test",
			visibility: "public",
		});
		expect(updated).not.toBeNull();
		expect(updated!.visibility).toBe("public");
	});

	it("getAllSetups includes visibility in response", async () => {
		await createSetup(db, userId, { name: "Public", visibility: "public" });
		await createSetup(db, userId, { name: "Private" });

		const setups = await getAllSetups(db, userId);
		expect(setups).toHaveLength(2);

		const pub = setups.find((s) => s.name === "Public");
		const priv = setups.find((s) => s.name === "Private");
		expect(pub!.visibility).toBe("public");
		expect(priv!.visibility).toBe("private");
	});

	it("getSetupWithItems includes visibility", async () => {
		const setup = await createSetup(db, userId, {
			name: "Test",
			visibility: "public",
		});
		const result = await getSetupWithItems(db, userId, setup.id);
		expect(result).not.toBeNull();
		expect(result!.visibility).toBe("public");
	});
});
