#!/usr/bin/env php
<?php
/**
 * Klee Smoke Tests (HTTP)
 *
 * Uso:
 *   php bin/smoke
 *   php bin/smoke --base-url=http://127.0.0.1:8000 --user=admin --pass=admin123
 *   php bin/smoke --json
 *   php bin/smoke --help
 */

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

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

// Cargar configuración de módulos para construir rutas dinámicas de smoke.
if (is_file($basePath . '/app/config/Config.php')) {
    require_once $basePath . '/app/config/Config.php';
}
if (is_file($basePath . '/app/config/ConfigEnv.php')) {
    require_once $basePath . '/app/config/ConfigEnv.php';
}
if (is_file($basePath . '/app/config/Modules.php')) {
    require_once $basePath . '/app/config/Modules.php';
}

$args = array_slice($argv, 1);
$jsonOutput = in_array('--json', $args, true);

if (!function_exists('smoke_exit')) {
    function smoke_exit($exitCode, $payload, $jsonOutput)
    {
        if ($jsonOutput) {
            echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . PHP_EOL;
        } else {
            if (isset($payload['message']) && is_string($payload['message']) && $payload['message'] !== '') {
                fwrite(STDERR, $payload['message'] . PHP_EOL);
            }
        }
        exit((int)$exitCode);
    }
}

if (in_array('--help', $args, true) || in_array('-h', $args, true)) {
    echo "\nKlee Smoke Tests\n";
    echo "Uso:\n";
    echo "  php bin/smoke\n";
    echo "  php bin/smoke --base-url=http://127.0.0.1:8000 --user=admin --pass=admin123\n";
    echo "  php bin/smoke --json\n";
    echo "Opciones:\n";
    echo "  --base-url=URL     Base URL de la app (ej: http://127.0.0.1:8000 o .../index.php)\n";
    echo "  --user=USUARIO     Usuario para login smoke\n";
    echo "  --pass=CLAVE       Clave para login smoke\n";
    echo "  --timeout=SEG      Timeout por request (default: 15)\n\n";
    echo "  --json             Salida en JSON (útil para CI)\n";
    echo "  --json-file=PATH   Guarda además el reporte JSON en archivo\n\n";
    exit(0);
}

if (!extension_loaded('curl')) {
    smoke_exit(2, array(
        'ok' => false,
        'message' => '[error] Extensión curl no disponible. Instálala para ejecutar smoke tests HTTP.',
    ), $jsonOutput);
}

$toBool = function ($value) {
    if (is_bool($value)) {
        return $value;
    }

    $value = strtolower(trim((string)$value));
    return in_array($value, array('1', 'true', 'yes', 'on'), true);
};

$options = array(
    'base-url' => getenv('SMOKE_BASE_URL') !== false ? trim((string)getenv('SMOKE_BASE_URL')) : 'http://127.0.0.1:8000',
    'user' => getenv('SMOKE_USER') !== false ? trim((string)getenv('SMOKE_USER')) : 'admin',
    'pass' => getenv('SMOKE_PASS') !== false ? (string)getenv('SMOKE_PASS') : 'admin123',
    'timeout' => getenv('SMOKE_TIMEOUT') !== false ? (int)getenv('SMOKE_TIMEOUT') : 15,
    'json' => getenv('SMOKE_JSON') !== false ? $toBool(getenv('SMOKE_JSON')) : $jsonOutput,
    'json-file' => getenv('SMOKE_JSON_FILE') !== false ? trim((string)getenv('SMOKE_JSON_FILE')) : '',
);

foreach ($args as $arg) {
    if (strpos($arg, '--base-url=') === 0) {
        $options['base-url'] = substr($arg, 11);
    } elseif (strpos($arg, '--user=') === 0) {
        $options['user'] = substr($arg, 7);
    } elseif (strpos($arg, '--pass=') === 0) {
        $options['pass'] = substr($arg, 7);
    } elseif (strpos($arg, '--timeout=') === 0) {
        $options['timeout'] = (int)substr($arg, 10);
    } elseif ($arg === '--json') {
        $options['json'] = true;
    } elseif (strpos($arg, '--json-file=') === 0) {
        $options['json-file'] = trim((string)substr($arg, 12));
    }
}

$baseUrl = trim((string)$options['base-url']);
$user = (string)$options['user'];
$pass = (string)$options['pass'];
$timeout = max(3, (int)$options['timeout']);
$jsonOutput = !empty($options['json']);
$jsonFile = (string)$options['json-file'];

if ($baseUrl === '') {
    smoke_exit(2, array(
        'ok' => false,
        'message' => '[error] base-url vacío',
    ), $jsonOutput);
}

$cookieJar = tempnam(sys_get_temp_dir(), 'klee_smoke_');
if ($cookieJar === false) {
    smoke_exit(2, array(
        'ok' => false,
        'message' => '[error] No fue posible crear archivo temporal para cookies.',
    ), $jsonOutput);
}

register_shutdown_function(function () use ($cookieJar) {
    if (is_string($cookieJar) && is_file($cookieJar)) {
        @unlink($cookieJar);
    }
});

$buildUrl = function ($params) use ($baseUrl) {
    $query = http_build_query($params);
    if (strpos($baseUrl, '?') !== false) {
        return $baseUrl . '&' . $query;
    }

    if (preg_match('/\.php\/?$/i', $baseUrl)) {
        return rtrim($baseUrl, '/') . '?' . $query;
    }

    return rtrim($baseUrl, '/') . '/?' . $query;
};

$request = function ($method, $url, $postFields = null) use ($cookieJar, $timeout) {
    $start = microtime(true);

    $ch = curl_init($url);
    curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => true,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_COOKIEJAR => $cookieJar,
        CURLOPT_COOKIEFILE => $cookieJar,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_USERAGENT => 'klee-smoke/1.0',
    ));

    if (strtoupper($method) === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($postFields) ? http_build_query($postFields) : (string)$postFields);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
    }

    $raw = curl_exec($ch);
    $err = curl_error($ch);
    $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $headerSize = (int)curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $totalTimeMs = (int)round((microtime(true) - $start) * 1000);
    curl_close($ch);

    if ($raw === false) {
        return array(
            'ok' => false,
            'status' => 0,
            'headers' => '',
            'body' => '',
            'error' => $err !== '' ? $err : 'curl_exec failed',
            'time_ms' => $totalTimeMs,
        );
    }

    $headers = substr($raw, 0, $headerSize);
    $body = substr($raw, $headerSize);

    return array(
        'ok' => true,
        'status' => $status,
        'headers' => $headers,
        'body' => $body,
        'error' => '',
        'time_ms' => $totalTimeMs,
    );
};

$extractCsrf = function ($html) {
    if (preg_match('/name="_csrf_token"\s+value="([^"]+)"/i', $html, $m)) {
        return $m[1];
    }
    return '';
};

$containsAny = function ($body, $needles) {
    foreach ($needles as $needle) {
        if ($needle !== '' && stripos($body, $needle) !== false) {
            return true;
        }
    }
    return false;
};

$buildProtectedRoutes = function () use ($basePath) {
    $routes = array(
        'GET home/index' => array('c' => 'home', 'a' => 'index'),
    );
    $seen = array('home|index' => true);

    $addRoute = function ($controller, $action, $extraParams = array()) use (&$routes, &$seen) {
        $controller = trim((string)$controller);
        $action = trim((string)$action);
        if ($controller === '') {
            return;
        }

        $actionForKey = ($action !== '') ? strtolower($action) : '';
        $routeKey = strtolower($controller) . '|' . $actionForKey;
        if (isset($seen[$routeKey])) {
            return;
        }

        $labelAction = ($action !== '') ? strtolower($action) : 'index';
        $label = 'GET ' . strtolower($controller) . '/' . $labelAction;

        $params = array('c' => $controller);
        if ($action !== '') {
            $params['a'] = $action;
        }

        foreach ($extraParams as $key => $value) {
            $params[(string)$key] = $value;
        }

        $routes[$label] = $params;
        $seen[$routeKey] = true;
    };

    $isReadLikeAction = function ($action) {
        $action = strtolower(trim((string)$action));
        if ($action === '' || strpos($action, '__') === 0) {
            return false;
        }

        $blockedPatterns = array(
            'save', 'store', 'update', 'delete', 'destroy', 'remove', 'insert',
            'createpost', 'editpost', 'post', 'ajax', 'validate', 'logout',
            'approve', 'reject', 'send', 'import', 'export', 'download',
            'upload', 'subir', 'sync', 'run', 'execute', 'migrate', 'seed',
            'boarddata', 'events', 'lookup', 'quickcreate', 'detail',
            'slots', 'resources', 'schema', 'history', 'fromparameters',
            'getmodelschema', 'testmodel',
            'availability', 'dataapi',
        );

        foreach ($blockedPatterns as $pattern) {
            if (strpos($action, $pattern) !== false) {
                return false;
            }
        }

        return true;
    };

    $needsEntityId = function ($action) {
        $action = strtolower(trim((string)$action));
        return in_array($action, array('edit', 'view', 'show', 'detalle', 'detail'), true);
    };

    $controllerFiles = array();
    $controllerGlob = glob($basePath . '/app/controllers/*Controller.php');
    if (is_array($controllerGlob)) {
        foreach ($controllerGlob as $controllerFile) {
            $fileName = basename($controllerFile);
            if (!preg_match('/^(.+)Controller\.php$/i', $fileName, $m)) {
                continue;
            }

            $controllerFiles[strtolower($m[1])] = $controllerFile;
        }
    }

    $extractControllerActions = function ($controller) use ($controllerFiles, $isReadLikeAction) {
        $actions = array();
        $lookupKey = strtolower((string)$controller);
        if (!isset($controllerFiles[$lookupKey])) {
            return $actions;
        }

        $content = @file_get_contents($controllerFiles[$lookupKey]);
        if (!is_string($content) || $content === '') {
            return $actions;
        }

        if (!preg_match_all('/public\s+function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/', $content, $matches)) {
            return $actions;
        }

        foreach ($matches[1] as $methodName) {
            $methodName = trim((string)$methodName);
            if ($methodName === '' || strpos($methodName, '__') === 0) {
                continue;
            }

            // Klee convention: only methods ending in 'Action' are routable.
            if (!preg_match('/Action$/', $methodName)) {
                continue;
            }

            $normalizedAction = preg_replace('/action$/i', '', $methodName);
            $normalizedAction = is_string($normalizedAction) ? trim($normalizedAction) : '';
            if ($normalizedAction === '') {
                continue;
            }

            if (!$isReadLikeAction($normalizedAction)) {
                continue;
            }

            $actions[] = $normalizedAction;
        }

        return array_values(array_unique($actions));
    };

    if (!class_exists('Modules')) {
        return $routes;
    }

    $registry = Modules::getRegistry();
    if (!is_array($registry)) {
        return $routes;
    }

    foreach ($registry as $moduleName => $module) {
        if (empty($module['enabled']) || empty($module['controller'])) {
            continue;
        }

        $controller = trim((string)$module['controller']);
        if ($controller === '') {
            continue;
        }

        if (!empty($module['menu']) && is_array($module['menu'])) {
            $menuAction = isset($module['menu']['action']) ? trim((string)$module['menu']['action']) : 'list';
            $menuExtra = $needsEntityId($menuAction) ? array('Id' => 1, 'id' => 1) : array();
            $addRoute($controller, $menuAction, $menuExtra);
        }

        if (!empty($module['quick_actions']) && is_array($module['quick_actions'])) {
            foreach ($module['quick_actions'] as $quickAction) {
                if (empty($quickAction['path']) || !is_array($quickAction['path'])) {
                    continue;
                }

                $quickController = isset($quickAction['path']['controller']) ? trim((string)$quickAction['path']['controller']) : '';
                $quickActionName = isset($quickAction['path']['action']) ? trim((string)$quickAction['path']['action']) : '';
                if ($quickController === '' || $quickActionName === '') {
                    continue;
                }

                $quickExtra = $needsEntityId($quickActionName) ? array('Id' => 1, 'id' => 1) : array();
                $addRoute($quickController, $quickActionName, $quickExtra);
            }
        }

        $controllerActions = $extractControllerActions($controller);
        foreach ($controllerActions as $controllerAction) {
            $extra = $needsEntityId($controllerAction) ? array('Id' => 1, 'id' => 1) : array();
            $addRoute($controller, $controllerAction, $extra);
        }
    }

    return $routes;
};

$tests = array();
$failures = 0;

$record = function ($name, $ok, $detail, $ms) use (&$tests, &$failures) {
    $tests[] = array('name' => $name, 'ok' => $ok, 'detail' => $detail, 'ms' => $ms);
    if (!$ok) {
        $failures++;
    }
};

$startedAt = date('c');

if (!$jsonOutput) {
    echo "\nKlee Smoke Tests\n";
    echo str_repeat('-', 70) . "\n";
    echo "Base URL: {$baseUrl}\n";
    echo "Usuario: {$user}\n";
    echo str_repeat('-', 70) . "\n";
}

// 1) Login page
$loginUrl = $buildUrl(array('c' => 'login', 'a' => 'admin'));
$loginGet = $request('GET', $loginUrl);
if (!$loginGet['ok']) {
    $record('GET login', false, 'No se pudo conectar: ' . $loginGet['error'], $loginGet['time_ms']);
} else {
    $loginFormOk = ($loginGet['status'] === 200) && $containsAny($loginGet['body'], array('name="user"', 'Iniciar sesión'));
    $record('GET login', $loginFormOk, 'status=' . $loginGet['status'], $loginGet['time_ms']);
}

$csrfToken = $extractCsrf($loginGet['body'] ?? '');

// 2) Login POST
$loginPostUrl = $buildUrl(array('c' => 'login', 'a' => 'validate'));
$payload = array(
    '_csrf_token' => $csrfToken,
    'user' => $user,
    'pass' => $pass,
);
$loginPost = $request('POST', $loginPostUrl, $payload);
if (!$loginPost['ok']) {
    $record('POST login', false, 'Error HTTP: ' . $loginPost['error'], $loginPost['time_ms']);
} else {
    $isRedirect = in_array($loginPost['status'], array(301, 302, 303), true);
    $goesHome = stripos($loginPost['headers'], 'Location:') !== false && stripos($loginPost['headers'], 'c=home') !== false;
    $loginOk = $isRedirect && $goesHome;
    $record('POST login', $loginOk, 'status=' . $loginPost['status'], $loginPost['time_ms']);
}

// 3) Protected routes with authenticated cookie
$protectedRoutes = $buildProtectedRoutes();

if (!$jsonOutput) {
    echo 'Rutas protegidas a validar: ' . count($protectedRoutes) . "\n";
    echo str_repeat('-', 70) . "\n";
}

foreach ($protectedRoutes as $label => $params) {
    $routeUrl = $buildUrl($params);
    $res = $request('GET', $routeUrl);

    if (!$res['ok']) {
        $record($label, false, 'Error HTTP: ' . $res['error'], $res['time_ms']);
        continue;
    }

    $statusOk = ($res['status'] === 200);
    $isRedirect = in_array($res['status'], array(301, 302, 303), true);
    $redirectsToLogin = stripos($res['headers'], 'Location:') !== false && stripos($res['headers'], 'c=login') !== false;
    $hasLoginFields = $containsAny($res['body'], array('name="user"', 'name="pass"'));
    $hasLoginMarkers = $containsAny($res['body'], array('Iniciar sesión', 'c=login', 'a=validate'));
    $isLoginScreen = $hasLoginFields && $hasLoginMarkers;
    $notLoginScreen = !$isLoginScreen;
    $ok = ($isRedirect && !$redirectsToLogin) || ($statusOk && $notLoginScreen);
    $detail = 'status=' . $res['status'];
    $record($label, $ok, $detail, $res['time_ms']);
}

if (!$jsonOutput) {
    foreach ($tests as $item) {
        $icon = $item['ok'] ? '[ok]' : '[fail]';
        echo sprintf("%s %-22s %6dms  %s\n", $icon, $item['name'], (int)$item['ms'], $item['detail']);
    }

    echo str_repeat('-', 70) . "\n";
    echo 'Resumen: total=' . count($tests) . ' fail=' . $failures . "\n";
}

$exitCode = ($failures > 0) ? 1 : 0;
$report = array(
    'ok' => $exitCode === 0,
    'base_url' => $baseUrl,
    'user' => $user,
    'started_at' => $startedAt,
    'ended_at' => date('c'),
    'summary' => array(
        'total' => count($tests),
        'failed' => $failures,
        'passed' => count($tests) - $failures,
    ),
    'tests' => $tests,
    'exit_code' => $exitCode,
);

if ($jsonFile !== '') {
    $jsonDir = dirname($jsonFile);
    if (!is_dir($jsonDir)) {
        @mkdir($jsonDir, 0755, true);
    }
    @file_put_contents($jsonFile, json_encode($report, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . PHP_EOL);
}

if ($jsonOutput) {
    echo json_encode($report, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . PHP_EOL;
}

exit($exitCode);
