import { createCommand } from "@/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { userService } from "@/modules/user/user.service";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { userTimers, users } from "@/db/schema";
import { eq, and, sql } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { config } from "@lib/config";
import { TimerType } from "@/lib/constants";

const EXAM_TIMER_TYPE = TimerType.EXAM_SYSTEM;
const EXAM_TIMER_KEY = 'default';

interface ExamMetadata {
    examDay: number;
    lastXp: string;
}

const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

export const exam = createCommand({
    data: new SlashCommandBuilder()
        .setName("exam")
        .setDescription("Take your weekly exam to earn rewards based on your XP progress."),
    execute: async (interaction) => {
        await interaction.deferReply();
        const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username);
        if (!user) {
            await interaction.editReply({ embeds: [createErrorEmbed("Failed to retrieve user data.")] });
            return;
        }
        const now = new Date();
        const currentDay = now.getDay();

        try {
            // 1. Fetch existing timer/exam data
            const timer = await DrizzleClient.query.userTimers.findFirst({
                where: and(
                    eq(userTimers.userId, user.id),
                    eq(userTimers.type, EXAM_TIMER_TYPE),
                    eq(userTimers.key, EXAM_TIMER_KEY)
                )
            });

            // 2. First Run Logic
            if (!timer) {
                // Set exam day to today
                const nextExamDate = new Date(now);
                nextExamDate.setDate(now.getDate() + 7);
                nextExamDate.setHours(0, 0, 0, 0);
                const nextExamTimestamp = Math.floor(nextExamDate.getTime() / 1000);

                const metadata: ExamMetadata = {
                    examDay: currentDay,
                    lastXp: (user.xp ?? 0n).toString()
                };

                await DrizzleClient.insert(userTimers).values({
                    userId: user.id,
                    type: EXAM_TIMER_TYPE,
                    key: EXAM_TIMER_KEY,
                    expiresAt: nextExamDate,
                    metadata: metadata
                });

                await interaction.editReply({
                    embeds: [createSuccessEmbed(
                        `You have registered for the exam! Your exam day is **${DAYS[currentDay]}** (Server Time).\n` +
                        `Come back on <t:${nextExamTimestamp}:D> (<t:${nextExamTimestamp}:R>) to take your first exam!`,
                        "Exam Registration Successful"
                    )]
                });
                return;
            }

            const metadata = timer.metadata as unknown as ExamMetadata;
            const examDay = metadata.examDay;

            // 3. Cooldown Check
            const expiresAt = new Date(timer.expiresAt);
            expiresAt.setHours(0, 0, 0, 0);

            if (now < expiresAt) {
                // Calculate time remaining
                const timestamp = Math.floor(expiresAt.getTime() / 1000);

                await interaction.editReply({
                    embeds: [createErrorEmbed(
                        `You have already taken your exam for this week (or are waiting for your first week to pass).\n` +
                        `Next exam available: <t:${timestamp}:D> (<t:${timestamp}:R>)`
                    )]
                });
                return;
            }

            // 4. Day Check
            if (currentDay !== examDay) {
                // Calculate next correct exam day to correct the schedule
                let daysUntil = (examDay - currentDay + 7) % 7;
                if (daysUntil === 0) daysUntil = 7;

                const nextExamDate = new Date(now);
                nextExamDate.setDate(now.getDate() + daysUntil);
                nextExamDate.setHours(0, 0, 0, 0);
                const nextExamTimestamp = Math.floor(nextExamDate.getTime() / 1000);

                const newMetadata: ExamMetadata = {
                    examDay: examDay,
                    lastXp: (user.xp ?? 0n).toString()
                };

                await DrizzleClient.update(userTimers)
                    .set({
                        expiresAt: nextExamDate,
                        metadata: newMetadata
                    })
                    .where(and(
                        eq(userTimers.userId, user.id),
                        eq(userTimers.type, EXAM_TIMER_TYPE),
                        eq(userTimers.key, EXAM_TIMER_KEY)
                    ));

                await interaction.editReply({
                    embeds: [createErrorEmbed(
                        `You missed your exam day! Your exam day is **${DAYS[examDay]}** (Server Time).\n` +
                        `You verify your attendance but score a **0**.\n` +
                        `Your next exam opportunity is: <t:${nextExamTimestamp}:D> (<t:${nextExamTimestamp}:R>)`,
                        "Exam Failed"
                    )]
                });
                return;
            }

            // 5. Reward Calculation
            const lastXp = BigInt(metadata.lastXp || "0"); // Fallback just in case
            const currentXp = user.xp ?? 0n;
            const diff = currentXp - lastXp;

            // Calculate Reward
            const multMin = config.economy.exam.multMin;
            const multMax = config.economy.exam.multMax;
            const multiplier = Math.random() * (multMax - multMin) + multMin;

            // Allow negative reward? existing description implies "difference", usually gain. 
            // If diff is negative (lost XP?), reward might be 0.
            let reward = 0n;
            if (diff > 0n) {
                reward = BigInt(Math.floor(Number(diff) * multiplier));
            }

            // 6. Update State
            const nextExamDate = new Date(now);
            nextExamDate.setDate(now.getDate() + 7);
            nextExamDate.setHours(0, 0, 0, 0);
            const nextExamTimestamp = Math.floor(nextExamDate.getTime() / 1000);

            const newMetadata: ExamMetadata = {
                examDay: examDay,
                lastXp: currentXp.toString()
            };

            await DrizzleClient.transaction(async (tx) => {
                // Update Timer
                await tx.update(userTimers)
                    .set({
                        expiresAt: nextExamDate,
                        metadata: newMetadata
                    })
                    .where(and(
                        eq(userTimers.userId, user.id),
                        eq(userTimers.type, EXAM_TIMER_TYPE),
                        eq(userTimers.key, EXAM_TIMER_KEY)
                    ));

                // Add Currency
                if (reward > 0n) {
                    await tx.update(users)
                        .set({
                            balance: sql`${users.balance} + ${reward}`
                        })
                        .where(eq(users.id, user.id));
                }
            });

            await interaction.editReply({
                embeds: [createSuccessEmbed(
                    `**XP Gained:** ${diff.toString()}\n` +
                    `**Multiplier:** x${multiplier.toFixed(2)}\n` +
                    `**Reward:** ${reward.toString()} Currency\n\n` +
                    `See you next week: <t:${nextExamTimestamp}:D>`,
                    "Exam Passed!"
                )]
            });

        } catch (error: any) {
            if (error instanceof UserError) {
                await interaction.reply({ embeds: [createErrorEmbed(error.message)], ephemeral: true });
            } else {
                console.error("Error in exam command:", error);
                await interaction.reply({ embeds: [createErrorEmbed("An unexpected error occurred.")], ephemeral: true });
            }
        }
    }
});
