const axios = require('axios');
const WebSocket = require('ws');
const { verifyLinkCode, updateUserLinkWithUsername, updateLastConnection } = require('../core/database.js');
const { handlePalworldChat } = require('../bridge/palworld-bridge.js');

let ws = null;
let reconnectTimeout = null;
let client = null;
let heartbeatInterval = null;
let heartbeatTimeout = null; // Timeout pour détecter si le serveur ne répond plus
let lastHeartbeatResponse = null; // Timestamp de la dernière réponse du serveur
let checkInterval = null;
let refreshCredentialsInterval = null; // Interval pour rafraîchir les credentials périodiquement
let isMonitoring = false;
let isConnecting = false;
let connectionTimestamp = null;
let monitoringStartTimestamp = null; // Timestamp du démarrage du monitoring (ne change pas lors des reconnexions)

// Backoff de reconnexion
let reconnectDelayMs = 5000;
const RECONNECT_DELAY_MAX_MS = 5 * 60 * 1000; // 5 min
const CREDENTIALS_REFRESH_INTERVAL = 55 * 60 * 1000; // Rafraîchir toutes les 55 minutes (avant l'expiration)

// Suivi des joueurs connectés (fallback si la console n’affiche pas les déconnexions)
const connectedPlayers = new Map(); // steamId -> { name, playerId, lastSeen }
// Suivi des logs déjà traités pour éviter les doublons
const processedLogs = new Set(); // Garde les 500 derniers logs traités
const MAX_PROCESSED_LOGS = 500;
const parseLogMessage = (log) => {
    // Format réel de log Palworld:
    // [2025-12-09 13:28:23] [CHAT] <LouisMazin> !link X2NMAY
    const linkRegex = /\[.*?\]\s*\[CHAT\]\s*<(.+?)>\s*!lier\s+([A-Z0-9]{6})/i;
    const match = log.match(linkRegex);
    
    if (match) {
        const playerName = match[1].trim();
        const code = match[2].toUpperCase();
        
        console.log(`✅ Commande !lier détectée: ${playerName} avec le code ${code}`);
        
        return {
            type: 'link',
            playerName: playerName,
            code: code,
            needsSteamId: true
        };
    }
    // Détecter les déconnexions: [2025-12-09 18:55:19] [LOG] Nami left the server. (User id: gdk_2535420062888893)
    const disconnectRegex = /\[.*?\]\s*\[LOG\]\s*(.+?)\s+left the server\.\s*\(User id:\s*(.+?)\)/i;
    const disconnectMatch = log.match(disconnectRegex);
    if (disconnectMatch) {
        const playerName = disconnectMatch[1].trim();
        const userId = disconnectMatch[2].trim();
        
        console.log(`👋 Déconnexion détectée: ${playerName} (${userId})`);
        
        return {
            type: 'disconnect',
            playerName: playerName,
            userId: userId
        };
    }
    
    return null;
};

const getSteamIdFromPlayerName = async (playerName) => {
    try {
        const response = await axios({
            method: 'get',
            maxBodyLength: Infinity,
            url: 'http://play.louismazin.ovh:8212/v1/api/players',
            headers: { 
                'Accept': 'application/json', 
                'Authorization': `Basic ${process.env.PALWORLD_API_TOKEN}`
            }
        });
        
        const players = response.data.players || {};
        console.log(`🔍 Recherche du Steam ID pour le joueur: ${playerName}`);
        // Chercher le joueur par nom
        for (const [steamId, player] of Object.entries(players)) {
            if (player.name === playerName) {
                return {
                    steamId: player.userId,
                    playerId: player.playerId,
                    name: player.name
                };
            }
        }
        
        return null;
    } catch (error) {
        console.error('Erreur lors de la récupération du Steam ID:', error.message);
        return null;
    }
};

const handleLinkCommand = async (playerName, playerData, code) => {
    try {
        console.log(`🔗 Tentative de liaison détectée: ${playerName} (${playerData.steamId}) avec le code ${code}`);
        
        const result = await verifyLinkCode(code, playerData.steamId, playerData.name, playerData.playerId);
        
        if (result.success) {
            console.log(`✅ Liaison réussie pour ${playerName}`);
            
            if (client) {
                const user = await client.users.fetch(result.discordId).catch(() => null);
                if (user) {
                    await updateUserLinkWithUsername(result.discordId, user.tag);
                    
                    // Ajouter le rôle de joueur lié
                    try {
                        const guild = client.guilds.cache.get(process.env.GUILD_ID);
                        if (guild) {
                            const member = await guild.members.fetch(result.discordId);
                            const linkedRole = guild.roles.cache.get('1467491093649035475');
                            if (linkedRole && !member.roles.cache.has(linkedRole.id)) {
                                await member.roles.add(linkedRole);
                                console.log(`✅ Rôle ajouté à ${user.tag}`);
                            }
                        }
                    } catch (roleError) {
                        console.error('Erreur lors de l\'ajout du rôle:', roleError);
                    }
                    
                    await user.send(
                        `✅ **Liaison réussie !**\n\n` +
                        `Votre compte Discord a été lié avec succès à votre compte Palworld:\n` +
                        `🎮 Nom Palworld: **${playerData.name}**\n` +
                        `🆔 Steam ID: \`${playerData.steamId}\`\n` +
                        `🎯 Player ID: \`${playerData.playerId}\``
                    ).catch(() => {});
                }
            }
        } else {
            console.log(`❌ Échec de la liaison: ${result.message}`);
        }
        
    } catch (error) {
        console.error('Erreur lors du traitement de la commande !link:', error);
    }
};

const getWebSocketCredentials = async (pterodactylToken, serverId) => {
    try {
        const response = await axios({
            method: 'get',
            url: `${process.env.PTERODACTYL_API_URL}/api/client/servers/${serverId}/websocket`,
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${pterodactylToken}`
            }
        });
        
        return response.data.data;
    } catch (error) {
        console.error('Erreur lors de la récupération des credentials WebSocket:', error.message);
        throw error;
    }
};

// Helper: récupérer la liste actuelle des joueurs via l’API Palworld
const fetchCurrentPlayers = async () => {
    try {
        const response = await axios({
            method: 'get',
            maxBodyLength: Infinity,
            url: 'http://play.louismazin.ovh:8212/v1/api/players',
            headers: { 
                'Accept': 'application/json', 
                'Authorization': `Basic ${process.env.PALWORLD_API_TOKEN}`
            }
        });
        const players = response.data.players || {};
        const list = [];
        for (const [, player] of Object.entries(players)) {
            list.push({
                steamId: player.userId,
                playerId: player.playerId,
                name: player.name
            });
        }
        return list;
    } catch (error) {
        // API inaccessible quand le serveur est down
        return null;
    }
};

// Mettre à jour le set des joueurs et détecter les départs silencieux
const pollPlayersAndDetectDisconnects = async (serverState = null) => {
    const list = await fetchCurrentPlayers();

    const now = Date.now();

    if (list && list.length > 0) {
        // Marquer présents
        for (const p of list) {
            connectedPlayers.set(p.steamId, { name: p.name, playerId: p.playerId, lastSeen: now });
        }
        // Détecter ceux qui ont disparu depuis le dernier poll
        for (const [steamId, info] of connectedPlayers) {
            const stillHere = list.find(x => x.steamId === steamId);
            if (!stillHere) {
                try {
                    const result = await updateLastConnection(steamId);
                    if (result.changes > 0) {
                        console.log(`✅ Départ silencieux détecté: ${info.name} (${steamId}) -> lastConnection mis à jour`);
                    }
                } catch (e) {
                    console.error(`❌ Erreur update lastConnection pour ${steamId}:`, e.message);
                }
                connectedPlayers.delete(steamId);
            }
        }
    } else {
        // Pas de liste (serveur inaccessible) ou vide:
        // Si le serveur est arrêté/stopping/offline, considérer tous comme déconnectés
        if (serverState && ['offline', 'stopping', 'stopped'].includes(serverState)) {
            for (const [steamId, info] of connectedPlayers) {
                try {
                    const result = await updateLastConnection(steamId);
                    if (result.changes > 0) {
                        console.log(`✅ Serveur ${serverState}: déconnexion implicite de ${info.name} (${steamId})`);
                    }
                } catch (e) {
                    console.error(`❌ Erreur update lastConnection pour ${steamId}:`, e.message);
                }
            }
            connectedPlayers.clear();
        }
    }
};

const checkAndManageWebSocket = async () => {
    const { cleanExpiredCodes } = require('../core/database.js');
    try {
        await cleanExpiredCodes();

        // Fallback polling toutes les 20s
        await pollPlayersAndDetectDisconnects();

        // Le WebSocket doit toujours être connecté maintenant (pour les déconnexions)
        if (!ws && !isConnecting) {
            console.log('🔌 Connexion au WebSocket pour surveillance...');
            const serverId = process.env.PTERODACTYL_SERVER_ID;
            const pterodactylToken = process.env.PTERODACTYL_API_TOKEN;
            await connectWebSocket(pterodactylToken, serverId);
        }
    } catch (error) {
        console.error('Erreur lors de la vérification du WebSocket:', error);
    }
};

const resetReconnectBackoff = () => {
    reconnectDelayMs = 5000;
};

const scheduleReconnect = () => {
    if (!isMonitoring) return;
    if (reconnectTimeout) clearTimeout(reconnectTimeout);
    const delay = reconnectDelayMs;
    reconnectDelayMs = Math.min(reconnectDelayMs * 2, RECONNECT_DELAY_MAX_MS);
    console.log(`🔄 [WEBSOCKET] Reconnexion WebSocket programmée dans ${Math.round(delay / 1000)}s... (Monitoring actif: ${isMonitoring})`);
    reconnectTimeout = setTimeout(() => {
        console.log('🔄 [WEBSOCKET] Tentative de reconnexion maintenant...');
        checkAndManageWebSocket();
    }, delay);
};

const connectWebSocket = async (pterodactylToken, serverId) => {
    if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
        console.log('⚠️ [WEBSOCKET] Déjà connecté ou en cours de connexion (état:', ws.readyState, ')');
        return;
    }
    if (isConnecting) {
        console.log('⚠️ [WEBSOCKET] Connexion déjà en cours, abandon de cette tentative');
        return;
    }

    isConnecting = true;
    connectionTimestamp = Date.now();
    console.log(`📅 [WEBSOCKET] Timestamp de connexion: ${new Date(connectionTimestamp).toISOString()}`);

    try {
        // Toujours rafraîchir les credentials pour éviter les tokens expirés après redémarrage serveur
        console.log('🔑 [WEBSOCKET] Récupération de nouveaux credentials...');
        const credentials = await getWebSocketCredentials(pterodactylToken, serverId);
        console.log('✅ [WEBSOCKET] Credentials récupérés avec succès');

        ws = new WebSocket(credentials.socket, {
            origin: process.env.PTERODACTYL_API_URL
        });

        ws.on('open', () => {
            console.log('✅ [WEBSOCKET] WebSocket Pterodactyl connecté avec succès');
            isConnecting = false;
            resetReconnectBackoff();
            lastHeartbeatResponse = Date.now(); // Initialiser à maintenant

            console.log('🔐 [WEBSOCKET] Envoi de l\'authentification...');
            ws.send(JSON.stringify({
                event: 'auth',
                args: [credentials.token]
            }));
        });
        
        // Pas besoin de handler 'pong' - on vérifie l'accès aux logs directement

        
        ws.on('message', async (data) => {
            try {
                const message = JSON.parse(data.toString());

                if (message.event === 'auth success') {
                    console.log('✅ [WEBSOCKET] Authentification WebSocket réussie');
                    console.log('📡 [WEBSOCKET] Demande de réception des logs console...');
                    
                    ws.send(JSON.stringify({
                        event: 'send logs',
                        args: [null]
                    }));
                    
                    // Configurer le heartbeat avec détection agressive
                    if (heartbeatInterval) clearInterval(heartbeatInterval);
                    if (heartbeatTimeout) clearTimeout(heartbeatTimeout);
                    
                    lastHeartbeatResponse = Date.now();
                    
                    heartbeatInterval = setInterval(() => {
                        if (ws && ws.readyState === WebSocket.OPEN) {
                            try {
                                const timeSinceLastLog = Date.now() - lastHeartbeatResponse;
                                
                                // Si on n'a pas reçu de log depuis plus de 40s, c'est que l'accès aux logs est perdu
                                if (timeSinceLastLog > 19000) {
                                    console.error(`💔 [WEBSOCKET] Aucun log reçu depuis ${Math.round(timeSinceLastLog/1000)}s - accès aux logs perdu! Reconnexion forcée...`);
                                    if (ws) {
                                        ws.terminate(); // Fermeture immédiate sans attendre
                                    }
                                    return;
                                }
                                
                                // Redemander l'accès aux logs pour s'assurer qu'on les reçoit toujours
                                ws.send(JSON.stringify({
                                    event: 'send logs',
                                    args: [null]
                                }));
                                console.log(`📡 [WEBSOCKET] Vérification accès aux logs (dernier reçu: ${Math.round(timeSinceLastLog/1000)}s)`);
                                
                            } catch (error) {
                                console.error('❌ [WEBSOCKET] Erreur lors de la vérification des logs:', error.message);
                                if (ws) {
                                    ws.terminate();
                                }
                            }
                        } else {
                            console.warn('⚠️ [WEBSOCKET] Vérification impossible: WebSocket non ouvert (état:', ws ? ws.readyState : 'null', ')');
                            if (heartbeatInterval) {
                                clearInterval(heartbeatInterval);
                                heartbeatInterval = null;
                            }
                        }
                    }, 20000); // Vérifier toutes les 20s
                    console.log('📡 [WEBSOCKET] Vérification d\'accès aux logs configurée (20s, timeout 40s)');
                }
                
                // Détecter les réponses du serveur (console output, status, etc) pour mettre à jour le lastHeartbeatResponse
                if (message.event === 'console output' || message.event === 'status' || message.event === 'stats') {
                    lastHeartbeatResponse = Date.now();
                }
                if (message.event === 'console output') {
                    const log = message.args[0];
                    
                    // Protection 0: Vérifier si ce log a déjà été traité (évite les doublons lors des 'send logs' répétés)
                    if (processedLogs.has(log)) {
                        return;
                    }
                    
                    const isChatMessage = log.includes('[CHAT]');
                    
                    // Extraire le timestamp du log
                    const timestampMatch = log.match(/\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\]/);
                    
                    if (timestampMatch) {
                        const logTimestamp = new Date(timestampMatch[1]).getTime();
                        const now = Date.now();
                        
                        // Protection 1: Ignorer les messages très anciens (>15 min) pour éviter l'historique complet
                        if (now - logTimestamp > 15 * 60 * 1000) {
                            return;
                        }
                        
                        // Protection 2: Ignorer les messages antérieurs au démarrage du monitoring
                        // S'applique à TOUS les messages (y compris chat) pour éviter de traiter l'historique au démarrage
                        if (monitoringStartTimestamp && logTimestamp < monitoringStartTimestamp) {
                            return;
                        }
                    }
                    
                    // Marquer ce log comme traité
                    processedLogs.add(log);
                    
                    // Limiter la taille du Set pour éviter une fuite mémoire
                    if (processedLogs.size > MAX_PROCESSED_LOGS) {
                        const firstItem = processedLogs.values().next().value;
                        processedLogs.delete(firstItem);
                    }
                    
                    // Détecter si c'est un message de chat
                    if (isChatMessage) {
                        console.log(`💬 [CONSOLE] Message de chat détecté: ${log.substring(0, 100)}`);
                        
                        // Transférer le message de chat vers Discord via le bridge
                        console.log(`📋 [CONSOLE] Traitement du log pour le bridge...`);
                        try {
                            await handlePalworldChat(log);
                        } catch (bridgeError) {
                            console.error('❌ [CONSOLE] Erreur lors du transfert vers le bridge:', bridgeError);
                        }
                    }
                    
                    const linkData = parseLogMessage(log);
                    
                    if (linkData) {
                        if (linkData.type === 'link') {
                            // Traiter de manière asynchrone sans bloquer le handler WebSocket
                            (async () => {
                                try {
                                    const playerData = await getSteamIdFromPlayerName(linkData.playerName);
                                    
                                    if (playerData) {
                                        await handleLinkCommand(linkData.playerName, playerData, linkData.code);
                                    } else {
                                        console.log(`❌ Impossible de trouver le Steam ID pour ${linkData.playerName}`);
                                    }
                                } catch (error) {
                                    console.error(`❌ Erreur lors du traitement de !lier pour ${linkData.playerName}:`, error);
                                }
                            })();
                        } else if (linkData.type === 'disconnect') {
                            // Traiter de manière asynchrone sans bloquer le handler WebSocket
                            (async () => {
                                try {
                                    const result = await updateLastConnection(linkData.userId);
                                    if (result.changes > 0) {
                                        console.log(`✅ Dernière connexion mise à jour pour ${linkData.playerName} (${linkData.userId})`);
                                    } else {
                                        console.log(`ℹ️ Aucun compte lié trouvé pour ${linkData.playerName} (${linkData.userId})`);
                                    }
                                } catch (error) {
                                    console.error('Erreur lors de la mise à jour de la dernière connexion:', error);
                                }
                            })();
                        }
                    }
                }
                
                if (message.event === 'status') {
                    const state = message.args[0];
                    console.log('📊 Statut du serveur:', state);

                    // Si le serveur s’arrête/offline, forcer la mise à jour des joueurs connectés (déconnexions implicites)
                    if (['offline', 'stopping', 'stopped'].includes(state)) {
                        await pollPlayersAndDetectDisconnects(state);
                    }
                }
                
            } catch (error) {
                console.error('Erreur parsing message WebSocket:', error);
            }
        });
        
        ws.on('error', (error) => {
            console.error('❌ [WEBSOCKET] Erreur WebSocket:', error.message);
            console.error('❌ [WEBSOCKET] Code erreur:', error.code || 'N/A');
            isConnecting = false;
        });
        
        ws.on('close', (code, reason) => {
            const reasonStr = reason ? reason.toString() : 'Aucune raison fournie';
            console.log(`⚠️ [WEBSOCKET] WebSocket Pterodactyl déconnecté (Code: ${code}, Raison: ${reasonStr})`);
            console.log(`🔍 [WEBSOCKET] État actuel: isMonitoring=${isMonitoring}, isConnecting=${isConnecting}`);
            ws = null;
            isConnecting = false;
            connectionTimestamp = null;
            
            if (heartbeatInterval) {
                clearInterval(heartbeatInterval);
                heartbeatInterval = null;
                console.log('💔 [WEBSOCKET] Heartbeat arrêté');
            }
            
            if (heartbeatTimeout) {
                clearTimeout(heartbeatTimeout);
                heartbeatTimeout = null;
            }
            
            // Planifier une reconnexion avec backoff (utile lors des redémarrages quotidiens du serveur)
            if (isMonitoring) {
                scheduleReconnect();
            } else {
                console.log('⚠️ [WEBSOCKET] Monitoring désactivé, pas de reconnexion');
            }
        });
        
    } catch (error) {
        console.error('❌ [WEBSOCKET] Erreur lors de la connexion WebSocket:', error.message);
        console.error('❌ [WEBSOCKET] Stack:', error.stack);
        isConnecting = false;
        connectionTimestamp = null;
        // Échec immédiat -> planifier reconnexion avec backoff
        if (isMonitoring) {
            scheduleReconnect();
        } else {
            console.log('⚠️ [WEBSOCKET] Monitoring désactivé, pas de reconnexion');
        }
    }
};

const stopWebSocketOnly = () => {
    if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
        heartbeatInterval = null;
    }
    
    if (heartbeatTimeout) {
        clearTimeout(heartbeatTimeout);
        heartbeatTimeout = null;
    }
    
    if (refreshCredentialsInterval) {
        clearInterval(refreshCredentialsInterval);
        refreshCredentialsInterval = null;
    }
    
    if (ws) {
        ws.close();
        ws = null;
    }
    
    isConnecting = false;
    lastHeartbeatResponse = null;
};

const forceReconnectToRefreshCredentials = async () => {
    if (!isMonitoring) return;
    
    console.log('🔄 Rafraîchissement des credentials WebSocket (reconnexion préventive)...');
    
    // Fermer la connexion actuelle proprement
    if (ws) {
        ws.close();
        ws = null;
    }
    
    // Attendre un peu avant de reconnecter
    setTimeout(async () => {
        if (isMonitoring) {
            await checkAndManageWebSocket();
        }
    }, 2000);
};

const startConsoleMonitoring = (discordClient, pterodactylToken) => {
    if (isMonitoring) {
        console.log('⚠️ Surveillance déjà active');
        return;
    }
    
    client = discordClient;
    isMonitoring = true;
    monitoringStartTimestamp = Date.now();
    console.log(`📅 Démarrage du monitoring: ${new Date(monitoringStartTimestamp).toISOString()}`);
    resetReconnectBackoff();
    
    const serverId = process.env.PTERODACTYL_SERVER_ID;
    if (!serverId) {
        console.error('❌ PTERODACTYL_SERVER_ID non défini dans .env');
        return;
    }
    
    console.log('🔍 Surveillance de la console Pterodactyl démarrée (mode intelligent)');
    
    // Vérifier immédiatement
    checkAndManageWebSocket();
    
    // Vérifier toutes les 20 secondes (nettoyage codes et reconnect si besoin)
    if (checkInterval) clearInterval(checkInterval);
    checkInterval = setInterval(checkAndManageWebSocket, 20000); // 20s pour détecter vite les départs silencieux
    
    // Rafraîchir les credentials toutes les 55 minutes pour éviter l'expiration
    if (refreshCredentialsInterval) clearInterval(refreshCredentialsInterval);
    refreshCredentialsInterval = setInterval(forceReconnectToRefreshCredentials, CREDENTIALS_REFRESH_INTERVAL);
    console.log(`⏱️ Rafraîchissement automatique des credentials programmé toutes les ${CREDENTIALS_REFRESH_INTERVAL / 60000} minutes`);
};

const stopConsoleMonitoring = () => {
    stopWebSocketOnly();
    
    if (checkInterval) {
        clearInterval(checkInterval);
        checkInterval = null;
    }
    
    if (refreshCredentialsInterval) {
        clearInterval(refreshCredentialsInterval);
        refreshCredentialsInterval = null;
    }
    
    if (reconnectTimeout) {
        clearTimeout(reconnectTimeout);
        reconnectTimeout = null;
    }
    
    isMonitoring = false;
    monitoringStartTimestamp = null;
    processedLogs.clear(); // Nettoyer le cache des logs traités
    console.log('🔌 Surveillance de la console arrêtée');
};

// Fonction pour forcer la reconnexion du WebSocket (utilisée par ramMonitor)
const forceWebSocketReconnect = async () => {
    console.log('🔄 [CONSOLE] Reconnexion forcée du WebSocket...');
    await forceReconnectToRefreshCredentials();
};

// Fonction pour obtenir l'état du WebSocket (pour diagnostic)
const getWebSocketStatus = () => {
    const wsStates = {
        [WebSocket.CONNECTING]: 'CONNECTING',
        [WebSocket.OPEN]: 'OPEN',
        [WebSocket.CLOSING]: 'CLOSING',
        [WebSocket.CLOSED]: 'CLOSED'
    };
    
    return {
        isMonitoring,
        isConnecting,
        hasWebSocket: !!ws,
        wsState: ws ? wsStates[ws.readyState] : 'N/A',
        wsStateRaw: ws ? ws.readyState : null,
        connectionTimestamp: connectionTimestamp ? new Date(connectionTimestamp).toISOString() : null,
        monitoringStartTimestamp: monitoringStartTimestamp ? new Date(monitoringStartTimestamp).toISOString() : null,
        lastHeartbeatResponse: lastHeartbeatResponse ? new Date(lastHeartbeatResponse).toISOString() : null,
        timeSinceLastResponse: lastHeartbeatResponse ? Math.round((Date.now() - lastHeartbeatResponse) / 1000) : null,
        reconnectDelayMs,
        hasHeartbeat: !!heartbeatInterval,
        hasHeartbeatTimeout: !!heartbeatTimeout,
        hasCheckInterval: !!checkInterval,
        hasRefreshInterval: !!refreshCredentialsInterval,
        hasPendingReconnect: !!reconnectTimeout
    };
};

module.exports = { startConsoleMonitoring, stopConsoleMonitoring, forceWebSocketReconnect, getWebSocketStatus };
