const db = require('../db');
const xpService = require('../utils/xpService');

// In-memory state for matchmaking and active duels
const duelQueue = []; 
const activeMatches = new Map(); // roomId -> MatchState

function generateRoomId() {
    return 'duel_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
}

const FALLBACK_QUESTIONS = [
    { id: 1, question: 'What does SQL injection exploit?', options: ['Database queries', 'Network protocols', 'File systems', 'CPU cycles'], correct: 0, category: 'Web Security' },
    { id: 2, question: 'What is a zero-day vulnerability?', options: ['An old bug', 'An unknown/unpatched flaw', 'A test environment', 'A DoS attack'], correct: 1, category: 'Threats' },
    { id: 3, question: 'What does XSS stand for?', options: ['Extreme Server Side', 'Cross-Site Scripting', 'XML Style Sheets', 'External System Script'], correct: 1, category: 'Web Security' },
    { id: 4, question: 'Which protocol encrypts web traffic?', options: ['HTTP', 'FTP', 'HTTPS/TLS', 'SMTP'], correct: 2, category: 'Protocols' },
    { id: 5, question: 'What is a firewall used for?', options: ['Speed up networks', 'Filter network traffic', 'Store passwords', 'Encrypt files'], correct: 1, category: 'Defence' },
];

async function fetchDuelQuestions() {
    try {
        const dbQuery = db.query(`
            SELECT lq.id, lq.question_text as question, lq.options, lq.correct_answer, lq.hint,
                   t.title as task_title, m.title as module_title
            FROM lesson_questions lq
            JOIN tasks t ON lq.task_id = t.id
            JOIN modules m ON t.module_id = m.id
            WHERE lq.options IS NOT NULL AND lq.options != ''
            ORDER BY RAND()
            LIMIT 5
        `);
        const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error('DB_TIMEOUT')), 5000));
        const result = await Promise.race([dbQuery, timeout]);
        
        const mapped = result.rows.map((q) => {
            let options = [];
            try {
                options = typeof q.options === 'string' ? JSON.parse(q.options) : q.options;
                if (!Array.isArray(options)) options = [];
            } catch (e) {
                options = [];
            }
            
            let correctIndex = 0;
            const correctAnswer = q.correct_answer?.toLowerCase().trim();
            
            // Randomize multiple-choice option order (Fisher-Yates shuffle)
            for (let i = options.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [options[i], options[j]] = [options[j], options[i]];
            }

            options.forEach((opt, i) => {
                if (opt.toLowerCase().trim() === correctAnswer) {
                    correctIndex = i;
                }
            });

            return {
                id: q.id,
                question: q.question,
                options: options,
                correct: correctIndex,
                category: q.module_title || 'Cyber Duel',
            };
        });
        return mapped.length > 0 ? mapped : FALLBACK_QUESTIONS;
    } catch (e) {
        console.error('[DUEL] Failed to fetch DB questions, using fallback:', e.message);
        return FALLBACK_QUESTIONS;
    }
}

module.exports = function(io, socket) {
    
    // Attempt to join the matchmaking queue
    socket.on('join_duel_queue', async (clientData) => {
        // Small delay to let 'authenticate' event propagate in case it was sent together
        const getUserConn = () => global.connectedUsers?.get(socket.id);
        let userConnection = getUserConn();
        if (!userConnection || userConnection.type !== 'registered') {
            await new Promise(r => setTimeout(r, 200));
            userConnection = getUserConn();
        }

        if (!userConnection || userConnection.type !== 'registered') {
            socket.emit('duel_error', { message: 'You must be logged in to play Cyber Duel.' });
            return;
        }

        const userId = userConnection.userId;

        // Check if user is already in queue
        if (duelQueue.find(p => p.userId === userId)) {
            // Already in queue, ignore
            return;
        }

        // Fetch user stats & real username for display in match
        try {
            const userStats = await xpService.getUserStats(userId);
            const userResult = await db.query('SELECT username, name FROM users WHERE id = ?', [userId]);
            const realUsername = userResult.rows[0]?.username || userResult.rows[0]?.name || 'Agent';

            const playerProfile = {
                socketId: socket.id,
                userId: userId,
                username: realUsername,
                level: userStats.level || 1,
            };

            // Check if queue has a waiting player
            if (duelQueue.length > 0) {
                const opponent = duelQueue.shift(); // Dequeue the first waiting player

                // Ensure we don't match the user against themselves from a duplicate socket
                if (opponent.userId === userId) {
                    duelQueue.push(playerProfile); 
                    return;
                }

                if (opponent.botTimeout) clearTimeout(opponent.botTimeout);

                await startMatch(playerProfile, opponent);
            } else {
                // No one in queue, add self
                duelQueue.push(playerProfile);
                socket.emit('joined_queue');

                // ---- AI BOT FALLBACK TIMER ----
                // Wait 8 seconds. If still in queue, play against an AI bot.
                playerProfile.botTimeout = setTimeout(async () => {
                    const idx = duelQueue.findIndex(p => p.userId === userId);
                    if (idx !== -1) {
                        duelQueue.splice(idx, 1); // remove from queue
                        
                        const botNames = ['Alex', 'Michael', 'Sarah', 'David', 'Emma', 'Jacob', 'Emily', 'Daniel', 'Olivia', 'Matthew', 'Sophia', 'James', 'Isabella', 'William', 'Mia', 'Benjamin', 'Charlotte'];
                        const botProfile = {
                            socketId: 'bot_' + Date.now(),
                            userId: 'bot_' + Date.now(),
                            username: botNames[Math.floor(Math.random() * botNames.length)],
                            level: Math.max(1, playerProfile.level + Math.floor(Math.random() * 3) - 1),
                            isBot: true // Flag identifies the backend simulated player
                        };
                        
                        await startMatch(playerProfile, botProfile);
                    }
                }, 8000); // 8 seconds wait time
            }
        } catch (err) {
            console.error('[DUEL] Error in join_duel_queue:', err.message);
            socket.emit('duel_error', { message: 'Matchmaking error.' });
        }
    });

    socket.on('leave_duel_queue', () => {
        const index = duelQueue.findIndex(p => p.socketId === socket.id);
        if (index !== -1) {
            if (duelQueue[index].botTimeout) clearTimeout(duelQueue[index].botTimeout);
            duelQueue.splice(index, 1);
        }
    });

    socket.on('duel_submit_answer', (data) => {
        // data: { roomId, questionIndex, isCorrect, timeTaken }
        const { roomId, questionIndex, isCorrect, timeTaken } = data;
        const match = activeMatches.get(roomId);
        
        if (!match) return;

        // Verify player is in match
        const userConnection = global.connectedUsers?.get(socket.id);
        if (!userConnection) return;
        
        const userId = userConnection.userId;
        const playerState = match.players[userId];
        
        if (!playerState) return;

        // Update answers count
        playerState.answers++;

        // Calculate score (Base 10 for correct, bonus for speed)
        if (isCorrect) {
            let points = 10;
            if (timeTaken < 5000) points += 5; // Fast answer bonus
            playerState.score += points;
        }

        // Broadcast score update to room
        io.to(roomId).emit('duel_update', {
            roomId,
            players: match.players
        });

        // Check if match is over (both answered all 5)
        if (playerState.answers >= match.questions.length) {
            playerState.finished = true;
            checkMatchEnd(roomId, match);
        }
    });

    // Cleanup on disconnect
    socket.on('disconnect', () => {
        // Remove from queue
        const qIndex = duelQueue.findIndex(p => p.socketId === socket.id);
        if (qIndex !== -1) {
            if (duelQueue[qIndex].botTimeout) clearTimeout(duelQueue[qIndex].botTimeout);
            duelQueue.splice(qIndex, 1);
        }

        // Handle Active Matches
        // If a player disconnects mid-match, we should end the match and award the other player
        const userConnection = global.connectedUsers?.get(socket.id);
        if (userConnection) {
            const userId = userConnection.userId;
            for (const [roomId, match] of activeMatches.entries()) {
                if (match.players[userId]) {
                    // This player disconnected, mark as finished and end match
                    match.players[userId].finished = true;
                    endMatch(roomId, true); // true = abnormal disconnect
                }
            }
        }
    });

    // Helper to start the match
    async function startMatch(p1, p2) {
        const roomId = generateRoomId();
        
        const p1Socket = io.sockets.sockets.get(p1.socketId);
        const p2Socket = io.sockets.sockets.get(p2.socketId);
        
        if (p1Socket) p1Socket.join(roomId);
        if (p2Socket) p2Socket.join(roomId);

        // Fetch questions
        const questions = await fetchDuelQuestions();
        if (questions.length === 0) {
            if (p1Socket) p1Socket.emit('duel_error', { message: 'Failed to generate arena. Try again.' });
            if (p2Socket) p2Socket.emit('duel_error', { message: 'Failed to generate arena. Try again.' });
            return;
        }

        // Initialize Match State
        activeMatches.set(roomId, {
            roomId,
            questions,
            players: {
                [p1.userId]: { ...p1, score: 0, answers: 0, finished: false },
                [p2.userId]: { ...p2, score: 0, answers: 0, finished: false }
            },
            startTime: Date.now()
        });

        // Notify both players
        if (p1Socket) {
            p1Socket.emit('duel_match_found', { 
                roomId, 
                opponent: { username: p2.username, level: p2.level },
                questions 
            });
        }
        
        if (p2Socket) {
            p2Socket.emit('duel_match_found', { 
                roomId, 
                opponent: { username: p1.username, level: p1.level },
                questions 
            });
        }

        // Start Bot Simulation Loop (if one of the players is a bot)
        if (p1.isBot) simulateBotAnswers(roomId, p1.userId);
        if (p2.isBot) simulateBotAnswers(roomId, p2.userId);
    }

    // Helper to check if both players finished
    function checkMatchEnd(roomId, match) {
        let allFinished = true;
        for (const pid in match.players) {
            if (!match.players[pid].finished) allFinished = false;
        }
        if (allFinished) {
            endMatch(roomId);
        }
    }

    // Backend AI Loop that answers questions over time
    function simulateBotAnswers(roomId, botId) {
        let currentQ = 0;
        
        const answerNext = () => {
            const match = activeMatches.get(roomId);
            if (!match || currentQ >= match.questions.length) return;
            
            // Random delay between 2 and 7 seconds
            const delay = 2000 + Math.random() * 5000;
            
            setTimeout(() => {
                const state = activeMatches.get(roomId);
                if (!state || !state.players[botId]) return; // Match might have ended/cleaned up
                
                const botState = state.players[botId];
                botState.answers++;
                
                // Bot has ~70% chance to be correct
                const isCorrect = Math.random() > 0.3; 
                if (isCorrect) {
                    let points = 10;
                    if (delay < 5000) points += 5;
                    botState.score += points;
                }
                
                // Broadcast bot score update silently (real clients only update visually)
                io.to(roomId).emit('duel_update', { roomId, players: state.players });
                
                currentQ++;
                if (botState.answers >= state.questions.length) {
                    botState.finished = true;
                    checkMatchEnd(roomId, state);
                } else {
                    answerNext();
                }
            }, delay);
        };
        
        answerNext();
    }

    // Helper to finalize match
    async function endMatch(roomId, abnormalDisconnect = false) {
        const match = activeMatches.get(roomId);
        if (!match) return;

        const playerIds = Object.keys(match.players);
        if (playerIds.length !== 2) return;

        const p1IdStr = playerIds[0];
        const p2IdStr = playerIds[1];
        const p1State = match.players[p1IdStr];
        const p2State = match.players[p2IdStr];

        let winnerId = null;
        if (p1State.score > p2State.score) winnerId = p1IdStr;
        else if (p2State.score > p1State.score) winnerId = p2IdStr;
        // else tie -> winnerId is null

        // Award XP
        try {
            // Winner gets base XP + their score. Loser gets half their score.
            let p1Xp = p1IdStr === winnerId ? 50 + p1State.score : Math.floor(p1State.score / 2);
            let p2Xp = p2IdStr === winnerId ? 50 + p2State.score : Math.floor(p2State.score / 2);
            
            // If tie, both get 25 + score
            if (winnerId === null) {
                p1Xp = 25 + p1State.score;
                p2Xp = 25 + p2State.score;
            }

            // Only award to real database users (ignore bots)
            if (p1Xp > 0 && !p1State.isBot) await xpService.awardXP(Number(p1IdStr), p1Xp, 'Cyber Duel');
            if (p2Xp > 0 && !p2State.isBot) await xpService.awardXP(Number(p2IdStr), p2Xp, 'Cyber Duel');

            io.to(roomId).emit('duel_end', {
                roomId,
                winnerId,
                results: {
                    [p1IdStr]: { score: p1State.score, xpEarned: p1Xp },
                    [p2IdStr]: { score: p2State.score, xpEarned: p2Xp }
                }
            });

        } catch (err) {
            console.error('Error ending duel:', err);
        }

        // Cleanup memory
        activeMatches.delete(roomId);
    }
};
