import { userQuests, quests } from "@db/schema";
import { eq, and } from "drizzle-orm";
import { UserError } from "@shared/lib/errors";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { economyService } from "@shared/modules/economy/economy.service";
import { levelingService } from "@shared/modules/leveling/leveling.service";
import { gameSettingsService } from "@shared/modules/game-settings/game-settings.service";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { TransactionType } from "@shared/lib/constants";
import { systemEvents, EVENTS } from "@shared/lib/events";

export const questService = {
    /** Assign a quest to a user; enforces the maximum active quest limit. */
    assignQuest: async (userId: string, questId: number, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            // Check active quest limit
            const settings = await gameSettingsService.getSettings();
            const maxActive = settings?.quest?.maxActiveQuests ?? 3;

            const userQuestList = await txFn.query.userQuests.findMany({
                where: eq(userQuests.userId, BigInt(userId)),
            });

            const activeCount = userQuestList.filter(uq => !uq.completedAt).length;

            if (activeCount >= maxActive) {
                throw new UserError(
                    `You can only have ${maxActive} active quests at a time. Complete a quest before accepting a new one.`
                );
            }

            return await txFn.insert(userQuests)
                .values({
                    userId: BigInt(userId),
                    questId: questId,
                    progress: 0,
                })
                .onConflictDoNothing()
                .returning();
        }, tx);
    },

    /** Set the progress value for a user's active quest. */
    updateProgress: async (userId: string, questId: number, progress: number, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            return await txFn.update(userQuests)
                .set({ progress: progress })
                .where(and(
                    eq(userQuests.userId, BigInt(userId)),
                    eq(userQuests.questId, questId)
                ))
                .returning();
        }, tx);
    },

    /** Process a domain event against active quests, incrementing progress and auto-completing if target is met. */
    handleEvent: async (userId: string, eventName: string, weight: number = 1, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            // 1. Fetch active user quests for this event
            const activeUserQuests = await txFn.query.userQuests.findMany({
                where: and(
                    eq(userQuests.userId, BigInt(userId)),
                ),
                with: {
                    quest: true
                }
            });

            const relevant = activeUserQuests.filter(uq => {
                const trigger = uq.quest.triggerEvent;
                // Exact match or prefix match (e.g. ITEM_COLLECT matches ITEM_COLLECT:101)
                const isMatch = eventName === trigger || eventName.startsWith(trigger + ":");
                return isMatch && !uq.completedAt;
            });

            for (const uq of relevant) {
                const requirements = uq.quest.requirements as { target?: number };
                const target = requirements?.target || 1;

                const newProgress = (uq.progress || 0) + weight;

                if (newProgress >= target) {
                    await questService.completeQuest(userId, uq.questId, txFn);
                } else {
                    await questService.updateProgress(userId, uq.questId, newProgress, txFn);
                }
            }
        }, tx);
    },

    /** Mark a quest as completed and distribute its XP and balance rewards. */
    completeQuest: async (userId: string, questId: number, tx?: Transaction) => {
        return await withTransaction(async (txFn) => {
            const userQuest = await txFn.query.userQuests.findFirst({
                where: and(
                    eq(userQuests.userId, BigInt(userId)),
                    eq(userQuests.questId, questId)
                ),
                with: {
                    quest: true,
                }
            });

            if (!userQuest) throw new UserError("Quest not assigned");
            if (userQuest.completedAt) throw new UserError("Quest already completed");

            // Mark completed
            await txFn.update(userQuests)
                .set({ completedAt: new Date() })
                .where(and(
                    eq(userQuests.userId, BigInt(userId)),
                    eq(userQuests.questId, questId)
                ));

            // Distribute Rewards
            const rewards = userQuest.quest.rewards as { xp?: number, balance?: number };
            const results = { xp: 0n, balance: 0n };

            if (rewards?.balance) {
                const bal = BigInt(rewards.balance);
                await economyService.modifyUserBalance(userId, bal, TransactionType.QUEST_REWARD, `Reward for quest ${questId}`, null, txFn);
                results.balance = bal;
            }

            if (rewards?.xp) {
                const xp = BigInt(rewards.xp);
                await levelingService.addXp(userId, xp, txFn);
                results.xp = xp;
            }

            // Emit completion event for the bot to handle notifications
            systemEvents.emit(EVENTS.QUEST.COMPLETED, {
                userId,
                questId,
                quest: userQuest.quest,
                rewards: results
            });

            return { success: true, rewards: results };
        }, tx);
    },

    /** Get all quests assigned to a user, including quest details. */
    getUserQuests: async (userId: string) => {
        return await DrizzleClient.query.userQuests.findMany({
            where: eq(userQuests.userId, BigInt(userId)),
            with: {
                quest: true,
            }
        });
    },

    /** Get quests not yet assigned to a user. */
    async getAvailableQuests(userId: string) {
        const userQuestIds = (await DrizzleClient.query.userQuests.findMany({
            where: eq(userQuests.userId, BigInt(userId)),
            columns: {
                questId: true
            }
        })).map(uq => uq.questId);

        return await DrizzleClient.query.quests.findMany({
            where: (quests, { notInArray }) => userQuestIds.length > 0
                ? notInArray(quests.id, userQuestIds)
                : undefined
        });
    },

    /** Create a new quest definition with trigger event, requirements, and rewards. */
    async createQuest(data: {
        name: string;
        description: string;
        triggerEvent: string;
        requirements: { target: number };
        rewards: { xp: number; balance: number };
    }, tx?: Transaction) {
        return await withTransaction(async (txFn) => {
            return await txFn.insert(quests)
                .values({
                    name: data.name,
                    description: data.description,
                    triggerEvent: data.triggerEvent,
                    requirements: data.requirements,
                    rewards: data.rewards,
                })
                .returning();
        }, tx);
    },

    /** List all quest definitions, ordered by ID. */
    async getAllQuests() {
        return await DrizzleClient.query.quests.findMany({
            orderBy: (quests, { asc }) => [asc(quests.id)],
        });
    },

    /** Delete a quest definition by ID. */
    async deleteQuest(id: number, tx?: Transaction) {
        return await withTransaction(async (txFn) => {
            return await txFn.delete(quests)
                .where(eq(quests.id, id))
                .returning();
        }, tx);
    },

    /** Update a quest definition with partial data. */
    async updateQuest(id: number, data: {
        name?: string;
        description?: string;
        triggerEvent?: string;
        requirements?: { target?: number };
        rewards?: { xp?: number; balance?: number };
    }, tx?: Transaction) {
        return await withTransaction(async (txFn) => {
            return await txFn.update(quests)
                .set({
                    ...(data.name !== undefined && { name: data.name }),
                    ...(data.description !== undefined && { description: data.description }),
                    ...(data.triggerEvent !== undefined && { triggerEvent: data.triggerEvent }),
                    ...(data.requirements !== undefined && { requirements: data.requirements }),
                    ...(data.rewards !== undefined && { rewards: data.rewards }),
                })
                .where(eq(quests.id, id))
                .returning();
        }, tx);
    }
};
