import { inventory, items, users, userTimers } from "@db/schema";
import { eq, and, sql, count, ilike } from "drizzle-orm";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { economyService } from "@shared/modules/economy/economy.service";
import { levelingService } from "@shared/modules/leveling/leveling.service";
import { config } from "@shared/lib/config";
import { UserError } from "@shared/lib/errors";
import { withTransaction } from "@/lib/db";
import type { Transaction, ItemUsageData } from "@shared/lib/types";
import { TransactionType } from "@shared/lib/constants";



export const inventoryService = {
    addItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            // Check if item exists in inventory
            const existing = await txFn.query.inventory.findFirst({
                where: and(
                    eq(inventory.userId, BigInt(userId)),
                    eq(inventory.itemId, itemId)
                ),
            });

            if (existing) {
                const newQuantity = (existing.quantity ?? 0n) + quantity;
                if (newQuantity > config.inventory.maxStackSize) {
                    throw new UserError(`Cannot exceed max stack size of ${config.inventory.maxStackSize}`);
                }

                const [entry] = await txFn.update(inventory)
                    .set({
                        quantity: newQuantity,
                    })
                    .where(and(
                        eq(inventory.userId, BigInt(userId)),
                        eq(inventory.itemId, itemId)
                    ))
                    .returning();

                // Trigger Quest Event
                const { questService } = await import("@shared/modules/quest/quest.service");
                await questService.handleEvent(userId, `ITEM_COLLECT:${itemId}`, Number(quantity), txFn);

                return entry;
            } else {
                // Check Slot Limit
                const [inventoryCount] = await txFn
                    .select({ count: count() })
                    .from(inventory)
                    .where(eq(inventory.userId, BigInt(userId)));

                if (inventoryCount && inventoryCount.count >= config.inventory.maxSlots) {
                    throw new UserError(`Inventory full (Max ${config.inventory.maxSlots} slots)`);
                }

                if (quantity > config.inventory.maxStackSize) {
                    throw new UserError(`Cannot exceed max stack size of ${config.inventory.maxStackSize}`);
                }

                const [entry] = await txFn.insert(inventory)
                    .values({
                        userId: BigInt(userId),
                        itemId: itemId,
                        quantity: quantity,
                    })
                    .returning();

                // Trigger Quest Event
                const { questService } = await import("@shared/modules/quest/quest.service");
                await questService.handleEvent(userId, `ITEM_COLLECT:${itemId}`, Number(quantity), txFn);

                return entry;
            }
        }, tx);
    },

    removeItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            const existing = await txFn.query.inventory.findFirst({
                where: and(
                    eq(inventory.userId, BigInt(userId)),
                    eq(inventory.itemId, itemId)
                ),
            });

            if (!existing || (existing.quantity ?? 0n) < quantity) {
                throw new UserError("Insufficient item quantity");
            }

            if ((existing.quantity ?? 0n) === quantity) {
                // Delete if quantity becomes 0
                await txFn.delete(inventory)
                    .where(and(
                        eq(inventory.userId, BigInt(userId)),
                        eq(inventory.itemId, itemId)
                    ));
                return { itemId, quantity: 0n, userId: BigInt(userId) };
            } else {
                const [entry] = await txFn.update(inventory)
                    .set({
                        quantity: sql`${inventory.quantity} - ${quantity}`,
                    })
                    .where(and(
                        eq(inventory.userId, BigInt(userId)),
                        eq(inventory.itemId, itemId)
                    ))
                    .returning();
                return entry;
            }
        }, tx);
    },

    getInventory: async (userId: string) => {
        return await DrizzleClient.query.inventory.findMany({
            where: eq(inventory.userId, BigInt(userId)),
            with: {
                item: true,
            },
        });
    },

    buyItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            const item = await txFn.query.items.findFirst({
                where: eq(items.id, itemId),
            });

            if (!item) throw new UserError("Item not found");
            if (!item.price) throw new UserError("Item is not for sale");

            const totalPrice = item.price * quantity;

            // Deduct Balance using economy service (passing tx ensures atomicity)
            await economyService.modifyUserBalance(userId, -totalPrice, TransactionType.PURCHASE, `Bought ${quantity}x ${item.name}`, null, txFn);

            await inventoryService.addItem(userId, itemId, quantity, txFn);

            return { success: true, item, totalPrice };
        }, tx);
    },

    getItem: async (itemId: number) => {
        return await DrizzleClient.query.items.findFirst({
            where: eq(items.id, itemId),
        });
    },

    useItem: async (userId: string, itemId: number, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            // 1. Check Ownership & Quantity
            const entry = await txFn.query.inventory.findFirst({
                where: and(
                    eq(inventory.userId, BigInt(userId)),
                    eq(inventory.itemId, itemId)
                ),
                with: { item: true }
            });

            if (!entry || (entry.quantity ?? 0n) < 1n) {
                throw new UserError("You do not own this item.");
            }

            const item = entry.item;
            const usageData = item.usageData as ItemUsageData | null;

            if (!usageData || !usageData.effects || usageData.effects.length === 0) {
                throw new UserError("This item cannot be used.");
            }

            const results: string[] = [];

            // 2. Apply Effects
            const { effectHandlers } = await import("@/modules/inventory/effects/registry");

            for (const effect of usageData.effects) {
                const handler = effectHandlers[effect.type];
                if (handler) {
                    const result = await handler(userId, effect, txFn);
                    results.push(result);
                } else {
                    console.warn(`No handler found for effect type: ${effect.type}`);
                    results.push(`Effect ${effect.type} applied (no description)`);
                }
            }

            // 3. Consume
            if (usageData.consume) {
                await inventoryService.removeItem(userId, itemId, 1n, txFn);
            }

            // Trigger Quest Event
            const { questService } = await import("@shared/modules/quest/quest.service");
            await questService.handleEvent(userId, `ITEM_USE:${itemId}`, 1, txFn);

            return { success: true, results, usageData, item };
        }, tx);
    },

    getAutocompleteItems: async (userId: string, query: string) => {
        const entries = await DrizzleClient.select({
            quantity: inventory.quantity,
            item: items
        })
            .from(inventory)
            .innerJoin(items, eq(inventory.itemId, items.id))
            .where(and(
                eq(inventory.userId, BigInt(userId)),
                ilike(items.name, `%${query}%`)
            ))
            .limit(20);

        const filtered = entries.filter((entry: any) => {
            const usageData = entry.item.usageData as ItemUsageData | null;
            return usageData && usageData.effects && usageData.effects.length > 0;
        });

        return filtered.map((entry: any) => ({
            name: `${entry.item.name} (${entry.quantity}) [${entry.item.rarity || 'Common'}]`,
            value: entry.item.id
        }));
    }
};
