import type { SQL } from "drizzle-orm";
import { and, count, eq, ilike, or, sql } from "drizzle-orm";
import { db as prodDb } from "../../db/index.ts";
import { globalItems, globalItemTags, items, tags } from "../../db/schema.ts";

type Db = typeof prodDb;
type TxDb = Parameters<Parameters<Db["transaction"]>[0]>[0];

/**
 * Search global items by brand or model and/or tag names.
 * Text search uses ILIKE for case-insensitive matching (PostgreSQL).
 * Tag filtering uses AND logic -- items must have ALL specified tags.
 * Escapes % and _ wildcard characters in user input.
 */
export async function searchGlobalItems(
	db: Db = prodDb,
	query?: string,
	tagNames?: string[],
) {
	const conditions: SQL[] = [];

	if (query) {
		const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
		const pattern = `%${escaped}%`;
		conditions.push(
			or(ilike(globalItems.brand, pattern), ilike(globalItems.model, pattern))!,
		);
	}

	if (tagNames && tagNames.length > 0) {
		conditions.push(
			sql`${globalItems.id} IN (
				SELECT ${globalItemTags.globalItemId}
				FROM ${globalItemTags}
				JOIN ${tags} ON ${tags.id} = ${globalItemTags.tagId}
				WHERE ${tags.name} IN (${sql.join(
					tagNames.map((t) => sql`${t}`),
					sql`, `,
				)})
				GROUP BY ${globalItemTags.globalItemId}
				HAVING COUNT(DISTINCT ${tags.name}) = ${tagNames.length}
			)`,
		);
	}

	if (conditions.length === 0) {
		return db.select().from(globalItems);
	}

	return db
		.select()
		.from(globalItems)
		.where(and(...conditions));
}

/**
 * Get a single global item by ID with the count of user items referencing it
 * via items.globalItemId.
 */
export async function getGlobalItemWithOwnerCount(db: Db = prodDb, id: number) {
	const [item] = await db
		.select()
		.from(globalItems)
		.where(eq(globalItems.id, id));

	if (!item) return null;

	const [result] = await db
		.select({ ownerCount: count() })
		.from(items)
		.where(eq(items.globalItemId, id));

	return { ...item, ownerCount: result?.ownerCount ?? 0 };
}

/**
 * Sync tags for a global item: delete existing, re-insert provided tag names.
 * Creates tags that don't exist yet (create-if-not-exists).
 */
async function syncGlobalItemTags(
	tx: TxDb,
	globalItemId: number,
	tagNames: string[],
) {
	await tx
		.delete(globalItemTags)
		.where(eq(globalItemTags.globalItemId, globalItemId));

	for (const name of tagNames) {
		const [tag] = await tx
			.insert(tags)
			.values({ name })
			.onConflictDoUpdate({ target: tags.name, set: { name } })
			.returning({ id: tags.id });

		await tx.insert(globalItemTags).values({ globalItemId, tagId: tag.id });
	}
}

/**
 * Upsert a single global item by (brand, model).
 * Creates if not exists, updates all non-key fields if exists.
 * Tag sync: provided → sync; undefined → leave untouched; [] → clear all tags.
 */
export async function upsertGlobalItem(
	db: Db,
	data: {
		brand: string;
		model: string;
		category?: string;
		weightGrams?: number;
		priceCents?: number;
		imageUrl?: string;
		description?: string;
		sourceUrl?: string;
		imageCredit?: string;
		imageSourceUrl?: string;
		tags?: string[];
	},
) {
	return await db.transaction(async (tx) => {
		const [existing] = await tx
			.select({ id: globalItems.id })
			.from(globalItems)
			.where(
				and(
					eq(globalItems.brand, data.brand),
					eq(globalItems.model, data.model),
				),
			);

		const { tags: tagNames, ...itemData } = data;

		const [item] = await tx
			.insert(globalItems)
			.values({
				brand: itemData.brand,
				model: itemData.model,
				category: itemData.category ?? null,
				weightGrams: itemData.weightGrams ?? null,
				priceCents: itemData.priceCents ?? null,
				imageUrl: itemData.imageUrl ?? null,
				description: itemData.description ?? null,
				sourceUrl: itemData.sourceUrl ?? null,
				imageCredit: itemData.imageCredit ?? null,
				imageSourceUrl: itemData.imageSourceUrl ?? null,
			})
			.onConflictDoUpdate({
				target: [globalItems.brand, globalItems.model],
				set: {
					category: itemData.category ?? null,
					weightGrams: itemData.weightGrams ?? null,
					priceCents: itemData.priceCents ?? null,
					imageUrl: itemData.imageUrl ?? null,
					description: itemData.description ?? null,
					sourceUrl: itemData.sourceUrl ?? null,
					imageCredit: itemData.imageCredit ?? null,
					imageSourceUrl: itemData.imageSourceUrl ?? null,
				},
			})
			.returning();

		if (tagNames !== undefined) {
			await syncGlobalItemTags(tx, item.id, tagNames);
		}

		return { item, created: !existing };
	});
}

/**
 * Bulk upsert global items in a single transaction.
 * Returns { created, updated, items } with accurate counts.
 * Rolls back entirely if any item fails.
 */
export async function bulkUpsertGlobalItems(
	db: Db,
	itemsData: Array<{
		brand: string;
		model: string;
		category?: string;
		weightGrams?: number;
		priceCents?: number;
		imageUrl?: string;
		description?: string;
		sourceUrl?: string;
		imageCredit?: string;
		imageSourceUrl?: string;
		tags?: string[];
	}>,
) {
	return await db.transaction(async (tx) => {
		let created = 0;
		let updated = 0;
		const resultItems = [];

		for (const data of itemsData) {
			const [existing] = await tx
				.select({ id: globalItems.id })
				.from(globalItems)
				.where(
					and(
						eq(globalItems.brand, data.brand),
						eq(globalItems.model, data.model),
					),
				);

			const { tags: tagNames, ...itemData } = data;

			const [item] = await tx
				.insert(globalItems)
				.values({
					brand: itemData.brand,
					model: itemData.model,
					category: itemData.category ?? null,
					weightGrams: itemData.weightGrams ?? null,
					priceCents: itemData.priceCents ?? null,
					imageUrl: itemData.imageUrl ?? null,
					description: itemData.description ?? null,
					sourceUrl: itemData.sourceUrl ?? null,
					imageCredit: itemData.imageCredit ?? null,
					imageSourceUrl: itemData.imageSourceUrl ?? null,
				})
				.onConflictDoUpdate({
					target: [globalItems.brand, globalItems.model],
					set: {
						category: itemData.category ?? null,
						weightGrams: itemData.weightGrams ?? null,
						priceCents: itemData.priceCents ?? null,
						imageUrl: itemData.imageUrl ?? null,
						description: itemData.description ?? null,
						sourceUrl: itemData.sourceUrl ?? null,
						imageCredit: itemData.imageCredit ?? null,
						imageSourceUrl: itemData.imageSourceUrl ?? null,
					},
				})
				.returning();

			if (tagNames !== undefined) {
				await syncGlobalItemTags(tx, item.id, tagNames);
			}

			if (existing) {
				updated++;
			} else {
				created++;
			}
			resultItems.push(item);
		}

		return { created, updated, items: resultItems };
	});
}
