#!/usr/bin/env php
<?php
/**
 * Environment Doctor CLI
 * Usage:
 *   php bin/doctor
 *   php bin/doctor --strict
 */

$basePath = dirname(__DIR__);
define('BASE_PATH', $basePath . '/');

$args = array_slice($argv, 1);
$strictMode = in_array('--strict', $args, true) || in_array('-s', $args, true);

if (in_array('--help', $args, true) || in_array('-h', $args, true)) {
    echo "\nKlee Doctor\n";
    echo "Uso:\n";
    echo "  php bin/doctor           Validación general de entorno\n";
    echo "  php bin/doctor --strict  Falla también por warnings (ideal CI)\n\n";
    exit(0);
}

require $basePath . '/core/helpers/Env.php';
Env::load($basePath . '/.env');

if (file_exists($basePath . '/app/config/Config.php')) {
    require $basePath . '/app/config/Config.php';
}
if (file_exists($basePath . '/app/config/ConfigEnv.php')) {
    require $basePath . '/app/config/ConfigEnv.php';
}

$errors = 0;
$warnings = 0;

$print = function ($type, $message) use (&$errors, &$warnings) {
    if ($type === 'ERROR') {
        $errors++;
        echo "[error] {$message}\n";
        return;
    }

    if ($type === 'WARN') {
        $warnings++;
        echo "[warn] {$message}\n";
        return;
    }

    echo "[ok] {$message}\n";
};

echo "\nKlee Doctor\n";
echo str_repeat('-', 60) . "\n";

// 1) Config files
$configPath = $basePath . '/app/config/Config.php';
$configEnvPath = $basePath . '/app/config/ConfigEnv.php';

if (is_file($configPath)) {
    $print('OK', 'Config.php encontrado.');
} else {
    $print('ERROR', 'Falta app/config/Config.php');
}

if (is_file($configEnvPath)) {
    $print('OK', 'ConfigEnv.php encontrado.');
} else {
    $print('ERROR', 'Falta app/config/ConfigEnv.php (copiar desde ConfigEnv.example.php).');
}

if (is_file($basePath . '/.env')) {
    $print('OK', '.env encontrado.');
} else {
    $print('WARN', '.env no encontrado (usa .env.example como plantilla).');
}

// 2) Env variables (soft requirement)
$requiredEnv = array('DB_DRIVER', 'DB_HOST', 'DB_NAME', 'DB_USER');
$missingEnv = array();
foreach ($requiredEnv as $envKey) {
    if (getenv($envKey) === false || trim((string)getenv($envKey)) === '') {
        $missingEnv[] = $envKey;
    }
}

if (empty($missingEnv)) {
    $print('OK', 'Variables de entorno base de DB presentes.');
} else {
    $print('WARN', 'Variables de entorno faltantes: ' . implode(', ', $missingEnv));
}

// 2.1) SMTP variables
$mailHost = trim((string)(getenv('MAIL_HOST') ?: ''));
$mailUser = trim((string)(getenv('MAIL_USER') ?: ''));
$mailPassRaw = getenv('MAIL_PASS');
$mailPass = $mailPassRaw === false ? '' : (string)$mailPassRaw;
$mailPort = trim((string)(getenv('MAIL_PORT') ?: ''));
$mailEncryption = strtolower(trim((string)(getenv('MAIL_ENCRYPTION') ?: '')));

if ($mailHost !== '') {
    $smtpErrors = array();
    $smtpWarnings = array();

    if ($mailPort === '' || !ctype_digit($mailPort)) {
        $smtpErrors[] = 'MAIL_PORT inválido (debe ser numérico).';
    }

    if ($mailEncryption !== '' && !in_array($mailEncryption, array('tls', 'ssl', 'starttls', 'none'), true)) {
        $smtpErrors[] = 'MAIL_ENCRYPTION inválido (use tls|ssl|starttls|none).';
    }

    if ($mailUser === '') {
        $smtpWarnings[] = 'MAIL_USER vacío.';
    }

    if ($mailPassRaw === false || $mailPass === '') {
        $smtpWarnings[] = 'MAIL_PASS vacío.';
    }

    if (empty($smtpErrors) && empty($smtpWarnings)) {
        $print('OK', 'Configuración SMTP base presente.');
    } else {
        foreach ($smtpErrors as $msg) {
            $print('ERROR', $msg);
        }
        foreach ($smtpWarnings as $msg) {
            $print('WARN', $msg);
        }
    }
} else {
    $print('WARN', 'MAIL_HOST no definido; envío SMTP quedará deshabilitado o incompleto.');
}

// 3) PHP/extensiones
$print('OK', 'PHP version: ' . PHP_VERSION);
$requiredExt = array('gd', 'mbstring', 'pdo', 'pdo_mysql', 'openssl');
foreach ($requiredExt as $ext) {
    if (extension_loaded($ext)) {
        $print('OK', "Extensión {$ext} cargada.");
    } else {
        $print('ERROR', "Extensión {$ext} no cargada.");
    }
}

// 4) Writable directories
$writableDirs = array(
    $basePath . '/storage',
    $basePath . '/storage/cache',
    $basePath . '/logs',
    $basePath . '/files',
);

foreach ($writableDirs as $dir) {
    if (!is_dir($dir)) {
        $print('WARN', "Directorio no existe: {$dir}");
        continue;
    }

    if (is_writable($dir)) {
        $print('OK', "Directorio escribible: {$dir}");
    } else {
        $print('ERROR', "Directorio sin permiso de escritura: {$dir}");
    }
}

// 5) Classmap integrity
$classmapFile = $basePath . '/storage/cache/classmap.php';
if (!is_file($classmapFile)) {
    $print('WARN', 'classmap.php no encontrado (ejecuta php bin/cache clear para reconstruir).');
} else {
    $classmap = include $classmapFile;
    if (!is_array($classmap)) {
        $print('ERROR', 'classmap.php inválido (no retorna array).');
    } else {
        $basePathReal = realpath($basePath);
        $invalid = 0;

        foreach ($classmap as $className => $classPath) {
            if (!is_string($className) || !is_string($classPath) || !file_exists($classPath)) {
                $invalid++;
                continue;
            }

            $classPathReal = realpath($classPath);
            if ($classPathReal === false || $basePathReal === false) {
                $invalid++;
                continue;
            }

            $startsWith = (DIRECTORY_SEPARATOR === '\\')
                ? stripos($classPathReal, $basePathReal) === 0
                : strpos($classPathReal, $basePathReal) === 0;

            if (!$startsWith) {
                $invalid++;
            }
        }

        if ($invalid === 0) {
            $print('OK', 'Classmap íntegro.');
        } else {
            $print('WARN', "Classmap con {$invalid} entrada(s) inválida(s).");
        }
    }
}

// 6) DB connectivity
$dbConfig = null;
if (class_exists('ConfigEnv') && property_exists('ConfigEnv', 'DB_CONNECTIONS') && isset(ConfigEnv::$DB_CONNECTIONS['klee'])) {
    $dbConfig = ConfigEnv::$DB_CONNECTIONS['klee'];
}

if (!$dbConfig) {
    $dbConfig = array(
        'driver' => getenv('DB_DRIVER') ?: 'mysql',
        'host' => getenv('DB_HOST') ?: '',
        'dbname' => getenv('DB_NAME') ?: '',
        'user' => getenv('DB_USER') ?: '',
        'password' => getenv('DB_PASS') ?: '',
    );
}

if (empty($dbConfig['host']) || empty($dbConfig['dbname']) || empty($dbConfig['user'])) {
    $print('ERROR', 'Configuración de DB incompleta (host/dbname/user).');
} else {
    try {
        if (($dbConfig['driver'] ?? 'mysql') !== 'mysql') {
            throw new RuntimeException('Driver no soportado por doctor: ' . ($dbConfig['driver'] ?? 'null'));
        }

        // $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset=utf8mb4";
        $dsn = "mysql:host={$dbConfig['host']};port={$dbConfig['port']};dbname={$dbConfig['dbname']};charset=utf8mb4";
        $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['password'] ?? '');
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->query('SELECT 1');
        $print('OK', 'Conexión DB correcta.');
    } catch (Exception $e) {
        $print('ERROR', 'Conexión DB falló: ' . $e->getMessage());
    }
}

echo str_repeat('-', 60) . "\n";
echo "Resumen: errors={$errors} warnings={$warnings}\n";

// 7) Permissions ↔ Controller actions gate
// Verify that every *Action method in controllers registered in Modules.php
// has a corresponding entry in PermissionsSeeder for the admin role.
$seederFile = $basePath . '/database/seeders/PermissionsSeeder.php';
if (is_file($seederFile)) {
    $seederSource = file_get_contents($seederFile);

    // Extract the $modules array keys and their action lists from the seeder source
    $seederModules = array();
    if (preg_match_all("/\s*'([A-Za-z]+)'\s*=>\s*\[([^\]]+)\]/", $seederSource, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $m) {
            $modName = $m[1];
            $actionsBlock = $m[2];
            $actions = array();
            if (preg_match_all("/'([a-zA-Z]+)'\s*=>/", $actionsBlock, $am)) {
                $actions = $am[1];
            }
            $seederModules[$modName] = $actions;
        }
    }

    // Load Modules registry to get controller names
    if (class_exists('Modules', false) || (is_file($basePath . '/app/config/Modules.php') && require_once $basePath . '/app/config/Modules.php')) {
        if (method_exists('Modules', 'getRegistry')) {
            $registry = Modules::getRegistry();
        } else {
            $registry = array();
        }
    } else {
        $registry = array();
    }

    $skipModules = array('Login', 'Portal', 'General', 'Ajax', 'Api', 'Test', 'ElFinder');
    $missingActions = array();

    foreach ($registry as $modKey => $modConfig) {
        if (empty($modConfig['enabled'])) {
            continue;
        }

        $controllerSlug = isset($modConfig['controller']) ? $modConfig['controller'] : $modKey;

        // Find the actual controller file by case-insensitive match
        $controllerFile = null;
        $controllerClass = null;
        $controllersDir = $basePath . '/app/controllers/';
        foreach (scandir($controllersDir) as $file) {
            if (strtolower($file) === strtolower($controllerSlug) . 'controller.php') {
                $controllerFile = $controllersDir . $file;
                $controllerClass = str_replace('.php', '', $file);
                break;
            }
        }

        if ($controllerFile === null || !is_file($controllerFile)) {
            continue;
        }

        // Derive ModuleName as stored in seeder (strip "Controller" suffix)
        $moduleName = str_replace('Controller', '', $controllerClass);

        if (in_array($moduleName, $skipModules, true)) {
            continue;
        }

        $src = file_get_contents($controllerFile);
        // Find all public *Action methods
        if (!preg_match_all('/public\s+function\s+([a-zA-Z]+)Action\s*\(/', $src, $actionMatches)) {
            continue;
        }

        $seededActions = isset($seederModules[$moduleName]) ? $seederModules[$moduleName] : array();
        foreach ($actionMatches[1] as $actionName) {
            if (!in_array($actionName, $seededActions, true)) {
                $missingActions[] = "{$moduleName}.{$actionName}";
            }
        }
    }

    if (empty($missingActions)) {
        $print('OK', 'Todas las acciones de controladores tienen permiso en PermissionsSeeder.');
    } else {
        foreach ($missingActions as $ma) {
            $print('WARN', "Accion sin permiso en PermissionsSeeder: {$ma}");
        }
    }
} else {
    $print('WARN', 'PermissionsSeeder.php no encontrado; no se pudo verificar gate de permisos.');
}

echo str_repeat('-', 60) . "\n";
echo "Resumen actualizado: errors={$errors} warnings={$warnings}\n";

if ($strictMode) {
    echo "Modo: strict\n";
} else {
    echo "Modo: normal\n";
}

if ($errors > 0) {
    exit(2);
}
if ($warnings > 0 && $strictMode) {
    exit(1);
}
exit(0);
