import { and, asc, desc, eq, max, sql } from "drizzle-orm";
import type { db as prodDb } from "../../db/index.ts";
import {
	categories,
	globalItems,
	items,
	threadCandidates,
	threads,
} from "../../db/schema.ts";
import type {
	CreateCandidate,
	CreateThread,
	ReorderCandidates,
} from "../../shared/types.ts";
import { getOrCreateUncategorized } from "./category.service.ts";

type Db = typeof prodDb;

export async function createThread(db: Db, userId: number, data: CreateThread) {
	const [row] = await db
		.insert(threads)
		.values({ name: data.name, categoryId: data.categoryId, userId })
		.returning();

	return row;
}

export async function getAllThreads(
	db: Db,
	userId: number,
	includeResolved = false,
) {
	const baseCondition = eq(threads.userId, userId);
	const whereCondition = includeResolved
		? baseCondition
		: and(baseCondition, eq(threads.status, "active"));

	return db
		.select({
			id: threads.id,
			name: threads.name,
			status: threads.status,
			resolvedCandidateId: threads.resolvedCandidateId,
			categoryId: threads.categoryId,
			categoryName: categories.name,
			categoryIcon: categories.icon,
			createdAt: threads.createdAt,
			updatedAt: threads.updatedAt,
			candidateCount: sql<number>`(
        SELECT COUNT(*) FROM thread_candidates
        WHERE thread_candidates.thread_id = threads.id
      )`.as("candidate_count"),
			minPriceCents: sql<number | null>`(
        SELECT MIN(price_cents) FROM thread_candidates
        WHERE thread_candidates.thread_id = threads.id
      )`.as("min_price_cents"),
			maxPriceCents: sql<number | null>`(
        SELECT MAX(price_cents) FROM thread_candidates
        WHERE thread_candidates.thread_id = threads.id
      )`.as("max_price_cents"),
		})
		.from(threads)
		.innerJoin(categories, eq(threads.categoryId, categories.id))
		.where(whereCondition)
		.orderBy(desc(threads.createdAt));
}

export async function getThreadWithCandidates(
	db: Db,
	userId: number,
	threadId: number,
) {
	const [thread] = await db
		.select()
		.from(threads)
		.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));
	if (!thread) return null;

	const candidateList = await db
		.select({
			id: threadCandidates.id,
			threadId: threadCandidates.threadId,
			name: sql<string>`COALESCE(
				CASE WHEN ${threadCandidates.globalItemId} IS NOT NULL
					THEN ${globalItems.brand} || ' ' || ${globalItems.model}
					ELSE ${threadCandidates.name}
				END,
				${threadCandidates.name}
			)`.as("name"),
			weightGrams: sql<number | null>`COALESCE(
				CASE WHEN ${threadCandidates.globalItemId} IS NOT NULL THEN ${globalItems.weightGrams} ELSE NULL END,
				${threadCandidates.weightGrams}
			)`.as("weight_grams"),
			priceCents: sql<number | null>`COALESCE(
				CASE WHEN ${threadCandidates.globalItemId} IS NOT NULL THEN ${globalItems.priceCents} ELSE NULL END,
				${threadCandidates.priceCents}
			)`.as("price_cents"),
			categoryId: threadCandidates.categoryId,
			notes: threadCandidates.notes,
			productUrl: threadCandidates.productUrl,
			imageFilename: sql<string | null>`COALESCE(
				${threadCandidates.imageFilename},
				CASE WHEN ${threadCandidates.globalItemId} IS NOT NULL THEN ${globalItems.imageUrl} ELSE NULL END
			)`.as("image_filename"),
			imageSourceUrl: threadCandidates.imageSourceUrl,
			status: threadCandidates.status,
			pros: threadCandidates.pros,
			cons: threadCandidates.cons,
			globalItemId: threadCandidates.globalItemId,
			createdAt: threadCandidates.createdAt,
			updatedAt: threadCandidates.updatedAt,
			categoryName: categories.name,
			categoryIcon: categories.icon,
		})
		.from(threadCandidates)
		.innerJoin(categories, eq(threadCandidates.categoryId, categories.id))
		.leftJoin(globalItems, eq(threadCandidates.globalItemId, globalItems.id))
		.where(eq(threadCandidates.threadId, threadId))
		.orderBy(asc(threadCandidates.sortOrder));

	return { ...thread, candidates: candidateList };
}

export async function updateThread(
	db: Db,
	userId: number,
	threadId: number,
	data: Partial<{ name: string; categoryId: number }>,
) {
	const [existing] = await db
		.select({ id: threads.id })
		.from(threads)
		.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));
	if (!existing) return null;

	const [row] = await db
		.update(threads)
		.set({ ...data, updatedAt: new Date() })
		.where(and(eq(threads.id, threadId), eq(threads.userId, userId)))
		.returning();

	return row;
}

export async function deleteThread(db: Db, userId: number, threadId: number) {
	const [thread] = await db
		.select()
		.from(threads)
		.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));
	if (!thread) return null;

	// Collect candidate image filenames for cleanup
	const candidatesWithImages = (
		await db
			.select({ imageFilename: threadCandidates.imageFilename })
			.from(threadCandidates)
			.where(eq(threadCandidates.threadId, threadId))
	).filter((c) => c.imageFilename != null);

	await db
		.delete(threads)
		.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));

	return {
		...thread,
		candidateImages: candidatesWithImages.map((c) => c.imageFilename!),
	};
}

export async function createCandidate(
	db: Db,
	userId: number,
	threadId: number,
	data: Partial<CreateCandidate> & {
		name: string;
		categoryId: number;
		imageFilename?: string;
		imageSourceUrl?: string;
	},
) {
	// Verify the parent thread belongs to this user
	const [thread] = await db
		.select({ id: threads.id })
		.from(threads)
		.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));
	if (!thread) return null;

	const [maxRow] = await db
		.select({ maxOrder: max(threadCandidates.sortOrder) })
		.from(threadCandidates)
		.where(eq(threadCandidates.threadId, threadId));

	const nextSortOrder = (maxRow?.maxOrder ?? 0) + 1000;

	const [row] = await db
		.insert(threadCandidates)
		.values({
			threadId,
			name: data.name,
			weightGrams: data.weightGrams ?? null,
			priceCents: data.priceCents ?? null,
			categoryId: data.categoryId,
			notes: data.notes ?? null,
			productUrl: data.productUrl ?? null,
			imageFilename: data.imageFilename ?? null,
			imageSourceUrl: data.imageSourceUrl ?? null,
			status: data.status ?? "researching",
			pros: data.pros ?? null,
			cons: data.cons ?? null,
			sortOrder: nextSortOrder,
			globalItemId: data.globalItemId ?? null,
		})
		.returning();

	return row;
}

export async function updateCandidate(
	db: Db,
	userId: number,
	candidateId: number,
	data: Partial<{
		name: string;
		weightGrams: number;
		priceCents: number;
		categoryId: number;
		notes: string;
		productUrl: string;
		imageFilename: string;
		imageSourceUrl: string;
		status: "researching" | "ordered" | "arrived";
		pros: string;
		cons: string;
	}>,
) {
	// Verify the candidate's parent thread belongs to this user
	const [existing] = await db
		.select({
			id: threadCandidates.id,
			threadId: threadCandidates.threadId,
		})
		.from(threadCandidates)
		.where(eq(threadCandidates.id, candidateId));
	if (!existing) return null;

	const [thread] = await db
		.select({ id: threads.id })
		.from(threads)
		.where(and(eq(threads.id, existing.threadId), eq(threads.userId, userId)));
	if (!thread) return null;

	const [row] = await db
		.update(threadCandidates)
		.set({ ...data, updatedAt: new Date() })
		.where(eq(threadCandidates.id, candidateId))
		.returning();

	return row;
}

export async function deleteCandidate(
	db: Db,
	userId: number,
	candidateId: number,
) {
	// Verify the candidate's parent thread belongs to this user
	const [candidate] = await db
		.select()
		.from(threadCandidates)
		.where(eq(threadCandidates.id, candidateId));
	if (!candidate) return null;

	const [thread] = await db
		.select({ id: threads.id })
		.from(threads)
		.where(and(eq(threads.id, candidate.threadId), eq(threads.userId, userId)));
	if (!thread) return null;

	await db.delete(threadCandidates).where(eq(threadCandidates.id, candidateId));
	return candidate;
}

export async function reorderCandidates(
	db: Db,
	userId: number,
	threadId: number,
	orderedIds: ReorderCandidates["orderedIds"],
): Promise<{ success: boolean; error?: string }> {
	return await db.transaction(async (tx) => {
		const [thread] = await tx
			.select()
			.from(threads)
			.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));
		if (!thread) {
			return { success: false, error: "Thread not found" };
		}
		if (thread.status !== "active") {
			return { success: false, error: "Thread not active" };
		}

		for (let i = 0; i < orderedIds.length; i++) {
			await tx
				.update(threadCandidates)
				.set({ sortOrder: (i + 1) * 1000 })
				.where(eq(threadCandidates.id, orderedIds[i]));
		}

		return { success: true };
	});
}

export async function resolveThread(
	db: Db,
	userId: number,
	threadId: number,
	candidateId: number,
): Promise<{ success: boolean; item?: any; error?: string }> {
	return await db.transaction(async (tx) => {
		// 1. Check thread is active and belongs to user
		const [thread] = await tx
			.select()
			.from(threads)
			.where(and(eq(threads.id, threadId), eq(threads.userId, userId)));
		if (!thread || thread.status !== "active") {
			return { success: false, error: "Thread not active" };
		}

		// 2. Get the candidate data
		const [candidate] = await tx
			.select()
			.from(threadCandidates)
			.where(eq(threadCandidates.id, candidateId));
		if (!candidate) {
			return { success: false, error: "Candidate not found" };
		}
		if (candidate.threadId !== threadId) {
			return { success: false, error: "Candidate not in thread" };
		}

		// 3. Verify categoryId belongs to user, fallback to Uncategorized
		const [category] = await tx
			.select({ id: categories.id })
			.from(categories)
			.where(
				and(
					eq(categories.id, candidate.categoryId),
					eq(categories.userId, userId),
				),
			);
		const safeCategoryId = category
			? candidate.categoryId
			: await getOrCreateUncategorized(tx as unknown as Db, userId);

		// 4. Create collection item — branched on catalog link
		let insertValues: Record<string, unknown>;
		if (candidate.globalItemId) {
			// Reference item — link to global, personal fields only
			const [gi] = await tx
				.select()
				.from(globalItems)
				.where(eq(globalItems.id, candidate.globalItemId));
			const fallbackName = gi ? `${gi.brand} ${gi.model}` : candidate.name;
			insertValues = {
				name: fallbackName,
				globalItemId: candidate.globalItemId,
				categoryId: safeCategoryId,
				userId,
				notes: candidate.notes,
				imageFilename: candidate.imageFilename,
				imageSourceUrl: candidate.imageSourceUrl,
				quantity: 1,
			};
		} else {
			// Standalone item — full data copy (existing behavior)
			insertValues = {
				name: candidate.name,
				weightGrams: candidate.weightGrams,
				priceCents: candidate.priceCents,
				categoryId: safeCategoryId,
				userId,
				notes: candidate.notes,
				productUrl: candidate.productUrl,
				imageFilename: candidate.imageFilename,
				imageSourceUrl: candidate.imageSourceUrl,
				quantity: 1,
			};
		}
		const [newItem] = await tx
			.insert(items)
			.values(insertValues as any)
			.returning();

		// 5. Archive the thread
		await tx
			.update(threads)
			.set({
				status: "resolved",
				resolvedCandidateId: candidateId,
				updatedAt: new Date(),
			})
			.where(eq(threads.id, threadId));

		return { success: true, item: newItem };
	});
}
