import { and, count, desc, eq, inArray, isNotNull, lt, sql } from "drizzle-orm";
import { db as prodDb } from "../../db/index.ts";
import {
	globalItems,
	globalItemTags,
	items,
	setupItems,
	setups,
	tags,
	users,
} from "../../db/schema.ts";

type Db = typeof prodDb;

interface CursorPage<T> {
	items: T[];
	nextCursor: string | null;
	hasMore: boolean;
}

/**
 * Get popular public setups ordered by item count descending.
 * Cursor format: "{itemCount}_{id}" for stable composite pagination.
 * Only public setups (visibility='public') are returned.
 */
export async function getPopularSetups(
	db: Db = prodDb,
	limit = 6,
	cursor?: string,
): Promise<
	CursorPage<{
		id: number;
		name: string;
		createdAt: Date;
		itemCount: number;
		creatorName: string | null;
	}>
> {
	// Fetch more rows when cursor provided to ensure we can filter and still fill the page
	const fetchLimit = cursor ? limit * 2 + limit + 1 : limit + 1;

	const rows = await db
		.select({
			id: setups.id,
			name: setups.name,
			createdAt: setups.createdAt,
			itemCount: sql<number>`CAST(COUNT(${setupItems.id}) AS INT)`,
			creatorName: users.displayName,
		})
		.from(setups)
		.leftJoin(setupItems, eq(setupItems.setupId, setups.id))
		.leftJoin(users, eq(users.id, setups.userId))
		.where(eq(setups.visibility, "public"))
		.groupBy(setups.id, setups.name, setups.createdAt, users.displayName)
		.orderBy(desc(sql<number>`COUNT(${setupItems.id})`), desc(setups.id))
		.limit(fetchLimit);

	// Apply cursor filter in JS (composite cursor: itemCount_id)
	let filtered = rows;
	if (cursor) {
		const parts = cursor.split("_");
		const cursorCount = Number(parts[0]);
		const cursorId = Number(parts[1]);
		filtered = rows.filter(
			(r) =>
				r.itemCount < cursorCount ||
				(r.itemCount === cursorCount && r.id < cursorId),
		);
	}

	const hasMore = filtered.length > limit;
	const items = hasMore ? filtered.slice(0, limit) : filtered;
	const nextCursor =
		hasMore && items.length > 0
			? `${items[items.length - 1].itemCount}_${items[items.length - 1].id}`
			: null;

	return { items, nextCursor, hasMore };
}

/**
 * Get recently added global catalog items ordered by createdAt descending.
 * Cursor is an ISO timestamp string — returns items created before that time.
 */
export async function getRecentGlobalItems(
	db: Db = prodDb,
	limit = 8,
	cursor?: string,
): Promise<CursorPage<typeof globalItems.$inferSelect>> {
	const conditions = cursor
		? [lt(globalItems.createdAt, new Date(cursor))]
		: [];

	const rows = await db
		.select()
		.from(globalItems)
		.where(conditions.length ? and(...conditions) : undefined)
		.orderBy(desc(globalItems.createdAt))
		.limit(limit + 1);

	const hasMore = rows.length > limit;
	const items = hasMore ? rows.slice(0, limit) : rows;
	const nextCursor =
		hasMore && items.length > 0
			? items[items.length - 1].createdAt.toISOString()
			: null;

	return { items, nextCursor, hasMore };
}

/**
 * Get trending categories ordered by global item count descending.
 * Excludes items where category IS NULL.
 * Simple limit pagination — no cursor (categories are a bounded list).
 */
export async function getTrendingCategories(
	db: Db = prodDb,
	limit = 12,
): Promise<Array<{ name: string; itemCount: number }>> {
	const rows = await db
		.select({
			name: globalItems.category,
			itemCount: count(globalItems.id),
		})
		.from(globalItems)
		.where(isNotNull(globalItems.category))
		.groupBy(globalItems.category)
		.orderBy(desc(count(globalItems.id)))
		.limit(limit);

	return rows.map((r) => ({
		name: r.name as string,
		itemCount: r.itemCount,
	}));
}

/**
 * Get popular global items filtered by tag names, ordered by owner count descending.
 * Owner count = number of user items linked to each global item via globalItemId.
 */
export async function getPopularItemsByTags(
	db: Db = prodDb,
	tagNames: string[],
	limit = 24,
): Promise<
	Array<{
		id: number;
		brand: string | null;
		model: string;
		category: string | null;
		weightGrams: number | null;
		priceCents: number | null;
		imageUrl: string | null;
		description: string | null;
		ownerCount: number;
	}>
> {
	if (tagNames.length === 0) return [];

	const rows = await db
		.select({
			id: globalItems.id,
			brand: globalItems.brand,
			model: globalItems.model,
			category: globalItems.category,
			weightGrams: globalItems.weightGrams,
			priceCents: globalItems.priceCents,
			imageUrl: globalItems.imageUrl,
			description: globalItems.description,
			ownerCount: sql<number>`CAST(COUNT(DISTINCT ${items.id}) AS INT)`,
		})
		.from(globalItems)
		.innerJoin(globalItemTags, eq(globalItemTags.globalItemId, globalItems.id))
		.innerJoin(tags, eq(tags.id, globalItemTags.tagId))
		.leftJoin(items, eq(items.globalItemId, globalItems.id))
		.where(inArray(tags.name, tagNames))
		.groupBy(globalItems.id)
		.orderBy(
			desc(sql<number>`COUNT(DISTINCT ${items.id})`),
			desc(globalItems.id),
		)
		.limit(limit);

	return rows;
}
