import { users, userTimers, transactions } from "@db/schema";
import { eq, and, sql } from "drizzle-orm";
import { config } from "@shared/lib/config";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { UserError } from "@shared/lib/errors";
import { TimerType, TransactionType } from "@shared/lib/constants";
import { DrizzleClient } from "@shared/db/DrizzleClient";

// OpenTDB API Response Types
interface OpenTDBResponse {
    response_code: number;
    results: Array<{
        category: string;
        type: 'boolean' | 'multiple';
        difficulty: string;
        question: string;
        correct_answer: string;
        incorrect_answers: string[];
    }>;
}

export interface TriviaQuestion {
    question: string;
    correctAnswer: string;
    incorrectAnswers: string[];
    type: 'boolean' | 'multiple';
    difficulty: string;
    category: string;
}

export interface TriviaSession {
    sessionId: string;
    userId: string;
    question: TriviaQuestion;
    allAnswers: string[];
    correctIndex: number;
    expiresAt: Date;
    entryFee: bigint;
    potentialReward: bigint;
}

export interface TriviaResult {
    correct: boolean;
    reward: bigint;
    correctAnswer: string;
}

class TriviaService {
    private activeSessions: Map<string, TriviaSession> = new Map();

    constructor() {
        // Cleanup expired sessions every 30 seconds
        setInterval(() => {
            this.cleanupExpiredSessions();
        }, 30000);
    }

    private cleanupExpiredSessions() {
        const now = Date.now();
        const expired: string[] = [];

        for (const [sessionId, session] of this.activeSessions.entries()) {
            if (session.expiresAt.getTime() < now) {
                expired.push(sessionId);
            }
        }

        for (const sessionId of expired) {
            this.activeSessions.delete(sessionId);
        }

        if (expired.length > 0) {
            console.log(`[TriviaService] Cleaned up ${expired.length} expired sessions.`);
        }
    }

    /**
     * Fetch a trivia question from OpenTDB API
     */
    async fetchQuestion(category?: number, difficulty?: string): Promise<TriviaQuestion> {
        let url = 'https://opentdb.com/api.php?amount=1&encode=base64';

        if (category) {
            url += `&category=${category}`;
        }

        if (difficulty && difficulty !== 'random') {
            url += `&difficulty=${difficulty}`;
        }

        try {
            const response = await fetch(url);
            const data = await response.json() as OpenTDBResponse;

            if (data.response_code !== 0 || !data.results || data.results.length === 0) {
                throw new Error('Failed to fetch trivia question');
            }

            const result = data.results[0];

            if (!result) {
                throw new Error('No trivia question returned');
            }

            // Decode base64
            return {
                category: Buffer.from(result.category, 'base64').toString('utf-8'),
                type: result.type,
                difficulty: Buffer.from(result.difficulty, 'base64').toString('utf-8'),
                question: Buffer.from(result.question, 'base64').toString('utf-8'),
                correctAnswer: Buffer.from(result.correct_answer, 'base64').toString('utf-8'),
                incorrectAnswers: result.incorrect_answers.map(ans =>
                    Buffer.from(ans, 'base64').toString('utf-8')
                ),
            };
        } catch (error) {
            console.error('[TriviaService] Error fetching question:', error);
            throw new UserError('Failed to fetch trivia question. Please try again later.');
        }
    }

    /**
     * Check if user can play trivia (cooldown check)
     */
    async canPlayTrivia(userId: string): Promise<{ canPlay: boolean; nextAvailable?: Date }> {
        const now = new Date();

        const cooldown = await DrizzleClient.query.userTimers.findFirst({
            where: and(
                eq(userTimers.userId, BigInt(userId)),
                eq(userTimers.type, TimerType.TRIVIA_COOLDOWN),
                eq(userTimers.key, 'default')
            ),
        });

        if (cooldown && cooldown.expiresAt > now) {
            return { canPlay: false, nextAvailable: cooldown.expiresAt };
        }

        return { canPlay: true };
    }

    /**
     * Start a trivia session - deducts entry fee and creates session
     */
    async startTrivia(userId: string, username: string, categoryId?: number): Promise<TriviaSession> {
        // Check cooldown
        const cooldownCheck = await this.canPlayTrivia(userId);
        if (!cooldownCheck.canPlay) {
            const timestamp = Math.floor(cooldownCheck.nextAvailable!.getTime() / 1000);
            throw new UserError(`You're on cooldown! Try again <t:${timestamp}:R>.`);
        }

        const entryFee = config.trivia.entryFee;

        return await withTransaction(async (tx) => {
            // Check balance
            const user = await tx.query.users.findFirst({
                where: eq(users.id, BigInt(userId)),
            });

            if (!user) {
                throw new UserError('User not found.');
            }

            if ((user.balance ?? 0n) < entryFee) {
                throw new UserError(`Insufficient funds! You need ${entryFee} AU to play trivia.`);
            }

            // Deduct entry fee (SINK MECHANISM)
            await tx.update(users)
                .set({
                    balance: sql`${users.balance} - ${entryFee}`,
                })
                .where(eq(users.id, BigInt(userId)));

            // Record transaction
            await tx.insert(transactions).values({
                userId: BigInt(userId),
                amount: -entryFee,
                type: TransactionType.TRIVIA_ENTRY,
                description: 'Trivia entry fee',
            });

            // Fetch question
            let category = categoryId;
            if (!category) {
                category = config.trivia.categories.length > 0
                    ? config.trivia.categories[Math.floor(Math.random() * config.trivia.categories.length)]
                    : undefined;
            }

            const difficulty = config.trivia.difficulty;
            const question = await this.fetchQuestion(category, difficulty);

            // Shuffle answers
            const allAnswers = [...question.incorrectAnswers, question.correctAnswer];
            const shuffled = allAnswers.sort(() => Math.random() - 0.5);
            const correctIndex = shuffled.indexOf(question.correctAnswer);

            // Create session
            const sessionId = `${userId}_${Date.now()}`;
            const expiresAt = new Date(Date.now() + config.trivia.timeoutSeconds * 1000);
            const potentialReward = BigInt(Math.floor(Number(entryFee) * config.trivia.rewardMultiplier));

            const session: TriviaSession = {
                sessionId,
                userId,
                question,
                allAnswers: shuffled,
                correctIndex,
                expiresAt,
                entryFee,
                potentialReward,
            };

            this.activeSessions.set(sessionId, session);

            // Set cooldown
            const cooldownEnd = new Date(Date.now() + config.trivia.cooldownMs);
            await tx.insert(userTimers)
                .values({
                    userId: BigInt(userId),
                    type: TimerType.TRIVIA_COOLDOWN,
                    key: 'default',
                    expiresAt: cooldownEnd,
                })
                .onConflictDoUpdate({
                    target: [userTimers.userId, userTimers.type, userTimers.key],
                    set: { expiresAt: cooldownEnd },
                });

            // Record dashboard event
            const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
            await dashboardService.recordEvent({
                type: 'info',
                message: `${username} started a trivia game (${question.difficulty})`,
                icon: '🎯'
            });

            return session;
        });
    }

    /**
     * Get session by ID
     */
    getSession(sessionId: string): TriviaSession | undefined {
        return this.activeSessions.get(sessionId);
    }

    /**
     * Submit answer and process reward
     */
    async submitAnswer(sessionId: string, userId: string, isCorrect: boolean): Promise<TriviaResult> {
        const session = this.activeSessions.get(sessionId);

        if (!session) {
            throw new UserError('Session not found or expired.');
        }

        if (session.userId !== userId) {
            throw new UserError('This is not your trivia question!');
        }

        // Remove session to prevent double-submit
        this.activeSessions.delete(sessionId);

        const reward = isCorrect ? session.potentialReward : 0n;

        if (isCorrect) {
            await withTransaction(async (tx) => {
                // Award prize
                await tx.update(users)
                    .set({
                        balance: (await tx.query.users.findFirst({
                            where: eq(users.id, BigInt(userId))
                        }))!.balance! + reward,
                    })
                    .where(eq(users.id, BigInt(userId)));

                // Record transaction
                await tx.insert(transactions).values({
                    userId: BigInt(userId),
                    amount: reward,
                    type: TransactionType.TRIVIA_WIN,
                    description: 'Trivia prize',
                });

                // Record dashboard event
                const user = await tx.query.users.findFirst({
                    where: eq(users.id, BigInt(userId))
                });

                const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
                await dashboardService.recordEvent({
                    type: 'success',
                    message: `${user?.username} won ${reward.toLocaleString()} AU from trivia!`,
                    icon: '🎉'
                });
            });
        }

        return {
            correct: isCorrect,
            reward,
            correctAnswer: session.question.correctAnswer,
        };
    }
}

export const triviaService = new TriviaService();
