import express from 'express';
import http from 'http';
import { Server } from 'socket.io';
import cors from 'cors';
import dotenv from 'dotenv';
import crypto from 'crypto';
import { Game } from './models/Game';
import { GamePhase } from '../../shared/types';
import { initDb, logGameStart, logGameEnd, updateGamePlayers, getGameHistory } from './db';

dotenv.config();

// Inicializar DB
initDb();

const app = express();
const port = process.env.PORT || 4000;

const allowedOrigins = [
    process.env.CORS_ORIGIN || "http://localhost:3000",
    "http://localhost:3000",
    "http://127.0.0.1:3000",
    "http://192.168.1.131:3000"
];

app.use(cors({
    origin: (origin, callback) => {
        if (!origin || allowedOrigins.includes(origin)) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    methods: ["GET", "POST"]
}));

const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        origin: allowedOrigins,
        methods: ["GET", "POST"]
    }
});

// ALMACÉN DE PARTIDAS (En memoria por ahora)
// En el futuro esto podría estar en Redis o Postgres
const games: Record<string, Game> = {};

// Almacén de timers para auto-resolución de votaciones
const voteTimers: Record<string, NodeJS.Timeout> = {};

// --- LOBBY MANAGEMENT ---

const MISSION_NAMES = [
    "Operación Overlord", "Operación Market Garden", "Operación Barbarroja",
    "Operación Valkiria", "Operación Torch", "Operación Husky",
    "Batalla de Stalingrado", "Dunkerque", "El Alamein", "Midway",
    "Operación Ciudadela", "Operación Dragoon", "Operación Fortaleza",
    "Operación Eiche", "Operación León Marino", "Operación Urano"
];

// Helper para iniciar timer de votación de líder (10 segundos)
function startLeaderVoteTimer(roomId: string) {
    if (voteTimers[roomId]) clearTimeout(voteTimers[roomId]);
    voteTimers[roomId] = setTimeout(() => {
        const g = games[roomId];
        if (g && g.state.phase === 'vote_leader') {
            // Forzar resolución de votos cuando se acaba el tiempo
            g.forceResolveLeaderVote();
            io.to(roomId).emit('game_state', g.state);

            // Si sigue en vote_leader (líder rechazado, nuevo líder), reiniciar timer
            if (g.state.phase === 'vote_leader') {
                startLeaderVoteTimer(roomId);
            }
        }
    }, 10000); // 10 segundos
}

const generateRoomName = () => {
    const idx = Math.floor(Math.random() * MISSION_NAMES.length);
    const suffix = Math.floor(100 + Math.random() * 900); // 3 digit code
    return `${MISSION_NAMES[idx]} #${suffix}`;
};

const getAdminData = async () => {
    const activeGamesData = Object.values(games).map(g => ({
        id: g.state.roomId,
        name: g.roomName,
        status: g.state.phase,
        currentPlayers: g.state.players.length,
        maxPlayers: g.maxPlayers,
        players: g.state.players.map(p => ({ id: p.id, name: p.name }))
    }));

    const history = await getGameHistory();

    return {
        activeGames: activeGamesData,
        history: history
    };
};

const broadcastAdminUpdate = async () => {
    const data = await getAdminData();
    io.to('admin-room').emit('admin_data', data);
};

io.on('connection', (socket) => {
    console.log('Cliente conectado:', socket.id);

    // Enviar lista de salas al conectar
    socket.emit('rooms_list', getRoomsList());

    // A. CREAR SALA
    socket.on('create_game', ({ hostName, maxPlayers, password }) => {
        console.log(`[CREATE_GAME] Host: ${hostName}, Players: ${maxPlayers}`);
        const roomId = crypto.randomUUID();
        const roomName = generateRoomName();

        const newGame = new Game(roomId, roomName, socket.id, maxPlayers, password);
        games[roomId] = newGame;

        // Unir al creador automáticamente
        newGame.addPlayer(socket.id, hostName);
        socket.join(roomId);

        // Notificar al creador
        socket.emit('game_joined', { roomId, state: newGame.state });

        // Actualizar lista a todos
        io.emit('rooms_list', getRoomsList());

        // LOG EN DB
        logGameStart(roomId, roomName, hostName, maxPlayers);
        broadcastAdminUpdate();
    });

    // B. UNIRSE A SALA
    socket.on('join_game', ({ roomId, playerName, password }) => {
        const game = games[roomId];

        if (!game) {
            socket.emit('error', 'La partida no existe');
            return;
        }

        if (game.password && game.password !== password) {
            socket.emit('error', 'Contraseña incorrecta');
            return;
        }

        if (game.state.players.length >= game.maxPlayers) {
            socket.emit('error', 'La partida está llena');
            return;
        }

        // Evitar duplicados
        const existingPlayer = game.state.players.find(p => p.id === socket.id);
        if (!existingPlayer) {
            game.addPlayer(socket.id, playerName);
        }

        socket.join(roomId);

        // Enviar estado actualizado a la sala
        io.to(roomId).emit('game_state', game.state);
        // Avisar al usuario que entró OK
        socket.emit('game_joined', { roomId, state: game.state });

        // Actualizar lista de salas (cambió contador de jugadores)
        io.emit('rooms_list', getRoomsList());

        // ACTUALIZAR LOG EN DB
        updateGamePlayers(roomId, game.state.players.map(p => p.name));
        broadcastAdminUpdate();
    });

    // C. REFRESCAR LISTA
    socket.on('get_rooms', () => {
        socket.emit('rooms_list', getRoomsList());
    });

    // --- GAMEPLAY DE SIEMPRE (Adaptado a roomId dinámico) ---

    // 2. INICIAR PARTIDA - FASE INTRO
    socket.on('start_game', ({ roomId }) => {
        const game = games[roomId];
        // Solo el host puede iniciar
        if (game && game.hostId === socket.id && game.state.phase === 'lobby') {
            game.state.phase = 'intro' as any;
            io.to(roomId).emit('game_state', game.state);
            io.emit('rooms_list', getRoomsList());
        }
    });

    // 2.1 PASAR INTRO -> REVEAL ROLE
    socket.on('finish_intro', ({ roomId }) => {
        const game = games[roomId];
        if (game && game.hostId === socket.id && game.state.phase === 'intro') {
            // AQUI DISTRIBUIMOS ROLES
            if (game.startGame()) {
                // startGame pone phase en TEAM_BUILDING, pero nosotros queremos REVEAL_ROLE primero
                game.state.phase = 'reveal_role' as any;
                io.to(roomId).emit('game_state', game.state);
            }
        }
    });

    // 2.2 REVEAL ROLE -> ROLL CALL
    socket.on('finish_reveal', ({ roomId }) => {
        const game = games[roomId];
        // Cualquiera puede llamar a esto si es auto-timer, o solo host?
        // El usuario dijo "Habrá una cuenta atrás de 5 segundos".
        // Lo ideal es que el Host controle el tiempo para sincronizar.
        if (game && game.hostId === socket.id && game.state.phase === 'reveal_role') {
            game.state.phase = 'roll_call' as any;
            io.to(roomId).emit('game_state', game.state);
        }
    });

    // 2.3 FINALIZAR ROLL CALL -> PRIMER TURNO DE JUEGO
    socket.on('finish_roll_call', ({ roomId }) => {
        const game = games[roomId];
        if (game && game.hostId === socket.id && game.state.phase === 'roll_call') {
            // ERROR CORREGIDO: No llamar a startGame() de nuevo porque re-baraja los roles.
            // Simplemente avanzamos a la fase de votación de líder que ya estaba configurada.
            game.state.phase = 'vote_leader' as any;

            // Iniciar timer de 11 segundos para forzar cambio de líder
            startLeaderVoteTimer(roomId);

            io.to(roomId).emit('game_state', game.state);
        }
    });


    // 2.4 VOTAR LÍDER
    socket.on('vote_leader', ({ roomId, approve }) => {
        const game = games[roomId];
        if (game) {
            const previousPhase = game.state.phase;
            game.voteLeader(socket.id, approve);
            io.to(roomId).emit('game_state', game.state);

            // Si cambió de fase (líder aprobado o rechazado)
            if (game.state.phase !== previousPhase) {
                // Limpiar timer actual
                if (voteTimers[roomId]) {
                    clearTimeout(voteTimers[roomId]);
                    delete voteTimers[roomId];
                }

                // Si pasó a vote_leader de nuevo (líder rechazado), iniciar nuevo timer
                if (game.state.phase === 'vote_leader') {
                    startLeaderVoteTimer(roomId);
                }
            }
        }
    });

    // 3. PROPONER EQUIPO
    socket.on('propose_team', ({ roomId, teamIds }) => {
        const game = games[roomId];
        if (game && game.proposeTeam(teamIds)) {
            io.to(roomId).emit('game_state', game.state);
        }
    });

    // 4. VOTAR EQUIPO
    socket.on('vote_team', ({ roomId, approve }) => {
        const game = games[roomId];
        if (game) {
            game.voteForTeam(socket.id, approve);
            io.to(roomId).emit('game_state', game.state);
        }
    });


    // 5. VOTAR MISIÓN
    socket.on('vote_mission', ({ roomId, success }) => {
        const game = games[roomId];
        if (game) {
            game.voteMission(success);
            io.to(roomId).emit('game_state', game.state);
        }
    });

    // 5.1 FINALIZAR REVELACIÓN DE CARTAS
    socket.on('finish_reveal', ({ roomId }) => {
        const game = games[roomId];
        // Permitir a cualquiera avanzar para evitar bloqueos
        if (game && game.state.phase === 'mission_reveal') {
            game.finishReveal();
            io.to(roomId).emit('game_state', game.state);
        }
    });


    // 5.2 FINALIZAR PANTALLA DE RESULTADO
    socket.on('finish_mission_result', ({ roomId }) => {
        const game = games[roomId];
        if (game && game.hostId === socket.id && game.state.phase === GamePhase.MISSION_RESULT) {
            game.finishMissionResult();
            io.to(roomId).emit('game_state', game.state);

            // Si volvió a vote_leader (nueva ronda), iniciar timer
            // TypeScript no detecta que finishMissionResult() cambia la fase, usamos type assertion
            if ((game.state.phase as GamePhase) === GamePhase.VOTE_LEADER) {
                startLeaderVoteTimer(roomId);
            }
        }
    });


    // 6. ASESINATO FINAL
    socket.on('assassin_kill', ({ roomId, targetId }) => {
        const game = games[roomId];
        if (game) {
            game.assassinKill(targetId);
            io.to(roomId).emit('game_state', game.state);
        }
    });

    // 7. REINICIAR PARTIDA
    socket.on('restart_game', ({ roomId }) => {
        const game = games[roomId];
        if (game && game.hostId === socket.id) {
            game.restartGame();
            io.to(roomId).emit('game_state', game.state);
        }
    });

    // 8. FINALIZAR Y EXPULSAR JUGADORES
    socket.on('finalize_game', ({ roomId }) => {
        const game = games[roomId];
        if (game && game.hostId === socket.id) {
            // Notificar a todos los jugadores que la partida ha sido finalizada
            io.to(roomId).emit('game_finalized');

            // Eliminar la partida inmediatamente del registro
            delete games[roomId];

            // Actualizar lista de salas para todos los clientes
            io.emit('rooms_list', getRoomsList());

            // Desconectar a todos los jugadores de la sala
            io.in(roomId).socketsLeave(roomId);

            // LOG EN DB
            logGameEnd(roomId, game.state.winner, false);
            broadcastAdminUpdate();
        }
    });

    // 8. ABANDONAR PARTIDA (cualquier jugador)
    socket.on('leave_game', ({ roomId }) => {
        const game = games[roomId];
        if (game) {
            // Encontrar el jugador que se va
            const leavingPlayer = game.state.players.find(p => p.id === socket.id);
            const playerName = leavingPlayer?.name || 'Un jugador';

            // Notificar a todos los jugadores
            io.to(roomId).emit('player_left_game', { playerName });
            io.to(roomId).emit('game_finalized');

            // Eliminar la partida de la base de datos
            delete games[roomId];

            // Limpiar timer si existe
            if (voteTimers[roomId]) {
                clearTimeout(voteTimers[roomId]);
                delete voteTimers[roomId];
            }

            // Actualizar lista de salas
            io.emit('rooms_list', getRoomsList());

            // Desconectar a todos de la sala
            io.in(roomId).socketsLeave(roomId);

            // LOG EN DB COMO ABORTADA
            logGameEnd(roomId, null, true);
            broadcastAdminUpdate();

            console.log(`[LEAVE_GAME] ${playerName} abandonó la partida ${roomId}. Partida eliminada.`);
        }
    });

    // 9. RECONECTAR SESIÓN
    socket.on('reconnect_session', ({ playerName, roomId }) => {
        console.log(`[RECONNECT_SESSION] Intento de reconexión: ${playerName} a sala ${roomId}`);

        if (roomId) {
            const game = games[roomId];
            if (game) {
                // Buscar si el jugador existe en la partida
                const existingPlayer = game.state.players.find(p => p.name === playerName);

                if (existingPlayer) {
                    // Actualizar el socket ID del jugador
                    existingPlayer.id = socket.id;

                    // Unir al socket a la sala
                    socket.join(roomId);

                    // Enviar estado actualizado
                    socket.emit('game_joined', { roomId, state: game.state });
                    io.to(roomId).emit('game_state', game.state);

                    console.log(`[RECONNECT_SESSION] ${playerName} reconectado exitosamente a ${roomId}`);
                } else {
                    console.log(`[RECONNECT_SESSION] Jugador ${playerName} no encontrado en partida ${roomId}`);
                    socket.emit('error', 'No se pudo reconectar a la partida');
                }
            } else {
                console.log(`[RECONNECT_SESSION] Partida ${roomId} no existe`);
                socket.emit('error', 'La partida ya no existe');
            }
        }
    });

    // 10. FINALIZAR Y EXPULSAR JUGADORES (solo host)
    socket.on('finalize_game', ({ roomId }) => {
        const game = games[roomId];
        if (game && game.hostId === socket.id) {
            // Notificar a todos los jugadores que la partida ha sido finalizada
            io.to(roomId).emit('game_finalized');

            // Eliminar la partida inmediatamente del registro
            delete games[roomId];

            // Actualizar lista de salas para todos los clientes
            io.emit('rooms_list', getRoomsList());

            // Desconectar a todos los jugadores de la sala
            io.in(roomId).socketsLeave(roomId);
        }
    });

    // 11. DESCONEXIÓN
    socket.on('disconnect', () => {
        // Buscar en qué partida estaba y sacarlo (opcional, por ahora solo notificamos)
        console.log('Desconectado:', socket.id);
    });

    // --- ADMIN COMMANDS ---

    socket.on('admin_get_data', async () => {
        console.log('[ADMIN] Agente administrativo conectado');
        socket.join('admin-room');
        const data = await getAdminData();
        socket.emit('admin_data', data);
    });

    socket.on('admin_close_game', async ({ roomId }) => {
        const game = games[roomId];
        if (game) {
            io.to(roomId).emit('game_finalized');
            delete games[roomId];

            if (voteTimers[roomId]) {
                clearTimeout(voteTimers[roomId]);
                delete voteTimers[roomId];
            }

            io.emit('rooms_list', getRoomsList());
            io.in(roomId).socketsLeave(roomId);

            // Log como abortada por admin
            await logGameEnd(roomId, null, true);
            socket.emit('admin_action_success');
            broadcastAdminUpdate();
        }
    });

    socket.on('admin_kick_player', ({ roomId, targetSocketId }) => {
        const game = games[roomId];
        if (game) {
            const playerIndex = game.state.players.findIndex(p => p.id === targetSocketId);
            if (playerIndex !== -1) {
                const playerName = game.state.players[playerIndex].name;
                game.state.players.splice(playerIndex, 1);

                // Notificar al jugador expulsado
                io.to(targetSocketId).emit('game_finalized');
                const targetSocket = io.sockets.sockets.get(targetSocketId);
                targetSocket?.leave(roomId);

                // Notificar al resto
                io.to(roomId).emit('player_left_game', { playerName: `${playerName} (Expulsado)` });
                io.to(roomId).emit('game_state', game.state);

                io.emit('rooms_list', getRoomsList());
                updateGamePlayers(roomId, game.state.players.map(p => p.name));

                socket.emit('admin_action_success');
                broadcastAdminUpdate();
            }
        }
    });
});

const getRoomsList = () => {
    return Object.values(games).map(g => ({
        id: g.state.roomId,
        name: g.roomName,
        hostId: g.hostId,
        currentPlayers: g.state.players.length,
        maxPlayers: g.maxPlayers,
        isPrivate: !!g.password,
        status: g.state.phase === 'lobby' ? 'waiting' : 'playing'
    }));
};

app.get('/', (req, res) => {
    res.send('Servidor de La Resistencia funcionando 🚀');
});

server.listen(port, () => {
    console.log(`[server]: Servidor corriendo en http://localhost:${port}`);
});
