import { and, eq, inArray, sql } from "drizzle-orm";
import type { db as prodDb } from "../../db/index.ts";
import {
	categories,
	globalItems,
	items,
	setupItems,
	setups,
} from "../../db/schema.ts";
import type { CreateSetup, UpdateSetup } from "../../shared/types.ts";

type Db = typeof prodDb;

export async function createSetup(db: Db, userId: number, data: CreateSetup) {
	const [row] = await db
		.insert(setups)
		.values({
			name: data.name,
			userId,
			visibility: data.visibility ?? "private",
		})
		.returning();

	return row;
}

export async function getAllSetups(db: Db, userId: number) {
	return db
		.select({
			id: setups.id,
			name: setups.name,
			visibility: setups.visibility,
			createdAt: setups.createdAt,
			updatedAt: setups.updatedAt,
			itemCount: sql<number>`COALESCE((
        SELECT COUNT(*) FROM setup_items
        WHERE setup_items.setup_id = setups.id
      ), 0)`.as("item_count"),
			totalWeight: sql<number>`COALESCE((
        SELECT SUM(
          COALESCE(
            CASE WHEN items.global_item_id IS NOT NULL THEN global_items.weight_grams ELSE NULL END,
            items.weight_grams
          ) * items.quantity
        ) FROM setup_items
        JOIN items ON items.id = setup_items.item_id
        LEFT JOIN global_items ON global_items.id = items.global_item_id
        WHERE setup_items.setup_id = setups.id
      ), 0)`.as("total_weight"),
			totalCost: sql<number>`COALESCE((
        SELECT SUM(
          COALESCE(
            CASE WHEN items.global_item_id IS NOT NULL THEN global_items.price_cents ELSE NULL END,
            items.price_cents
          ) * items.quantity
        ) FROM setup_items
        JOIN items ON items.id = setup_items.item_id
        LEFT JOIN global_items ON global_items.id = items.global_item_id
        WHERE setup_items.setup_id = setups.id
      ), 0)`.as("total_cost"),
		})
		.from(setups)
		.where(eq(setups.userId, userId));
}

export async function getSetupWithItems(
	db: Db,
	userId: number,
	setupId: number,
) {
	const [setup] = await db
		.select()
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!setup) return null;

	const itemList = await db
		.select({
			id: items.id,
			name: sql<string>`COALESCE(
				CASE WHEN ${items.globalItemId} IS NOT NULL
					THEN ${globalItems.brand} || ' ' || ${globalItems.model}
					ELSE ${items.name}
				END,
				${items.name}
			)`.as("name"),
			weightGrams: sql<number | null>`COALESCE(
				CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.weightGrams} ELSE NULL END,
				${items.weightGrams}
			)`.as("weight_grams"),
			priceCents: sql<number | null>`COALESCE(
				CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.priceCents} ELSE NULL END,
				${items.priceCents}
			)`.as("price_cents"),
			quantity: items.quantity,
			categoryId: items.categoryId,
			notes: items.notes,
			productUrl: items.productUrl,
			imageFilename: sql<string | null>`COALESCE(
				${items.imageFilename},
				CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.imageUrl} ELSE NULL END
			)`.as("image_filename"),
			globalItemId: items.globalItemId,
			purchasePriceCents: items.purchasePriceCents,
			createdAt: items.createdAt,
			updatedAt: items.updatedAt,
			categoryName: categories.name,
			categoryIcon: categories.icon,
			classification: setupItems.classification,
		})
		.from(setupItems)
		.innerJoin(items, eq(setupItems.itemId, items.id))
		.innerJoin(categories, eq(items.categoryId, categories.id))
		.leftJoin(globalItems, eq(items.globalItemId, globalItems.id))
		.where(eq(setupItems.setupId, setupId));

	return { ...setup, items: itemList };
}

export async function updateSetup(
	db: Db,
	userId: number,
	setupId: number,
	data: UpdateSetup,
) {
	const [existing] = await db
		.select({ id: setups.id })
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!existing) return null;

	const updateData: Record<string, unknown> = {
		name: data.name,
		updatedAt: new Date(),
	};
	if (data.visibility !== undefined) {
		updateData.visibility = data.visibility;
	}

	const [row] = await db
		.update(setups)
		.set(updateData)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)))
		.returning();

	return row;
}

export async function deleteSetup(db: Db, userId: number, setupId: number) {
	const [existing] = await db
		.select({ id: setups.id })
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!existing) return false;

	await db
		.delete(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	return true;
}

export async function syncSetupItems(
	db: Db,
	userId: number,
	setupId: number,
	itemIds: number[],
) {
	return await db.transaction(async (tx) => {
		// Verify the setup belongs to this user
		const [setup] = await tx
			.select({ id: setups.id })
			.from(setups)
			.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
		if (!setup) return null;

		// Verify all itemIds belong to this user
		const validItems =
			itemIds.length > 0
				? await tx
						.select({ id: items.id })
						.from(items)
						.where(and(eq(items.userId, userId), inArray(items.id, itemIds)))
				: [];
		const validItemIds = new Set(validItems.map((i) => i.id));
		const filteredItemIds = itemIds.filter((id) => validItemIds.has(id));

		// Save existing classifications before deleting
		const existing = await tx
			.select({
				itemId: setupItems.itemId,
				classification: setupItems.classification,
			})
			.from(setupItems)
			.where(eq(setupItems.setupId, setupId));

		const classificationMap = new Map<number, string>();
		for (const row of existing) {
			classificationMap.set(row.itemId, row.classification);
		}

		// Delete all existing items for this setup
		await tx.delete(setupItems).where(eq(setupItems.setupId, setupId));

		// Re-insert only user-owned items, preserving classifications
		for (const itemId of filteredItemIds) {
			await tx.insert(setupItems).values({
				setupId,
				itemId,
				classification: classificationMap.get(itemId) ?? "base",
			});
		}
	});
}

export async function updateItemClassification(
	db: Db,
	userId: number,
	setupId: number,
	itemId: number,
	classification: string,
) {
	// Verify setup belongs to user
	const [setup] = await db
		.select({ id: setups.id })
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!setup) return null;

	await db
		.update(setupItems)
		.set({ classification })
		.where(and(eq(setupItems.setupId, setupId), eq(setupItems.itemId, itemId)));
}

export async function removeSetupItem(
	db: Db,
	userId: number,
	setupId: number,
	itemId: number,
) {
	// Verify setup belongs to user
	const [setup] = await db
		.select({ id: setups.id })
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!setup) return null;

	await db
		.delete(setupItems)
		.where(and(eq(setupItems.setupId, setupId), eq(setupItems.itemId, itemId)));
}
