/**
 * SLM Framework - Memory Monitor
 * Sistema de monitoreo y detección de memory leaks
 *
 * Características:
 * - Monitoreo automático de memoria
 * - Detección de memory leaks
 * - Alertas por email
 * - Limpieza automática de heap snapshots
 * - Estadísticas históricas
 */

require('dotenv').config();

const fs = require('fs');
const path = require('path');

// Logger se carga de forma "lazy" para evitar dependencias circulares
let logger = null;
function getLogger() {
    if (!logger) {
        try {
            logger = require('./logger.slm');
        } catch (err) {
            logger = console;
        }
    }
    return logger;
}

// Notifications se carga de forma "lazy"
let notifications = null;
function getNotifications() {
    if (!notifications) {
        try {
            notifications = require('./notifications.slm');
        } catch (err) {
            notifications = null;
        }
    }
    return notifications;
}

//======================================================================================================================
// CONFIGURACIÓN
//======================================================================================================================

const config = {
    // Umbral de alerta (80% del heap total)
    alertThreshold: parseFloat(process.env.MEMORY_ALERT_THRESHOLD || '0.80'),

    // Umbral crítico (90% del heap total)
    criticalThreshold: parseFloat(process.env.MEMORY_CRITICAL_THRESHOLD || '0.90'),

    // Intervalo de monitoreo (5 minutos por defecto)
    monitorInterval: parseInt(process.env.MEMORY_MONITOR_INTERVAL || String(5 * 60 * 1000)),

    // Habilitar logging automático
    enableAutoLogging: process.env.MEMORY_AUTO_LOG !== 'false',

    // Habilitar notificaciones por email
    enableEmailAlerts: process.env.MEMORY_EMAIL_ALERTS === 'true',

    // Email para alertas
    alertEmail: process.env.MEMORY_ALERT_EMAIL || '',

    // Máximo de heap snapshots a mantener
    maxHeapSnapshots: parseInt(process.env.MAX_HEAP_SNAPSHOTS || '5'),

    // Umbral para detectar memory leak (MB por medición)
    leakDetectionSlope: parseFloat(process.env.MEMORY_LEAK_SLOPE || '2')
};

//======================================================================================================================
// ESTADO DEL MONITOR
//======================================================================================================================

let monitorTimer = null;
let metrics = {
    startTime: Date.now(),
    measurements: [],
    alerts: [],
    criticalAlerts: [],
    leakDetections: []
};

//======================================================================================================================
// FUNCIONES DE MÉTRICAS
//======================================================================================================================

/**
 * Obtiene métricas actuales de memoria
 * @returns {Object} Métricas de memoria detalladas
 */
function getMemoryMetrics() {
    const usage = process.memoryUsage();

    return {
        timestamp: Date.now(),
        uptime: process.uptime(),
        rss: usage.rss,
        rssMB: Math.round(usage.rss / 1024 / 1024),
        heapTotal: usage.heapTotal,
        heapTotalMB: Math.round(usage.heapTotal / 1024 / 1024),
        heapUsed: usage.heapUsed,
        heapUsedMB: Math.round(usage.heapUsed / 1024 / 1024),
        external: usage.external,
        externalMB: Math.round(usage.external / 1024 / 1024),
        arrayBuffers: usage.arrayBuffers || 0,
        arrayBuffersMB: Math.round((usage.arrayBuffers || 0) / 1024 / 1024),
        heapPercentage: Math.round((usage.heapUsed / usage.heapTotal) * 100)
    };
}

/**
 * Formatea métricas de memoria para display legible
 * @param {Object} metrics - Métricas de memoria
 * @returns {Object} Métricas formateadas
 */
function formatMetrics(metrics) {
    return {
        timestamp: new Date(metrics.timestamp).toISOString(),
        uptime: `${Math.floor(metrics.uptime / 3600)}h ${Math.floor((metrics.uptime % 3600) / 60)}m`,
        memory: {
            rss: `${metrics.rssMB} MB`,
            heapTotal: `${metrics.heapTotalMB} MB`,
            heapUsed: `${metrics.heapUsedMB} MB (${metrics.heapPercentage}%)`,
            external: `${metrics.externalMB} MB`,
            arrayBuffers: `${metrics.arrayBuffersMB} MB`
        },
        status: getMemoryStatus(metrics)
    };
}

/**
 * Obtiene el estado de la memoria
 * @param {Object} metrics - Métricas de memoria
 * @returns {string} Estado: 'healthy', 'warning', 'critical'
 */
function getMemoryStatus(metrics) {
    const percentage = metrics.heapUsed / metrics.heapTotal;

    if (percentage >= config.criticalThreshold) {
        return 'critical';
    } else if (percentage >= config.alertThreshold) {
        return 'warning';
    }
    return 'healthy';
}

/**
 * Verifica si hay alerta de memoria
 * @param {Object} metrics - Métricas de memoria
 * @returns {boolean} True si hay alerta
 */
function isMemoryAlert(metrics) {
    const percentage = metrics.heapUsed / metrics.heapTotal;
    return percentage >= config.alertThreshold;
}

/**
 * Verifica si hay alerta crítica
 * @param {Object} metrics - Métricas de memoria
 * @returns {boolean} True si hay alerta crítica
 */
function isMemoryCritical(metrics) {
    const percentage = metrics.heapUsed / metrics.heapTotal;
    return percentage >= config.criticalThreshold;
}

//======================================================================================================================
// FUNCIONES DE ALERTAS Y NOTIFICACIONES
//======================================================================================================================

/**
 * Registra una alerta de memoria
 * @param {Object} currentMetrics - Métricas actuales
 * @param {string} level - Nivel: 'warning' o 'critical'
 */
async function logAlert(currentMetrics, level = 'warning') {
    const alert = {
        timestamp: Date.now(),
        level,
        heapUsedMB: currentMetrics.heapUsedMB,
        heapTotalMB: currentMetrics.heapTotalMB,
        percentage: currentMetrics.heapPercentage,
        rssMB: currentMetrics.rssMB,
        uptime: currentMetrics.uptime
    };

    if (level === 'critical') {
        metrics.criticalAlerts.push(alert);
    } else {
        metrics.alerts.push(alert);
    }

    const logLevel = level === 'critical' ? 'error' : 'warning';
    getLogger()[logLevel](`Memory ${level} - high heap usage`, {
        type: `MEMORY_${level.toUpperCase()}`,
        heapUsedMB: currentMetrics.heapUsedMB,
        heapTotalMB: currentMetrics.heapTotalMB,
        heapPercentage: currentMetrics.heapPercentage,
        rssMB: currentMetrics.rssMB,
        externalMB: currentMetrics.externalMB,
        uptimeHours: Math.floor(currentMetrics.uptime / 3600),
        recommendation: 'Consider creating a heap snapshot: GET /debug/heap-snapshot'
    });

    // Enviar notificación por email si está habilitado
    if (config.enableEmailAlerts && config.alertEmail) {
        await sendMemoryAlertEmail(alert, currentMetrics);
    }
}

/**
 * Envía notificación por email de alerta de memoria
 * @param {Object} alert - Información de la alerta
 * @param {Object} metrics - Métricas actuales
 */
async function sendMemoryAlertEmail(alert, metrics) {
    const notif = getNotifications();
    if (!notif) return;

    try {
        const uptimeHours = Math.floor(metrics.uptime / 3600);
        const subject = `[${alert.level.toUpperCase()}] Memory Alert - SLM Framework API`;

        const message = `
Alerta de Memoria - Nivel ${alert.level.toUpperCase()}

Servidor: ${process.env.API_NAME || 'SLM Framework API'}
Timestamp: ${new Date(alert.timestamp).toISOString()}

MÉTRICAS ACTUALES:
- Heap usado: ${metrics.heapUsedMB} MB / ${metrics.heapTotalMB} MB (${metrics.heapPercentage}%)
- RSS: ${metrics.rssMB} MB
- External: ${metrics.externalMB} MB
- Uptime: ${uptimeHours} horas

RECOMENDACIONES:
${alert.level === 'critical' ?
    '⚠️ ACCIÓN INMEDIATA REQUERIDA:\n' +
    '1. Revisar heap snapshot: GET /debug/heap-snapshot\n' +
    '2. Reiniciar servicio si es necesario\n' +
    '3. Investigar posible memory leak'
    :
    '1. Monitorear tendencia de memoria\n' +
    '2. Considerar crear heap snapshot para análisis\n' +
    '3. Revisar logs para patrones anormales'
}

ACCIONES DISPONIBLES:
- Heap Snapshot: curl http://localhost:3000/debug/heap-snapshot
- Métricas: curl http://localhost:3000/debug/memory
- Stats: curl http://localhost:3000/debug/memory/stats
- GC Forzado: curl http://localhost:3000/debug/gc

---
Este es un mensaje automático del sistema de monitoreo de memoria.
        `.trim();

        await notif.sendEmail({
            to: config.alertEmail,
            subject,
            text: message
        });

        getLogger().info('Memory alert email sent', {
            type: 'MEMORY_ALERT_EMAIL',
            level: alert.level,
            to: config.alertEmail
        });

    } catch (error) {
        getLogger().error('Error sending memory alert email', {
            type: 'MEMORY_ALERT_EMAIL_ERROR',
            error: error.message
        });
    }
}

/**
 * Registra métricas de memoria
 * @param {Object} currentMetrics - Métricas actuales
 */
async function logMetrics(currentMetrics) {
    if (isMemoryCritical(currentMetrics)) {
        await logAlert(currentMetrics, 'critical');
    } else if (isMemoryAlert(currentMetrics)) {
        await logAlert(currentMetrics, 'warning');
    } else if (config.enableAutoLogging) {
        getLogger().info('Memory metrics', {
            type: 'MEMORY_METRICS',
            heapUsedMB: currentMetrics.heapUsedMB,
            heapTotalMB: currentMetrics.heapTotalMB,
            heapPercentage: currentMetrics.heapPercentage,
            status: getMemoryStatus(currentMetrics)
        });
    }
}

//======================================================================================================================
// FUNCIONES DE MONITOREO
//======================================================================================================================

/**
 * Función de monitoreo que se ejecuta periódicamente
 */
async function monitorMemory() {
    const currentMetrics = getMemoryMetrics();

    // Guardar medición
    metrics.measurements.push({
        timestamp: currentMetrics.timestamp,
        heapUsedMB: currentMetrics.heapUsedMB,
        heapPercentage: currentMetrics.heapPercentage,
        rssMB: currentMetrics.rssMB
    });

    // Mantener solo las últimas 100 mediciones
    if (metrics.measurements.length > 100) {
        metrics.measurements.shift();
    }

    // Log de métricas y alertas
    await logMetrics(currentMetrics);

    // Detectar memory leak
    const leak = detectMemoryLeak();
    if (leak && config.enableEmailAlerts && config.alertEmail) {
        await sendMemoryLeakEmail(leak);
    }

    // Forzar GC si está disponible y memoria está alta (> 70%)
    if (global.gc && currentMetrics.heapPercentage > 70) {
        getLogger().info('Triggering manual garbage collection', {
            type: 'MEMORY_GC',
            heapPercentage: currentMetrics.heapPercentage
        });
        global.gc();
    }
}

/**
 * Inicia el monitoreo automático de memoria
 */
function startMonitoring() {
    if (monitorTimer) {
        getLogger().info('Memory monitor already running');
        return;
    }

    getLogger().info('Starting memory monitor', {
        type: 'MEMORY_MONITOR_START',
        intervalSeconds: config.monitorInterval / 1000,
        thresholdPercent: config.alertThreshold * 100,
        criticalPercent: config.criticalThreshold * 100,
        emailAlertsEnabled: config.enableEmailAlerts
    });

    // Ejecutar inmediatamente
    monitorMemory();

    // Configurar intervalo
    monitorTimer = setInterval(monitorMemory, config.monitorInterval);

    // Asegurar limpieza al salir
    process.once('exit', stopMonitoring);
}

/**
 * Detiene el monitoreo automático
 */
function stopMonitoring() {
    if (monitorTimer) {
        clearInterval(monitorTimer);
        monitorTimer = null;
        getLogger().info('Memory monitor stopped', { type: 'MEMORY_MONITOR_STOP' });
    }
}

/**
 * Obtiene estadísticas históricas de memoria
 * @returns {Object|null} Estadísticas agregadas
 */
function getMemoryStats() {
    if (metrics.measurements.length === 0) {
        return null;
    }

    const heapUsages = metrics.measurements.map(m => m.heapUsedMB);
    const heapPercentages = metrics.measurements.map(m => m.heapPercentage);

    return {
        startTime: metrics.startTime,
        uptime: Math.floor((Date.now() - metrics.startTime) / 1000),
        measurementCount: metrics.measurements.length,
        alertCount: metrics.alerts.length,
        criticalAlertCount: metrics.criticalAlerts.length,
        current: getMemoryMetrics(),
        heap: {
            min: Math.min(...heapUsages),
            max: Math.max(...heapUsages),
            avg: Math.round(heapUsages.reduce((a, b) => a + b, 0) / heapUsages.length),
            current: heapUsages[heapUsages.length - 1]
        },
        percentage: {
            min: Math.min(...heapPercentages),
            max: Math.max(...heapPercentages),
            avg: Math.round(heapPercentages.reduce((a, b) => a + b, 0) / heapPercentages.length),
            current: heapPercentages[heapPercentages.length - 1]
        },
        recentAlerts: metrics.alerts.slice(-5),
        recentCriticalAlerts: metrics.criticalAlerts.slice(-3),
        leakDetections: metrics.leakDetections
    };
}

//======================================================================================================================
// FUNCIONES DE HEAP SNAPSHOT
//======================================================================================================================

/**
 * Limpia heap snapshots antiguos
 * @param {string} directory - Directorio donde están los snapshots
 * @param {number} maxFiles - Máximo número de archivos a mantener
 * @returns {Array} Lista de archivos eliminados
 */
function cleanupHeapSnapshots(directory, maxFiles = config.maxHeapSnapshots) {
    try {
        const files = fs.readdirSync(directory)
            .filter(f => f.startsWith('heapSnapshot-') && f.endsWith('.heapsnapshot'))
            .map(f => ({
                name: f,
                path: path.join(directory, f),
                time: fs.statSync(path.join(directory, f)).mtime.getTime()
            }))
            .sort((a, b) => b.time - a.time);

        const toDelete = files.slice(maxFiles);
        const deleted = [];

        toDelete.forEach(file => {
            try {
                fs.unlinkSync(file.path);
                deleted.push(file.name);
                getLogger().info('Deleted old heap snapshot', {
                    type: 'HEAP_CLEANUP',
                    file: file.name
                });
            } catch (err) {
                getLogger().error('Error deleting heap snapshot', {
                    type: 'HEAP_CLEANUP_ERROR',
                    file: file.name,
                    error: err.message
                });
            }
        });

        return deleted;

    } catch (err) {
        getLogger().error('Error cleaning up heap snapshots', {
            type: 'HEAP_CLEANUP_ERROR',
            error: err.message
        });
        return [];
    }
}

//======================================================================================================================
// DETECCIÓN DE MEMORY LEAKS
//======================================================================================================================

/**
 * Detecta posibles memory leaks comparando mediciones
 * @returns {Object|null} Información del leak si se detecta
 */
function detectMemoryLeak() {
    if (metrics.measurements.length < 10) {
        return null;
    }

    const recent = metrics.measurements.slice(-10);
    const n = recent.length;
    const sumX = recent.reduce((sum, _, i) => sum + i, 0);
    const sumY = recent.reduce((sum, m) => sum + m.heapUsedMB, 0);
    const sumXY = recent.reduce((sum, m, i) => sum + (i * m.heapUsedMB), 0);
    const sumX2 = recent.reduce((sum, _, i) => sum + (i * i), 0);

    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);

    if (slope > config.leakDetectionSlope) {
        const firstHeap = recent[0].heapUsedMB;
        const lastHeap = recent[recent.length - 1].heapUsedMB;
        const increase = lastHeap - firstHeap;
        const increasePercent = Math.round((increase / firstHeap) * 100);

        const leakInfo = {
            detected: true,
            timestamp: Date.now(),
            slope: Math.round(slope * 100) / 100,
            increasePerMeasurement: Math.round(slope),
            totalIncrease: increase,
            increasePercent,
            measurements: n,
            recommendation: 'Create a heap snapshot and analyze with Chrome DevTools: GET /debug/heap-snapshot'
        };

        // Registrar detección
        if (metrics.leakDetections.length === 0 ||
            Date.now() - metrics.leakDetections[metrics.leakDetections.length - 1].timestamp > 3600000) {
            metrics.leakDetections.push(leakInfo);

            getLogger().error('Memory leak detected', {
                type: 'MEMORY_LEAK_DETECTED',
                ...leakInfo
            });
        }

        return leakInfo;
    }

    return null;
}

/**
 * Envía notificación de memory leak detectado
 * @param {Object} leak - Información del leak
 */
async function sendMemoryLeakEmail(leak) {
    const notif = getNotifications();
    if (!notif) return;

    // Solo enviar si es una nueva detección (no duplicados en la última hora)
    if (metrics.leakDetections.length > 1) {
        const lastLeak = metrics.leakDetections[metrics.leakDetections.length - 2];
        if (Date.now() - lastLeak.timestamp < 3600000) {
            return; // Ya se envió notificación recientemente
        }
    }

    try {
        const subject = '[MEMORY LEAK] Posible fuga de memoria detectada - SLM Framework API';

        const message = `
ALERTA: Posible Memory Leak Detectado

Servidor: ${process.env.API_NAME || 'SLM Framework API'}
Timestamp: ${new Date(leak.timestamp).toISOString()}

ANÁLISIS:
- Tendencia (slope): ${leak.slope} MB por medición
- Incremento por medición: ${leak.increasePerMeasurement} MB
- Incremento total: ${leak.totalIncrease} MB (${leak.increasePercent}%)
- Mediciones analizadas: ${leak.measurements}

⚠️ ACCIÓN REQUERIDA:
1. Crear heap snapshot para análisis:
   curl http://localhost:3000/debug/heap-snapshot

2. Analizar heap snapshot con Chrome DevTools:
   - Abrir Chrome DevTools
   - Memory tab > Load
   - Cargar archivo .heapsnapshot

3. Buscar patrones comunes de memory leak:
   - Event listeners no removidos
   - Timers/intervals no limpiados
   - Closures manteniendo referencias
   - Caches sin límite de tamaño
   - Conexiones abiertas no cerradas

ENDPOINTS DE DEBUG:
- Métricas: curl http://localhost:3000/debug/memory
- Stats: curl http://localhost:3000/debug/memory/stats
- GC: curl http://localhost:3000/debug/gc
- Leak detection: curl http://localhost:3000/debug/memory/leak-detection

RECOMENDACIÓN:
${leak.recommendation}

---
Este es un mensaje automático del sistema de detección de memory leaks.
        `.trim();

        await notif.sendEmail({
            to: config.alertEmail,
            subject,
            text: message
        });

        getLogger().info('Memory leak email sent', {
            type: 'MEMORY_LEAK_EMAIL',
            to: config.alertEmail
        });

    } catch (error) {
        getLogger().error('Error sending memory leak email', {
            type: 'MEMORY_LEAK_EMAIL_ERROR',
            error: error.message
        });
    }
}

//======================================================================================================================
// EXPORTS
//======================================================================================================================

module.exports = {
    // Métricas
    getMemoryMetrics,
    formatMetrics,
    getMemoryStatus,
    isMemoryAlert,
    isMemoryCritical,

    // Monitoreo
    startMonitoring,
    stopMonitoring,
    getMemoryStats,

    // Heap snapshots
    cleanupHeapSnapshots,

    // Detección de leaks
    detectMemoryLeak,

    // Configuración
    config
};
