// ── Dev Seed Runner ────────────────────────────────────────────────
// Idempotent script to populate a dev database with realistic data.
// Usage: bun run db:seed:dev

import { and, eq } from "drizzle-orm";
import {
	DEV_CATEGORIES,
	DEV_GLOBAL_ITEMS,
	DEV_SETTINGS,
	DEV_SETUPS,
	DEV_TAG_ASSIGNMENTS,
	DEV_THREADS,
	DEV_USER_ITEMS,
} from "./dev-seed-data.ts";
import { db } from "./index.ts";
import * as schema from "./schema.ts";
import { seedGlobalItems } from "./seed-global-items.ts";

type Db = typeof db;

async function seedDevData(database: Db = db) {
	// ── Idempotency check ──────────────────────────────────────────
	const existing = await database
		.select()
		.from(schema.users)
		.where(eq(schema.users.logtoSub, "dev-user-seed"))
		.limit(1);

	if (existing.length > 0) {
		console.log("Dev seed data already exists, skipping.");
		return;
	}

	try {
		// ── 1. Seed global items and tags ──────────────────────────
		await seedGlobalItems(database);
		console.log("  Global items and tags seeded.");

		// ── 2. Insert dev user ─────────────────────────────────────
		const [user] = await database
			.insert(schema.users)
			.values({
				logtoSub: "dev-user-seed",
				displayName: "Dev User",
				bio: "Bikepacking enthusiast and gear nerd. Always optimizing the kit.",
			})
			.returning();
		if (!user) throw new Error("Failed to insert dev user");
		const userId = user.id;
		console.log(`  Dev user created (id=${userId}).`);

		// ── 3. Insert categories ───────────────────────────────────
		const insertedCategories = await database
			.insert(schema.categories)
			.values(
				DEV_CATEGORIES.map((c) => ({
					name: c.name,
					icon: c.icon,
					userId,
				})),
			)
			.returning();

		const categoryByName = new Map<string, number>();
		for (const cat of insertedCategories) {
			categoryByName.set(cat.name, cat.id);
		}
		console.log(`  ${insertedCategories.length} categories created.`);

		// ── 4. Look up tag IDs ─────────────────────────────────────
		const allTags = await database.select().from(schema.tags);
		const tagNameToId = new Map<string, number>();
		for (const tag of allTags) {
			tagNameToId.set(tag.name, tag.id);
		}

		// ── 5. Insert global items and tag assignments ─────────────
		// DEV_GLOBAL_ITEMS may overlap with seed-global-items.json entries.
		// Insert only items that don't already exist (by brand+model).
		const existingGlobalItems = await database
			.select()
			.from(schema.globalItems);
		const existingGlobalItemMap = new Map<string, number>();
		for (const gi of existingGlobalItems) {
			existingGlobalItemMap.set(`${gi.brand}::${gi.model}`, gi.id);
		}

		const globalItemIds: number[] = [];
		let newGlobalCount = 0;

		for (const item of DEV_GLOBAL_ITEMS) {
			const key = `${item.brand}::${item.model}`;
			const existingId = existingGlobalItemMap.get(key);
			if (existingId) {
				globalItemIds.push(existingId);
			} else {
				const [inserted] = await database
					.insert(schema.globalItems)
					.values({
						brand: item.brand,
						model: item.model,
						category: item.category,
						weightGrams: item.weightGrams,
						priceCents: item.priceCents,
						description: item.description,
					})
					.returning();
				if (!inserted)
					throw new Error(
						`Failed to insert global item: ${item.brand} ${item.model}`,
					);
				globalItemIds.push(inserted.id);
				newGlobalCount++;
			}
		}
		console.log(
			`  ${globalItemIds.length} global items mapped (${newGlobalCount} new).`,
		);

		// Insert tag assignments
		let tagAssignmentCount = 0;
		for (const assignment of DEV_TAG_ASSIGNMENTS) {
			const giId = globalItemIds[assignment.globalItemIndex];
			if (!giId) continue;
			for (const tagName of assignment.tagNames) {
				const tagId = tagNameToId.get(tagName);
				if (!tagId) continue;
				// Skip if already exists
				const existingLink = await database
					.select()
					.from(schema.globalItemTags)
					.where(
						and(
							eq(schema.globalItemTags.globalItemId, giId),
							eq(schema.globalItemTags.tagId, tagId),
						),
					)
					.limit(1);
				if (existingLink.length === 0) {
					await database
						.insert(schema.globalItemTags)
						.values({ globalItemId: giId, tagId });
					tagAssignmentCount++;
				}
			}
		}
		console.log(`  ${tagAssignmentCount} tag assignments created.`);

		// ── 6. Insert user items ───────────────────────────────────
		const userItemValues = DEV_USER_ITEMS.map((item) => ({
			name: item.name,
			weightGrams: item.weightGrams,
			priceCents: item.priceCents,
			categoryId: categoryByName.get(item.categoryName)!,
			userId,
			notes: item.notes,
			quantity: item.quantity,
			globalItemId:
				item.globalItemIndex !== null
					? globalItemIds[item.globalItemIndex]
					: null,
			purchasePriceCents: item.purchasePriceCents,
		}));

		const insertedItems = await database
			.insert(schema.items)
			.values(userItemValues)
			.returning();
		console.log(`  ${insertedItems.length} user items created.`);

		// ── 7. Insert threads ──────────────────────────────────────
		const threadResults: Array<{
			threadId: number;
			threadDef: (typeof DEV_THREADS)[number];
		}> = [];

		for (const threadDef of DEV_THREADS) {
			const catId = categoryByName.get(threadDef.categoryName);
			if (!catId) continue;

			const [thread] = await database
				.insert(schema.threads)
				.values({
					name: threadDef.name,
					status: threadDef.status,
					categoryId: catId,
					userId,
				})
				.returning();
			if (!thread)
				throw new Error(`Failed to insert thread: ${threadDef.name}`);
			threadResults.push({ threadId: thread.id, threadDef });
		}
		console.log(`  ${threadResults.length} threads created.`);

		// ── 8. Insert thread candidates ────────────────────────────
		let candidateCount = 0;
		for (const { threadId, threadDef } of threadResults) {
			const catId = categoryByName.get(threadDef.categoryName)!;

			const insertedCandidates = [];
			for (const cand of threadDef.candidates) {
				const [inserted] = await database
					.insert(schema.threadCandidates)
					.values({
						threadId,
						name: cand.name,
						weightGrams: cand.weightGrams,
						priceCents: cand.priceCents,
						categoryId: catId,
						status: cand.status,
						pros: cand.pros,
						cons: cand.cons,
						notes: cand.notes,
						sortOrder: cand.sortOrder,
						globalItemId:
							cand.globalItemIndex !== null
								? globalItemIds[cand.globalItemIndex]
								: null,
					})
					.returning();
				insertedCandidates.push(inserted);
				candidateCount++;
			}

			// Set resolvedCandidateId for resolved threads
			if (
				"resolvedCandidateIndex" in threadDef &&
				threadDef.resolvedCandidateIndex !== undefined
			) {
				const winnerCandidate =
					insertedCandidates[threadDef.resolvedCandidateIndex];
				if (winnerCandidate) {
					await database
						.update(schema.threads)
						.set({ resolvedCandidateId: winnerCandidate.id })
						.where(eq(schema.threads.id, threadId));
				}
			}
		}
		console.log(`  ${candidateCount} thread candidates created.`);

		// ── 9. Insert setups ───────────────────────────────────────
		const setupResults: Array<{
			setupId: number;
			setupDef: (typeof DEV_SETUPS)[number];
		}> = [];

		for (const setupDef of DEV_SETUPS) {
			const [setup] = await database
				.insert(schema.setups)
				.values({
					name: setupDef.name,
					userId,
					visibility: setupDef.visibility,
				})
				.returning();
			if (!setup) throw new Error(`Failed to insert setup: ${setupDef.name}`);
			setupResults.push({ setupId: setup.id, setupDef });
		}
		console.log(`  ${setupResults.length} setups created.`);

		// ── 10. Insert setup items ─────────────────────────────────
		let setupItemCount = 0;
		for (const { setupId, setupDef } of setupResults) {
			for (const si of setupDef.items) {
				const userItem = insertedItems[si.userItemIndex];
				if (!userItem) continue;
				await database.insert(schema.setupItems).values({
					setupId,
					itemId: userItem.id,
					classification: si.classification,
				});
				setupItemCount++;
			}
		}
		console.log(`  ${setupItemCount} setup items created.`);

		// ── 11. Insert settings ────────────────────────────────────
		for (const setting of DEV_SETTINGS) {
			await database.insert(schema.settings).values({
				userId,
				key: setting.key,
				value: setting.value,
			});
		}
		console.log(`  ${DEV_SETTINGS.length} settings created.`);

		// ── Summary ────────────────────────────────────────────────
		console.log(
			`\nDev seed complete: ${globalItemIds.length} global items, ${allTags.length} tags, ${insertedItems.length} user items, ${threadResults.length} threads, ${setupResults.length} setups`,
		);
	} catch (err) {
		console.error("Seed failed:", err);
		throw err;
	}
}

// ── Entry point ────────────────────────────────────────────────────

seedDevData()
	.then(() => process.exit(0))
	.catch((err) => {
		console.error("Seed failed:", err);
		process.exit(1);
	});
