/*
    SLM Framework API REST
    Node.js + Express.js

    por: SLM Dev Team
    Enero/2026 -- Creación
*/

// Cargar variables de entorno (DEBE estar al inicio)
require('dotenv').config();

const fs = require('fs');
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const cors = require('cors');
const NodeCache = require('node-cache');
const { match } = require('path-to-regexp');

// Módulos del sistema
const slmapi = require('./slm.framework/system/slmapi.slm');
const logger = require('./slm.framework/system/logger.slm');
const constants = require('./slm.framework/system/constants.slm');
const utils = require('./slm.framework/system/utils.slm');
const memoryMonitor = require('./slm.framework/system/memory-monitor.slm');

// Configuración
const API_VERSION = '1.0.0';
const API_NAME = 'SLM Framework API';
const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
const PORT = parseInt(process.env.API_PORT) || 3000;

// Body parser configuration
app.use(bodyParser.json({ limit: '100mb' }));
app.use(bodyParser.urlencoded({ limit: '100mb', extended: true }));

// CORS configuration
app.use(cors({
    origin: '*', // En producción, especificar dominios permitidos
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
}));

// HTTP Logging middleware
app.use(logger.httpLogger);

// Cache configuration (15 seconds TTL)
const cache = new NodeCache({ stdTTL: 15, useClones: false });

// Trust proxy (para obtener IP real detrás de reverse proxy)
app.set('trust proxy', true);

// ============================================================================
// SISTEMA DE CARGA DINÁMICA DE ENDPOINTS
// ============================================================================

const endpointsPath = path.join(__dirname, 'slm.framework/system/endpoints.slm');
let cachedRoutes = null;
let endpointsLastModified = null;

/**
 * Carga endpoints.slm con caché inteligente
 * Solo recarga si el archivo fue modificado (hot-reload automático)
 * @returns {Object} Rutas configuradas
 */
function loadEndpoints() {
    try {
        const stats = fs.statSync(endpointsPath);
        const currentModified = stats.mtime.getTime();

        // Solo recargar si el archivo cambió
        if (!cachedRoutes || currentModified !== endpointsLastModified) {
            const raw = fs.readFileSync(endpointsPath, 'utf8');
            cachedRoutes = JSON.parse(raw);
            endpointsLastModified = currentModified;

            logger.info('Endpoints reloaded', {
                routeCount: Object.keys(cachedRoutes).length,
                timestamp: currentModified
            });
        }

        return cachedRoutes;
    } catch (err) {
        logger.error('Error loading endpoints', {
            error: err.message,
            stack: err.stack
        });
        // Devolver caché anterior si hay error
        return cachedRoutes || {};
    }
}

// ============================================================================
// MIDDLEWARE DE SEGURIDAD
// ============================================================================

const securityMiddleware = (req, res, next) => {
    const authHeader = req.get('X-API-Key');
    const requestIp = req.ip;

    // 401 Unauthorized: No se proporcionó API key
    if (!authHeader) {
        logger.security('API Key required', { ip: requestIp, path: req.path });
        return res.status(constants.HTTP.UNAUTHORIZED).json(
            utils.errorResponse(constants.ERRORS.PERMISSION_REQUIRED)
        );
    }

    // Validar API key contra lista permitida
    if (!slmapi.api_keys.includes(authHeader)) {
        logger.security('Invalid API Key', {
            apiKey: utils.sanitizeForLog(authHeader),
            ip: requestIp,
            path: req.path
        });
        return res.status(constants.HTTP.FORBIDDEN).json(
            utils.errorResponse(constants.ERRORS.INVALID_APIKEY)
        );
    }

    // API Key válida, continuar
    req.slm = {
        apiKey: authHeader,
        ip: requestIp
    };

    next();
};

// Aplicar middleware de seguridad a todas las rutas /v1
app.use('/v1', securityMiddleware);

// ============================================================================
// RUTAS ESTÁTICAS (públicas, sin autenticación)
// ============================================================================

app.get('/', (req, res) => {
    res.json({
        name: API_NAME,
        version: API_VERSION,
        status: 'operational',
        timestamp: slmapi.timestamp()
    });
});

app.get('/about', (req, res) => {
    res.json({
        name: API_NAME,
        version: API_VERSION,
        developer: 'SLM Dev Team',
        email: 'dev@slm.cloud',
        status: 'operational',
        elasticsearch: {
            server: slmapi.es_server,
            index: slmapi.es_index
        },
        timestamp: slmapi.timestamp()
    });
});

app.get(['/version', '/ver'], (req, res) => {
    res.json({
        name: API_NAME,
        version: API_VERSION,
        timestamp: slmapi.timestamp()
    });
});

app.get('/status', (req, res) => {
    const memUsage = process.memoryUsage();

    res.json({
        name: API_NAME,
        status: 'operational',
        version: API_VERSION,
        uptime: process.uptime(),
        environment: process.env.NODE_ENV || 'production',
        memory: {
            rss: utils.formatBytes(memUsage.rss),
            heapTotal: utils.formatBytes(memUsage.heapTotal),
            heapUsed: utils.formatBytes(memUsage.heapUsed),
            external: utils.formatBytes(memUsage.external)
        },
        timestamp: slmapi.timestamp()
    });
});

// Health check endpoint
app.get('/health', (req, res) => {
    const health = {
        status: 'healthy',
        timestamp: slmapi.timestamp(),
        uptime: process.uptime(),
        version: API_VERSION,
        environment: process.env.NODE_ENV || 'production'
    };

    const memMetrics = process.memoryUsage();
    health.memory = {
        heapUsedMB: Math.round(memMetrics.heapUsed / 1024 / 1024),
        heapTotalMB: Math.round(memMetrics.heapTotal / 1024 / 1024),
        rssMB: Math.round(memMetrics.rss / 1024 / 1024)
    };

    res.status(constants.HTTP.OK).json(health);
});

// ============================================================================
// ROUTER DINÁMICO UNIVERSAL (path-to-regexp)
// ============================================================================

/**
 * Middleware para manejar todas las rutas dinámicamente
 * Usa path-to-regexp para matching avanzado de rutas
 */
app.all('*', async (req, res) => {
    try {
        const reqMethod = req.method.toUpperCase();
        const reqPath = req.path;

        // Cargar endpoints con hot-reload automático
        const routes = loadEndpoints();

        // Iterar sobre rutas registradas
        for (const [key, config] of Object.entries(routes)) {
            // Separar el método y la ruta del key
            const [routeMethod, pattern] = key.split(' ');

            if (routeMethod !== reqMethod) continue;

            // Intentar hacer match con path-to-regexp
            const matcher = match(pattern, { decode: decodeURIComponent });
            const matchResult = matcher(reqPath);

            if (matchResult) {
                // Match exitoso, extraer parámetros
                const params = matchResult.params;

                // Validar que no haya parámetros vacíos
                const missingParams = Object.entries(params).filter(
                    ([_, val]) => val === undefined || val === ''
                );

                if (missingParams.length > 0) {
                    return res.status(constants.HTTP.BAD_REQUEST).json(
                        utils.errorResponse(constants.ERRORS.MISSING_PARAMETERS, constants.HTTP.BAD_REQUEST, {
                            missing: missingParams.map(([k]) => k)
                        })
                    );
                }

                // Resolver path del handler
                const handlerPath = path.resolve(__dirname, config.handler);

                // Hot-reload: invalidar caché de módulos solo en desarrollo
                if (IS_DEVELOPMENT) {
                    delete require.cache[require.resolve(handlerPath)];
                }

                // Cargar handler
                const handler = require(handlerPath);

                // Asignar parámetros a req
                req.params = matchResult.params;

                // Ejecutar handler
                return handler(req, res, null, slmapi);
            }
        }

        // No se encontró ninguna ruta
        logger.warning('Endpoint not found', {
            method: reqMethod,
            path: reqPath,
            ip: req.ip
        });

        return res.status(constants.HTTP.NOT_FOUND).json(
            utils.errorResponse(constants.ERRORS.ENDPOINT_NOT_FOUND, constants.HTTP.NOT_FOUND, {
                method: reqMethod,
                path: reqPath
            })
        );

    } catch (err) {
        logger.error('Dynamic route handler error', {
            error: err.message,
            method: req.method,
            path: req.path,
            stack: err.stack
        });

        return res.status(constants.HTTP.INTERNAL_ERROR).json(
            utils.errorResponse(
                constants.ERRORS.INTERNAL_ERROR,
                constants.HTTP.INTERNAL_ERROR,
                {
                    details: IS_DEVELOPMENT ? err.message : undefined
                }
            )
        );
    }
});

// ============================================================================
// MANEJO DE ERRORES GLOBALES
// ============================================================================

app.use((err, req, res, next) => {
    logger.error('Global error handler', {
        error: err.message,
        stack: err.stack,
        method: req.method,
        path: req.path
    });

    res.status(constants.HTTP.INTERNAL_ERROR).json(
        utils.errorResponse(
            constants.ERRORS.INTERNAL_ERROR,
            constants.HTTP.INTERNAL_ERROR,
            {
                details: IS_DEVELOPMENT ? err.message : undefined
            }
        )
    );
});

// ============================================================================
// INICIAR SERVIDOR
// ============================================================================

app.listen(PORT, () => {
    logger.info('Server started', {
        api: `${API_NAME} v${API_VERSION}`,
        port: PORT,
        environment: process.env.NODE_ENV || 'production',
        hotReload: IS_DEVELOPMENT ? 'enabled (automatic)' : 'disabled',
        nodeVersion: process.version,
        elasticsearch: slmapi.es_server,
        index: slmapi.es_index
    });

    console.log('╔════════════════════════════════════════════════════════════╗');
    console.log('║          SLM Framework API REST - Node.js                 ║');
    console.log('╚════════════════════════════════════════════════════════════╝');
    console.log('');
    console.log(`  Name:           ${API_NAME}`);
    console.log(`  Version:        ${API_VERSION}`);
    console.log(`  Port:           ${PORT}`);
    console.log(`  Environment:    ${process.env.NODE_ENV || 'production'}`);
    console.log(`  Hot Reload:     ${IS_DEVELOPMENT ? 'Automatic (dev)' : 'Disabled (prod)'}`);
    console.log(`  Elasticsearch:  ${slmapi.es_server}`);
    console.log(`  Index:          ${slmapi.es_index}`);
    console.log('');
    console.log(`  Server running at: http://localhost:${PORT}`);
    console.log('');
    console.log('════════════════════════════════════════════════════════════');

    // Iniciar monitoreo de memoria
    memoryMonitor.startMonitoring();
});

// ============================================================================
// PROCESS EVENT HANDLERS
// ============================================================================

// Capturar excepciones no manejadas
process.on('uncaughtException', (err) => {
    logger.error('Uncaught exception', {
        error: err.message,
        stack: err.stack,
        type: 'CRITICAL'
    });
    // No cerrar el servidor para mantener disponibilidad
});

// Capturar rechazos de promesas no manejados
process.on('unhandledRejection', (reason, promise) => {
    logger.error('Unhandled promise rejection', {
        reason: reason?.message || reason,
        type: 'CRITICAL'
    });
    // No cerrar el servidor para mantener disponibilidad
});

// Graceful shutdown
process.on('SIGTERM', () => {
    logger.info('SIGTERM signal received: closing HTTP server');
    memoryMonitor.stopMonitoring();
    process.exit(0);
});

process.on('SIGINT', () => {
    logger.info('SIGINT signal received: closing HTTP server');
    memoryMonitor.stopMonitoring();
    process.exit(0);
});
