const { WebSocket, WebSocketServer } = require('ws');
const crypto = require('crypto');
const { getEnvConfig, readConfig } = require('./config');
const { listRows, batchCreateRows } = require('./baserow');
const { calculateScore, sanitizeQuestion, parseCorrectAnswers } = require('./quiz-logic');

const sessions = new Map();

function generateCode() {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    let code = '';
    for (let i = 0; i < 6; i++) code += chars[Math.floor(Math.random() * chars.length)];
    return code;
}

function sanitizeCode(input) {
    // Remove everything except letters and numbers, uppercase
    return (input || '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
}

function generatePresentToken() {
    return crypto.randomBytes(24).toString('hex');
}

function send(ws, data) {
    if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data));
}

function broadcastAll(session, data, exclude) {
    for (const [, p] of session.players) if (p.ws !== exclude) send(p.ws, data);
    for (const ws of session.admins) if (ws !== exclude) send(ws, data);
    for (const ws of session.presents) if (ws !== exclude) send(ws, data);
}

function broadcastAdmins(session, data) {
    for (const ws of session.admins) send(ws, data);
    for (const ws of session.presents) send(ws, data);
}

function playerList(session) {
    return Array.from(session.players.keys());
}

function leaderboard(session) {
    return Array.from(session.players.entries())
        .map(([name, p]) => ({ name, score: p.score }))
        .sort((a, b) => b.score - a.score)
        .map((e, i) => ({ ...e, rank: i + 1 }));
}

function sessionInfo(s) {
    return {
        code: s.code, setName: s.setName, status: s.status, options: s.options,
        questionCount: s.questions.length, currentQuestionIndex: s.currentQuestionIndex,
        playerCount: s.players.size, players: playerList(s), leaderboard: leaderboard(s),
        presentToken: s.presentToken,
    };
}

function shuffle(arr) {
    const a = [...arr];
    for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
    }
    return a;
}

function isAdmin(token) {
    return token === getEnvConfig().adminPassword;
}

function isValidPresentToken(token, code) {
    const s = sessions.get(code);
    return s && s.presentToken === token;
}

function setupWebSocket(server) {
    const wss = new WebSocketServer({ server });
    wss.on('connection', (ws) => {
        ws.isAlive = true;
        ws.on('pong', () => (ws.isAlive = true));
        ws.on('message', (raw) => {
            try { handleMessage(ws, JSON.parse(raw)); }
            catch (e) { console.error('[ws] Error:', e); send(ws, { type: 'error', message: 'Ungueltige Nachricht' }); }
        });
        ws.on('close', () => handleDisconnect(ws));
    });
    setInterval(() => {
        wss.clients.forEach((ws) => {
            if (!ws.isAlive) return ws.terminate();
            ws.isAlive = false;
            ws.ping();
        });
    }, 30000);
}

async function handleMessage(ws, msg) {
    switch (msg.type) {
        case 'admin-create': return adminCreate(ws, msg);
        case 'admin-join': return adminJoin(ws, msg);
        case 'admin-start': return adminStart(ws, msg);
        case 'admin-show-result': return adminShowResult(ws, msg);
        case 'admin-next': return adminNext(ws, msg);
        case 'admin-end': return adminEnd(ws, msg);
        case 'present-join': return presentJoin(ws, msg);
        case 'join': return playerJoin(ws, msg);
        case 'answer': return playerAnswer(ws, msg);
        default: send(ws, { type: 'error', message: 'Unbekannter Typ' });
    }
}

/* ---- Admin ---- */

async function adminCreate(ws, msg) {
    if (!isAdmin(msg.token)) return send(ws, { type: 'error', message: 'Nicht autorisiert' });
    const config = readConfig();
    const set = config.sets.find((s) => s.id === msg.setId);
    if (!set) return send(ws, { type: 'error', message: 'Fragenset nicht gefunden' });

    let questions;
    try { questions = await listRows(set.tableId); }
    catch (e) { return send(ws, { type: 'error', message: 'Fehler: ' + e.message }); }
    if (!questions.length) return send(ws, { type: 'error', message: 'Keine Fragen gefunden' });

    const opts = {
        shuffle: msg.options.shuffle !== false,
        scoring: msg.options.scoring || 'binary',
        timeLimit: parseInt(msg.options.timeLimit) || 30,
    };
    if (opts.shuffle) questions = shuffle(questions);

    // Custom or auto-generated code
    let code;
    if (msg.customCode) {
        code = sanitizeCode(msg.customCode);
        if (code.length < 3) return send(ws, { type: 'error', message: 'Code muss mindestens 3 Zeichen lang sein' });
        if (code.length > 20) return send(ws, { type: 'error', message: 'Code darf maximal 20 Zeichen lang sein' });
        if (sessions.has(code)) return send(ws, { type: 'error', message: `Code "${code}" ist bereits vergeben` });
    } else {
        code = generateCode();
        // Ensure uniqueness
        while (sessions.has(code)) code = generateCode();
    }

    const presentToken = generatePresentToken();

    const session = {
        code, sessionId: 'LIVE-' + Date.now().toString(36).toUpperCase(),
        setId: set.id, setName: set.name, tableId: set.tableId, options: opts,
        questions, currentQuestionIndex: -1, status: 'waiting',
        players: new Map(), admins: new Set([ws]), presents: new Set(),
        currentAnswers: new Map(), questionStartTime: null, timer: null,
        currentSanitized: null, results: [],
        presentToken,
    };

    sessions.set(code, session);
    ws._qa = { code, role: 'admin' };
    console.log(`[live] Session created: ${code} (${set.name}, ${questions.length} questions)`);
    send(ws, {
        type: 'session-created', code, setName: set.name,
        questionCount: questions.length, options: opts,
        presentToken,
    });
}

function adminJoin(ws, msg) {
    if (!isAdmin(msg.token)) return send(ws, { type: 'error', message: 'Nicht autorisiert' });
    const code = sanitizeCode(msg.code);
    const s = sessions.get(code);
    if (!s) return send(ws, { type: 'error', message: `Session "${code}" nicht gefunden` });
    s.admins.add(ws);
    ws._qa = { code, role: 'admin' };
    send(ws, { type: 'admin-joined', session: sessionInfo(s) });
}

function presentJoin(ws, msg) {
    const code = sanitizeCode(msg.code);
    if (!isAdmin(msg.token) && !isValidPresentToken(msg.token, code)) {
        return send(ws, { type: 'error', message: 'Nicht autorisiert' });
    }
    const s = sessions.get(code);
    if (!s) return send(ws, { type: 'error', message: 'Session nicht gefunden' });
    s.presents.add(ws);
    ws._qa = { code, role: 'present' };
    send(ws, { type: 'present-joined', session: sessionInfo(s) });
}

function adminStart(ws, msg) {
    if (!isAdmin(msg.token)) return;
    const s = sessions.get(sanitizeCode(msg.code));
    if (!s || s.status !== 'waiting') return;
    s.status = 'active';
    broadcastAll(s, { type: 'game-start', totalQuestions: s.questions.length });
    sendNextQuestion(s);
}

function adminShowResult(ws, msg) {
    if (!isAdmin(msg.token)) return;
    const s = sessions.get(sanitizeCode(msg.code));
    if (!s || s.status !== 'active') return;
    showResult(s);
}

function adminNext(ws, msg) {
    if (!isAdmin(msg.token)) return;
    const s = sessions.get(sanitizeCode(msg.code));
    if (!s || s.status !== 'showing-result') return;
    sendNextQuestion(s);
}

async function adminEnd(ws, msg) {
    if (!isAdmin(msg.token)) return;
    const s = sessions.get(sanitizeCode(msg.code));
    if (!s) return;
    await endSession(s);
}

/* ---- Player ---- */

function playerJoin(ws, msg) {
    const code = sanitizeCode(msg.code);
    const s = sessions.get(code);
    if (!s) return send(ws, { type: 'error', message: 'Session nicht gefunden. Pruefe den Code.' });

    const name = (msg.name || '').trim();
    if (!name) return send(ws, { type: 'error', message: 'Name darf nicht leer sein' });

    if (s.players.has(name)) {
        const existing = s.players.get(name);
        if (existing.ws && existing.ws.readyState === WebSocket.OPEN) {
            return send(ws, { type: 'error', message: 'Name bereits online. Bitte waehle einen anderen.' });
        }

        existing.ws = ws;
        ws._qa = { code, role: 'player', name };

        send(ws, {
            type: 'joined',
            session: { code: s.code, setName: s.setName, playerCount: s.players.size, players: playerList(s) },
        });

        if (s.status === 'active' && s.currentSanitized) {
            const tl = s.options.scoring === 'time' ? s.options.timeLimit : 0;
            send(ws, { type: 'game-start', totalQuestions: s.questions.length });
            send(ws, {
                type: 'question', index: s.currentQuestionIndex, total: s.questions.length,
                question: s.currentSanitized, timeLimit: tl,
            });
            if (s.currentAnswers.has(name)) {
                send(ws, { type: 'answer-received' });
            }
        } else if (s.status === 'showing-result') {
            send(ws, { type: 'game-start', totalQuestions: s.questions.length });
        } else if (s.status === 'ended') {
            send(ws, { type: 'game-end', leaderboard: leaderboard(s), sessionId: s.sessionId });
        }

        broadcastAll(s, { type: 'player-joined', name, playerCount: s.players.size, players: playerList(s) }, ws);
        return;
    }

    if (s.status !== 'waiting') {
        return send(ws, { type: 'error', message: 'Session laeuft bereits. Nur bestehende Teilnehmer koennen wieder beitreten.' });
    }

    s.players.set(name, { ws, score: 0, answers: [] });
    ws._qa = { code, role: 'player', name };

    send(ws, {
        type: 'joined',
        session: { code: s.code, setName: s.setName, playerCount: s.players.size, players: playerList(s) },
    });
    broadcastAll(s, { type: 'player-joined', name, playerCount: s.players.size, players: playerList(s) }, ws);
}

function playerAnswer(ws, msg) {
    const qa = ws._qa;
    if (!qa || qa.role !== 'player') return;
    const s = sessions.get(qa.code);
    if (!s || s.status !== 'active') return;
    if (s.currentAnswers.has(qa.name)) return;

    const elapsed = (Date.now() - s.questionStartTime) / 1000;
    const tl = s.options.scoring === 'time' ? s.options.timeLimit : 0;
    const remaining = tl > 0 ? Math.max(0, tl - elapsed) : 0;
    if (tl > 0 && remaining <= 0) return;

    s.currentAnswers.set(qa.name, { answers: msg.answers || [], timeRemaining: remaining });
    broadcastAdmins(s, { type: 'answer-count', count: s.currentAnswers.size, total: s.players.size });
    send(ws, { type: 'answer-received' });

    if (s.currentAnswers.size >= s.players.size) showResult(s);
}

/* ---- Game flow ---- */

function sendNextQuestion(s) {
    s.currentQuestionIndex++;
    if (s.currentQuestionIndex >= s.questions.length) return endSession(s);

    s.status = 'active';
    s.currentAnswers.clear();
    s.questionStartTime = Date.now();

    const q = s.questions[s.currentQuestionIndex];
    const tl = s.options.scoring === 'time' ? s.options.timeLimit : 0;
    const sanitized = sanitizeQuestion(q);
    s.currentSanitized = sanitized;

    broadcastAll(s, {
        type: 'question', index: s.currentQuestionIndex, total: s.questions.length,
        question: sanitized, timeLimit: tl,
    });

    clearTimeout(s.timer);
    if (tl > 0) {
        s.timer = setTimeout(() => {
            if (s.status === 'active') broadcastAll(s, { type: 'time-up' });
        }, tl * 1000);
    }
}

function showResult(s) {
    clearTimeout(s.timer);
    s.status = 'showing-result';

    const q = s.questions[s.currentQuestionIndex];
    const correct = parseCorrectAnswers(q);
    const tl = s.options.scoring === 'time' ? s.options.timeLimit : 0;

    const stats = {};
    ['A', 'B', 'C', 'D'].forEach((k) => { if (q[`Antwort ${k}`]) stats[k] = 0; });

    for (const [name, ans] of s.currentAnswers) {
        const player = s.players.get(name);
        const result = calculateScore(q, ans.answers, ans.timeRemaining, tl, s.options.scoring);
        player.score += result.points;
        player.answers.push({ qId: q.id, idx: s.currentQuestionIndex, answers: ans.answers, correct: result.correct, points: result.points });
        ans.answers.forEach((a) => { if (stats[a] !== undefined) stats[a]++; });
        s.results.push({
            Nutzername: name, Frage_ID: q.id, Fragenset: s.setName,
            Antwort: ans.answers.join(','), Richtig: result.correct,
            Punkte: result.points, Session_ID: s.sessionId,
            Modus: 'Live', Zeitstempel: new Date().toISOString(),
        });
    }

    for (const [name, player] of s.players) {
        if (!s.currentAnswers.has(name)) {
            player.answers.push({ qId: q.id, idx: s.currentQuestionIndex, answers: [], correct: false, points: 0 });
            s.results.push({
                Nutzername: name, Frage_ID: q.id, Fragenset: s.setName,
                Antwort: '', Richtig: false, Punkte: 0, Session_ID: s.sessionId,
                Modus: 'Live', Zeitstempel: new Date().toISOString(),
            });
        }
    }

    const lb = leaderboard(s);

    for (const [name, player] of s.players) {
        const last = player.answers[player.answers.length - 1];
        send(player.ws, {
            type: 'question-result', correctAnswer: correct, stats, leaderboard: lb,
            yourResult: { correct: last.correct, points: last.points, totalScore: player.score },
        });
    }

    broadcastAdmins(s, { type: 'question-result', correctAnswer: correct, stats, leaderboard: lb });
}

async function endSession(s) {
    clearTimeout(s.timer);
    s.status = 'ended';
    const lb = leaderboard(s);
    broadcastAll(s, { type: 'game-end', leaderboard: lb, sessionId: s.sessionId });

    try {
        const config = readConfig();
        if (config.answersTableId && s.results.length) {
            await batchCreateRows(config.answersTableId, s.results);
            console.log(`[live] Session ${s.code}: ${s.results.length} Ergebnisse gespeichert`);
        }
    } catch (e) {
        console.error('[live] Speicherfehler:', e.message);
    }

    setTimeout(() => sessions.delete(s.code), 300000);
}

function handleDisconnect(ws) {
    const qa = ws._qa;
    if (!qa) return;
    const s = sessions.get(qa.code);
    if (!s) return;
    if (qa.role === 'admin') s.admins.delete(ws);
    else if (qa.role === 'present') s.presents.delete(ws);
}

function getSessions() {
    return Array.from(sessions.values()).map(sessionInfo);
}

module.exports = { setupWebSocket, getSessions };