<?php
if(!defined('access') || access !== 'admincp') die();
NexoraRbac::requirePermission('system.view');

function nexoraSecurityEsc($value) {
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

function nexoraSecurityLabel($kind, $text) {
    $classes = array('ok'=>'success','warn'=>'warning','danger'=>'danger','info'=>'info','neutral'=>'default');
    $class = isset($classes[$kind]) ? $classes[$kind] : 'default';
    return '<span class="label label-'.$class.'">'.nexoraSecurityEsc($text).'</span>';
}

function nexoraSecurityModuleActive($filename) {
    $path = __PATH_CONFIGS__.'modules/'.$filename;
    if(!is_file($path) || !is_readable($path)) return false;
    libxml_use_internal_errors(true);
    $xml = simplexml_load_file($path, 'SimpleXMLElement', LIBXML_NONET);
    libxml_clear_errors();
    return $xml && isset($xml->active) && (string)$xml->active === '1';
}

function nexoraSecurityLocalCronHealth() {
    $now = time();
    $result = array('healthy'=>true, 'realms'=>array());
    foreach(NexoraRealm::ids() as $realmId) {
        $state = function_exists('nexoraCronStateLoad') ? nexoraCronStateLoad($realmId) : array();
        $latest = 0;
        if(is_array($state)) foreach($state as $timestamp) if((int)$timestamp > $latest) $latest = (int)$timestamp;
        $fresh = $latest > 0 && ($now - $latest) <= 900;
        $result['realms'][$realmId] = array('latest'=>$latest, 'fresh'=>$fresh);
        if(!$fresh) $result['healthy'] = false;
    }
    return $result;
}

function nexoraSecurityPlatformProbe() {
    $result = array(
        'enabled'=>NexoraRealm::platformEnabled(), 'database_exists'=>false, 'ready'=>false,
        'connected'=>false, 'found'=>array(), 'missing'=>array(), 'error'=>false,
    );
    $dbName = NexoraRealm::platformDatabase();
    if(!preg_match('/^[A-Za-z0-9_]+$/D', $dbName)) {
        $result['error'] = true;
        return $result;
    }
    $required = array(
        'WEBENGINE_NEWS','WEBENGINE_CREDITS_CONFIG','WEBENGINE_PAYPAL_TRANSACTIONS','WEBENGINE_PLUGINS','WEBENGINE_CRON',
        'NEXORA_PASSWORD_RESETS','NEXORA_EMAIL_CHANGE_REQUESTS','NEXORA_AUDIT_LOG','NEXORA_PAYMENT_EVENTS',
        'NEXORA_WALLET_LEDGER','NEXORA_REALM_CONFIG','NEXORA_ROLES','NEXORA_ROLE_PERMISSIONS','NEXORA_USER_ROLES'
    );
    try {
        $probeDb = Connection::Database('MuOnline', 'legacy');
        $exists = $probeDb->query_fetch_single('SELECT DB_ID(?) AS nexora_db_id', array($dbName));
        $result['database_exists'] = is_array($exists) && isset($exists['nexora_db_id']) && $exists['nexora_db_id'] !== null;
        if($result['database_exists']) {
            foreach($required as $table) {
                $qualified = $dbName.'.dbo.'.$table;
                $row = $probeDb->query_fetch_single("SELECT OBJECT_ID(?, 'U') AS nexora_object_id", array($qualified));
                if(is_array($row) && !empty($row['nexora_object_id'])) $result['found'][] = $table;
                else $result['missing'][] = $table;
            }
        } else {
            $result['missing'] = $required;
        }
        if($result['enabled']) {
            $platformDb = Connection::Database('Platform');
            $test = $platformDb->query_fetch_single('SELECT 1 AS nexora_connection_test');
            $result['connected'] = is_array($test) && isset($test['nexora_connection_test']);
        }
    } catch(Throwable $ex) {
        $result['error'] = true;
    }
    $result['ready'] = $result['database_exists'] && empty($result['missing']);
    return $result;
}

function nexoraSecurityLocalAudit($limit=25) {
    $path = __PATH_LOGS__.'admin_audit.log';
    if(!is_file($path) || !is_readable($path)) return array();
    $lines = @file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if(!is_array($lines)) return array();
    $lines = array_slice($lines, -max(1,(int)$limit));
    $result = array();
    foreach(array_reverse($lines) as $line) {
        $row = json_decode($line, true);
        if(!is_array($row)) continue;
        $result[] = array(
            'created_at'=>isset($row['at']) ? $row['at'] : '',
            'actor'=>isset($row['actor']) ? $row['actor'] : '',
            'realm'=>isset($row['realm']) ? $row['realm'] : '',
            'action'=>isset($row['action']) ? $row['action'] : '',
            'target_type'=>isset($row['target_type']) ? $row['target_type'] : '',
            'target_id'=>isset($row['target_id']) ? $row['target_id'] : '',
            'ip_address'=>isset($row['ip']) ? $row['ip'] : '',
        );
        if(count($result) >= $limit) break;
    }
    return $result;
}

function nexoraSecurityWriteWebengineFlag($key, $value) {
    $path = __PATH_CONFIGS__.'webengine.json';
    $raw = @file_get_contents($path);
    $data = $raw !== false ? json_decode($raw, true) : null;
    if(!is_array($data)) throw new RuntimeException('webengine.json no pudo abrirse de forma segura.');
    $data[$key] = $value;
    $encoded = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    if($encoded === false) throw new RuntimeException('No se pudo serializar webengine.json.');
    if(function_exists('nexoraAtomicWrite')) {
        if(!nexoraAtomicWrite($path, $encoded."\n")) throw new RuntimeException('No se pudo actualizar webengine.json.');
    } elseif(@file_put_contents($path, $encoded."\n", LOCK_EX) === false) {
        throw new RuntimeException('No se pudo actualizar webengine.json.');
    }
}

$message = isset($_SESSION['nexora_security_flash_message']) ? (string)$_SESSION['nexora_security_flash_message'] : null;
if(isset($_SESSION['nexora_security_flash_message'])) unset($_SESSION['nexora_security_flash_message']);
$error = null;
$localCron = nexoraSecurityLocalCronHealth();

try {
    if(($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && isset($_POST['nexora_security_action'])) {
        NexoraRbac::requirePermission('system.manage');
        $action = (string)$_POST['nexora_security_action'];
        if($action === 'generate_payment_key') {
            $current = NexoraSecurity::secretInfo('NEXORA_PAYMENT_SIGNING_KEY');
            if($current['strong']) throw new RuntimeException('La firma de pagos ya utiliza una clave segura; no se rotó.');
            NexoraSecurity::generateManagedSecret('NEXORA_PAYMENT_SIGNING_KEY');
            NexoraAudit::log('security.payment_key_generated', 'security', 'payment_signing_key');
            $_SESSION['nexora_security_flash_message'] = 'Clave privada de firma de pagos creada en el archivo privado del servidor. No se muestra ni se exporta.';
        } elseif($action === 'generate_cron_key') {
            $current = NexoraSecurity::secretInfo('NEXORA_CRON_API_KEY', 'cron_api_key');
            if($current['strong']) throw new RuntimeException('Cron API ya utiliza una clave segura; no se rotó.');
            $generated = NexoraSecurity::generateManagedSecret('NEXORA_CRON_API_KEY');
            $_SESSION['nexora_security_once_cron_key'] = $generated;
            NexoraAudit::log('security.cron_api_key_generated', 'security', 'cron_api_key');
            $_SESSION['nexora_security_flash_message'] = 'Clave Cron API generada. Cópiala del aviso de una sola visualización antes de salir de esta página.';
        } elseif($action === 'disable_cron_api') {
            if(!$localCron['healthy']) throw new RuntimeException('No se desactivó la API: primero confirma que el cron local tenga ejecución reciente en ambos reinos.');
            nexoraSecurityWriteWebengineFlag('cron_api', false);
            NexoraAudit::log('security.cron_api_disabled', 'security', 'cron_api');
            $_SESSION['nexora_security_flash_message'] = 'Cron API pública desactivada. El cron local por reino continúa operativo.';
        } else {
            throw new RuntimeException('Acción de seguridad no reconocida.');
        }
        header('Location: '.admincp_base('security_overview'));
        exit;
    }
} catch(Throwable $ex) {
    $error = $ex->getMessage();
}

$csrfOk = NexoraSecurity::config('csrf_enabled', true) && strlen(NexoraSecurity::csrfToken()) >= 64;
$headersOk = NexoraSecurity::expectedHeadersPresent();
$proxy = NexoraSecurity::proxyTrustStatus();
$paymentActive = nexoraSecurityModuleActive('donation.xml') && nexoraSecurityModuleActive('donation.paypal.xml');
$paymentSecret = NexoraSecurity::secretInfo('NEXORA_PAYMENT_SIGNING_KEY');
$cronApiEnabled = (bool)config('cron_api', true);
$cronSecret = NexoraSecurity::secretInfo('NEXORA_CRON_API_KEY', 'cron_api_key');
$platform = nexoraSecurityPlatformProbe();
$passwordMode = strtolower((string)config('SQL_PASSWORD_ENCRYPTION', true));

$triggerState = 'No detectado';
try {
    $legacy = Connection::Database('MuOnline', 'legacy');
    $trigger = $legacy->query_fetch_single("SELECT TOP 1 is_disabled FROM sys.triggers WHERE name = ?", array('TR_Copy_WebAccount_To_muonlinetemp'));
    if(is_array($trigger)) $triggerState = ((int)$trigger['is_disabled'] === 1 ? 'Deshabilitado' : 'Activo');
} catch(Throwable $ignored) {}

$localAuditPath = __PATH_LOGS__.'admin_audit.log';
$localAuditWritable = is_file($localAuditPath) ? is_writable($localAuditPath) : is_writable(__PATH_LOGS__);
$blockers = array();
if(!$csrfOk) $blockers[] = 'CSRF';
if(!$headersOk) $blockers[] = 'security_headers';
if(!$proxy['safe']) $blockers[] = 'proxy_headers';
if($paymentActive && !$paymentSecret['strong']) $blockers[] = 'payment_signing_key';
if($cronApiEnabled && !$cronSecret['strong']) $blockers[] = 'cron_api_key';
if($platform['enabled'] && (!$platform['ready'] || !$platform['connected'])) $blockers[] = 'platform';
if(!$localAuditWritable && !($platform['enabled'] && $platform['connected'])) $blockers[] = 'audit';
$ready = count($blockers) === 0;

$recentAudit = array();
$auditMode = 'Local';
if($platform['enabled'] && $platform['ready'] && $platform['connected']) {
    try {
        $pdb = Connection::Database('Platform');
        $rows = $pdb->query_fetch("SELECT TOP 25 actor, realm, action, target_type, target_id, ip_address, created_at FROM NEXORA_AUDIT_LOG ORDER BY id DESC");
        if(is_array($rows)) $recentAudit = $rows;
        $auditMode = 'Platform';
    } catch(Throwable $ignored) {}
}
if(empty($recentAudit)) {
    $recentAudit = nexoraSecurityLocalAudit(25);
    $auditMode = 'Local';
}

$onceCronKey = isset($_SESSION['nexora_security_once_cron_key']) ? (string)$_SESSION['nexora_security_once_cron_key'] : '';
if($onceCronKey !== '') unset($_SESSION['nexora_security_once_cron_key']);
?>
<h1 class="page-header">Nexora · Security &amp; Audit</h1>
<div class="alert alert-info">Centro de validación operativo. Distingue controles obligatorios, compatibilidad heredada y componentes opcionales sin revelar secretos. Contexto: <strong><?php echo nexoraSecurityEsc(NexoraRealm::label(NexoraRealm::operationalRealm())); ?></strong>.</div>
<?php if($message) echo '<div class="alert alert-success">'.nexoraSecurityEsc($message).'</div>'; ?>
<?php if($error) echo '<div class="alert alert-danger">'.nexoraSecurityEsc($error).'</div>'; ?>
<?php if($onceCronKey !== '') { ?>
<div class="alert alert-warning">
    <strong>Clave Cron API · visualización única</strong>
    <p style="margin:7px 0 5px;">Cópiala ahora al scheduler externo si decides mantener la API pública. Después de recargar, Nexora solo mostrará su huella.</p>
    <pre style="white-space:pre-wrap;word-break:break-all;margin:0;"><?php echo nexoraSecurityEsc($onceCronKey); ?></pre>
</div>
<?php } ?>

<div class="row">
    <div class="col-md-8">
        <div class="panel panel-<?php echo $ready ? 'success' : 'warning'; ?>">
            <div class="panel-heading"><strong>Readiness de producción</strong></div>
            <div class="panel-body">
                <div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;">
                    <span class="label label-<?php echo $ready ? 'success' : 'warning'; ?>" style="font-size:13px;padding:8px 12px;"><?php echo $ready ? 'CONTROLES ESENCIALES LISTOS' : count($blockers).' CONTROL(ES) POR CERRAR'; ?></span>
                    <span class="text-muted">Platform, password legacy y trigger cruzado se evalúan aparte porque no todos son bloqueantes.</span>
                </div>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading"><strong>Auditoría efectiva</strong></div>
            <div class="panel-body"><strong><?php echo nexoraSecurityEsc($auditMode); ?></strong><br><span class="text-muted">El registro local permanece activo aunque Platform no esté habilitado.</span></div>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-md-7">
        <div class="panel panel-default">
            <div class="panel-heading"><strong>Controles esenciales</strong></div>
            <div class="panel-body table-responsive">
                <table class="table table-condensed table-striped"><tbody>
                    <tr><th>CSRF</th><td><?php echo $csrfOk ? nexoraSecurityLabel('ok','Runtime OK') : nexoraSecurityLabel('danger','Fallo'); ?></td><td class="text-muted">POST web/AdminCP protegido por token de sesión.</td></tr>
                    <tr><th>Security headers</th><td><?php echo $headersOk ? nexoraSecurityLabel('ok','Runtime OK') : nexoraSecurityLabel('danger','Revisar'); ?></td><td class="text-muted">Nosniff, frame policy, referrer, permissions y frame-ancestors.</td></tr>
                    <tr><th>Headers de proxy</th><td><?php echo nexoraSecurityLabel($proxy['safe'] ? 'ok' : 'danger', $proxy['label']); ?></td><td class="text-muted">Desactivados es un estado seguro; nunca se confía en X-Forwarded-For sin allowlist.</td></tr>
                    <tr><th>Firma de pagos</th><td><?php
                        if(!$paymentActive) echo nexoraSecurityLabel('neutral','No aplica');
                        elseif($paymentSecret['strong']) echo nexoraSecurityLabel('ok','Protegida');
                        else echo nexoraSecurityLabel('danger','Requerida');
                    ?></td><td>
                        <?php if($paymentSecret['strong']) { ?><span class="text-muted">Fuente: <?php echo nexoraSecurityEsc($paymentSecret['source']); ?> · huella <?php echo nexoraSecurityEsc($paymentSecret['fingerprint']); ?></span>
                        <?php } elseif($paymentActive) { ?><form method="post" style="margin:0;"><input type="hidden" name="nexora_security_action" value="generate_payment_key"><button class="btn btn-xs btn-primary" type="submit">Generar clave privada</button></form><?php } ?>
                    </td></tr>
                    <tr><th>Cron</th><td><?php
                        if(!$cronApiEnabled) echo nexoraSecurityLabel($localCron['healthy'] ? 'ok' : 'warn', $localCron['healthy'] ? 'API cerrada · local OK' : 'API cerrada · revisar cron local');
                        elseif($cronSecret['strong']) echo nexoraSecurityLabel('ok','API protegida');
                        else echo nexoraSecurityLabel('danger','API con clave débil');
                    ?></td><td>
                        <?php if($cronApiEnabled && !$cronSecret['strong']) { ?>
                            <div style="display:flex;gap:6px;flex-wrap:wrap;">
                                <form method="post" style="margin:0;"><input type="hidden" name="nexora_security_action" value="generate_cron_key"><button class="btn btn-xs btn-primary" type="submit">Generar key API</button></form>
                                <?php if($localCron['healthy']) { ?><form method="post" style="margin:0;"><input type="hidden" name="nexora_security_action" value="disable_cron_api"><button class="btn btn-xs btn-default" type="submit">Cerrar API pública</button></form><?php } ?>
                            </div>
                        <?php } elseif($cronSecret['strong']) { ?><span class="text-muted">Fuente: <?php echo nexoraSecurityEsc($cronSecret['source']); ?> · huella <?php echo nexoraSecurityEsc($cronSecret['fingerprint']); ?></span><?php } ?>
                    </td></tr>
                    <tr><th>Audit log</th><td><?php echo $localAuditWritable || ($platform['enabled'] && $platform['connected']) ? nexoraSecurityLabel('ok',$auditMode.' activo') : nexoraSecurityLabel('danger','Sin escritura'); ?></td><td class="text-muted">Las acciones sensibles siguen auditadas incluso sin Platform.</td></tr>
                </tbody></table>
            </div>
        </div>
    </div>
    <div class="col-md-5">
        <div class="panel panel-default">
            <div class="panel-heading"><strong>Compatibilidad y arquitectura</strong></div>
            <div class="panel-body table-responsive">
                <table class="table table-condensed"><tbody>
                    <tr><th>Nexora Platform</th><td><?php
                        if($platform['enabled'] && $platform['ready'] && $platform['connected']) echo nexoraSecurityLabel('ok','Activa');
                        elseif($platform['enabled']) echo nexoraSecurityLabel('danger','Activada incompleta');
                        elseif($platform['ready']) echo nexoraSecurityLabel('info','Preparada · apagada');
                        else echo nexoraSecurityLabel('neutral','Opcional · no instalada');
                    ?></td></tr>
                    <tr><th>Password GameServer</th><td><?php echo $passwordMode === 'none' ? nexoraSecurityLabel('warn','Legacy compatible') : nexoraSecurityLabel('ok',strtoupper($passwordMode)); ?></td></tr>
                    <tr><th>Registro compartido</th><td><?php echo NexoraRealm::sharedRegistrationEnabled() ? nexoraSecurityLabel('ok','PHP transaccional') : nexoraSecurityLabel('neutral','Desactivado'); ?></td></tr>
                    <tr><th>Trigger Legado → Seasonal</th><td><?php
                        if($triggerState === 'Deshabilitado') echo nexoraSecurityLabel('ok','Deshabilitado');
                        elseif($triggerState === 'Activo') echo nexoraSecurityLabel('warn','Compatibilidad activa');
                        else echo nexoraSecurityLabel('neutral',$triggerState);
                    ?></td></tr>
                </tbody></table>
                <hr>
                <p class="small text-muted"><strong>Platform no es requisito para que la web sea segura.</strong> Es una migración arquitectónica que centraliza contenido, pagos, roles y auditoría. Solo debe activarse después de ejecutar y validar <code>sql/001_nexora_platform.sql</code> + <code>sql/004_platform_validation.sql</code>.</p>
                <?php if($platform['database_exists'] && !$platform['ready']) { ?><p class="small text-warning">La base Platform fue detectada pero faltan <?php echo count($platform['missing']); ?> objetos requeridos. No actives <code>NEXORA_PLATFORM_ENABLED</code> todavía.</p><?php } ?>
                <?php if($passwordMode === 'none') { ?><p class="small text-warning">El modo <code>none</code> no se cambia desde esta pantalla: primero hay que verificar qué esquema acepta el GameServer. Cambiarlo a ciegas puede impedir el login.</p><?php } ?>
                <?php if($triggerState === 'Activo') { ?><p class="small text-warning">El trigger permanece como puente de compatibilidad. Cuando cierres una reconciliación de cuentas y confirmes el alta PHP en ambos reinos, puedes ejecutar <code>sql/002_disable_legacy_account_sync_trigger.sql</code>.</p><?php } ?>
            </div>
        </div>
    </div>
</div>

<div class="panel panel-default">
    <div class="panel-heading"><strong>Cron local por reino</strong></div>
    <div class="panel-body">
        <div class="row">
        <?php foreach($localCron['realms'] as $realmId=>$cronInfo) { ?>
            <div class="col-sm-6"><strong><?php echo nexoraSecurityEsc(NexoraRealm::label($realmId)); ?></strong> · <?php echo $cronInfo['fresh'] ? nexoraSecurityLabel('ok','Reciente') : nexoraSecurityLabel('warn','Sin ejecución reciente'); ?> <span class="text-muted"><?php echo $cronInfo['latest'] ? date('Y/m/d H:i:s',$cronInfo['latest']) : 'sin estado'; ?></span></div>
        <?php } ?>
        </div>
    </div>
</div>

<div class="panel panel-default">
    <div class="panel-heading"><strong>Auditoría reciente · <?php echo nexoraSecurityEsc($auditMode); ?></strong></div>
    <div class="panel-body">
    <?php if(empty($recentAudit)) { ?>
        <p class="text-muted">Todavía no hay eventos registrados o el registro no es legible.</p>
    <?php } else { ?>
        <div class="table-responsive"><table class="table table-condensed table-striped"><thead><tr><th>Fecha UTC</th><th>Actor</th><th>Realm</th><th>Acción</th><th>Objetivo</th><th>IP</th></tr></thead><tbody>
        <?php foreach($recentAudit as $row) { ?>
            <tr>
                <td><?php echo nexoraSecurityEsc(isset($row['created_at']) ? $row['created_at'] : ''); ?></td>
                <td><?php echo nexoraSecurityEsc(isset($row['actor']) ? $row['actor'] : ''); ?></td>
                <td><?php echo nexoraSecurityEsc(isset($row['realm']) ? $row['realm'] : ''); ?></td>
                <td><code><?php echo nexoraSecurityEsc(isset($row['action']) ? $row['action'] : ''); ?></code></td>
                <td><?php echo nexoraSecurityEsc(trim((string)(isset($row['target_type']) ? $row['target_type'] : '').' '.(string)(isset($row['target_id']) ? $row['target_id'] : ''))); ?></td>
                <td><?php echo nexoraSecurityEsc(isset($row['ip_address']) ? $row['ip_address'] : ''); ?></td>
            </tr>
        <?php } ?>
        </tbody></table></div>
    <?php } ?>
    </div>
</div>
