<?php

declare(strict_types=1);

/**
 * QA + Seguridad operativa para producción.
 *
 * Uso:
 *   /Applications/XAMPP/xamppfiles/bin/php scripts/qa_production_security.php
 */

$basePath = dirname(__DIR__);
$envPath = $basePath . '/.env';

if (!is_file($envPath)) {
    fwrite(STDERR, "[FAIL] No se encontró .env en {$basePath}\n");
    exit(1);
}

$envVars = [];
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
foreach ($lines as $line) {
    $line = trim($line);
    if ($line === '' || str_starts_with($line, '#') || !str_contains($line, '=')) {
        continue;
    }
    [$key, $value] = explode('=', $line, 2);
    $envVars[trim($key)] = trim($value);
}

$dbHost = $envVars['DB_HOST'] ?? '127.0.0.1';
$dbPort = (int) ($envVars['DB_PORT'] ?? 3306);
$dbName = $envVars['DB_DATABASE'] ?? 'veltrix_pos';
$dbUser = $envVars['DB_USERNAME'] ?? 'root';
$dbPass = $envVars['DB_PASSWORD'] ?? '';

try {
    $pdo = new PDO(
        sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $dbHost, $dbPort, $dbName),
        $dbUser,
        $dbPass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]
    );
} catch (Throwable $e) {
    fwrite(STDERR, '[FAIL] Conexión DB: ' . $e->getMessage() . PHP_EOL);
    exit(1);
}

$fail = 0;
$warn = 0;

$ok = static function (string $label): void {
    echo "[OK] {$label}" . PHP_EOL;
};

$warning = static function (string $label) use (&$warn): void {
    $warn++;
    echo "[WARN] {$label}" . PHP_EOL;
};

$error = static function (string $label) use (&$fail): void {
    $fail++;
    echo "[FAIL] {$label}" . PHP_EOL;
};

$asBool = static function (mixed $value, bool $default = false): bool {
    if ($value === null) {
        return $default;
    }
    $normalized = strtolower(trim((string) $value));
    if ($normalized === '') {
        return $default;
    }
    return in_array($normalized, ['1', 'true', 'yes', 'si', 'on'], true);
};

$appEnv = strtolower((string) ($envVars['APP_ENV'] ?? 'local'));
if ($appEnv === 'production') {
    $ok('APP_ENV en production');
} else {
    $warning('APP_ENV no está en production (actual: ' . $appEnv . ')');
}

$appDebug = strtolower((string) ($envVars['APP_DEBUG'] ?? 'true'));
if (in_array($appDebug, ['false', '0'], true)) {
    $ok('APP_DEBUG desactivado');
} else {
    $error('APP_DEBUG debe estar en false para producción');
}

$forceHttps = $asBool($envVars['FORCE_HTTPS'] ?? null, false);
if ($appEnv !== 'production') {
    $warning('FORCE_HTTPS no evaluado porque APP_ENV no está en production');
} elseif ($forceHttps) {
    $ok('FORCE_HTTPS activado');
} else {
    $warning('FORCE_HTTPS está desactivado en producción');
}

$securityHeadersEnabled = $asBool($envVars['SECURITY_HEADERS_ENABLED'] ?? null, true);
if ($securityHeadersEnabled) {
    $ok('SECURITY_HEADERS_ENABLED activado');
} else {
    $warning('SECURITY_HEADERS_ENABLED está desactivado');
}

$sessionSecureCookie = $asBool($envVars['SESSION_SECURE_COOKIE'] ?? null, false);
if ($appEnv !== 'production') {
    $warning('SESSION_SECURE_COOKIE no evaluado porque APP_ENV no está en production');
} elseif ($sessionSecureCookie) {
    $ok('SESSION_SECURE_COOKIE activado');
} else {
    $warning('SESSION_SECURE_COOKIE está desactivado en producción');
}

$sessionHttpOnly = $asBool($envVars['SESSION_HTTP_ONLY'] ?? null, true);
if ($sessionHttpOnly) {
    $ok('SESSION_HTTP_ONLY activado');
} else {
    $warning('SESSION_HTTP_ONLY está desactivado');
}

$sameSite = strtolower(trim((string) ($envVars['SESSION_SAME_SITE'] ?? 'lax')));
if (in_array($sameSite, ['lax', 'strict', 'none'], true)) {
    $ok('SESSION_SAME_SITE válido (' . strtoupper($sameSite) . ')');
} else {
    $warning('SESSION_SAME_SITE inválido; usa Lax, Strict o None');
}

$hstsEnabled = $asBool($envVars['HSTS_ENABLED'] ?? null, false);
if ($appEnv !== 'production') {
    $warning('HSTS_ENABLED no evaluado porque APP_ENV no está en production');
} elseif ($hstsEnabled) {
    $ok('HSTS_ENABLED activado');
} else {
    $warning('HSTS_ENABLED está desactivado en producción');
}

$sessionTimeout = (int) ($envVars['SESSION_TIMEOUT'] ?? 0);
if ($sessionTimeout >= 30 && $sessionTimeout <= 240) {
    $ok('SESSION_TIMEOUT en rango recomendado');
} else {
    $warning('SESSION_TIMEOUT fuera de rango recomendado (30-240), actual: ' . $sessionTimeout);
}

$maxAttempts = (int) ($envVars['MAX_LOGIN_ATTEMPTS'] ?? 0);
$lockMinutes = (int) ($envVars['LOGIN_LOCK_MINUTES'] ?? 0);
if ($maxAttempts > 0 && $lockMinutes > 0) {
    $ok('Protección por intentos fallidos configurada');
} else {
    $error('Configura MAX_LOGIN_ATTEMPTS y LOGIN_LOCK_MINUTES con valores > 0');
}

$requiredTables = [
    'saas_checkout_intents',
    'configuracion_global',
    'avisos_cobro_suscripcion',
    'soporte_tickets',
    'soporte_ticket_eventos',
];
$tableStmt = $pdo->prepare(
    'SELECT COUNT(*) AS total
     FROM information_schema.tables
     WHERE table_schema = DATABASE()
       AND table_name IN (' . implode(', ', array_fill(0, count($requiredTables), '?')) . ')'
);
$tableStmt->execute($requiredTables);
$foundTables = (int) ($tableStmt->fetch()['total'] ?? 0);
if ($foundTables === count($requiredTables)) {
    $ok('Tablas SaaS/Soporte requeridas presentes');
} else {
    $error('Faltan tablas requeridas (' . $foundTables . '/' . count($requiredTables) . ')');
}

$superadminStmt = $pdo->query(
    'SELECT COUNT(*) AS total
     FROM usuarios u
     INNER JOIN roles r ON r.id = u.rol_id
     WHERE r.slug = "superadmin"
       AND u.activo = 1'
);
$superadmins = (int) ($superadminStmt->fetch()['total'] ?? 0);
if ($superadmins > 0) {
    $ok('Existe al menos un superadmin activo');
} else {
    $error('No existe superadmin activo');
}

$defaultAdminStmt = $pdo->prepare('SELECT password FROM usuarios WHERE email = :email LIMIT 1');
$defaultAdminStmt->execute([':email' => 'admin@veltrix.com']);
$defaultAdminHash = (string) ($defaultAdminStmt->fetchColumn() ?: '');
if ($defaultAdminHash !== '' && password_verify('Admin12345*', $defaultAdminHash)) {
    $warning('La clave por defecto de admin@veltrix.com sigue vigente. Cámbiala.');
} else {
    $ok('Credencial inicial admin no coincide con la clave por defecto');
}

$globalWompiStmt = $pdo->query(
    'SELECT clave, valor
     FROM configuracion_global
     WHERE clave IN ("wompi_public_key", "wompi_integrity_secret", "wompi_events_secret")'
);
$globalWompi = [];
foreach ($globalWompiStmt->fetchAll() ?: [] as $row) {
    $globalWompi[(string) $row['clave']] = trim((string) ($row['valor'] ?? ''));
}

$publicKey = $globalWompi['wompi_public_key'] ?? ($envVars['WOMPI_PUBLIC_KEY'] ?? '');
$integrity = $globalWompi['wompi_integrity_secret'] ?? ($envVars['WOMPI_INTEGRITY_SECRET'] ?? '');
$events = $globalWompi['wompi_events_secret'] ?? ($envVars['WOMPI_EVENTS_SECRET'] ?? '');

if (trim($publicKey) !== '' && trim($integrity) !== '' && trim($events) !== '') {
    $ok('Claves Wompi configuradas');
} else {
    $warning('Faltan claves Wompi (public/integrity/events) para onboarding automático');
}

$notifyStmt = $pdo->query(
    'SELECT clave, valor
     FROM configuracion_global
     WHERE clave IN (
        "notify_channel_email",
        "notify_channel_whatsapp",
        "notify_whatsapp_provider",
        "notify_email_from_address",
        "notify_whatsapp_api_url",
        "notify_whatsapp_phone_number_id",
        "notify_whatsapp_access_token",
        "notify_manychat_api_url",
        "notify_manychat_access_token",
        "notify_manychat_contact_field_id",
        "notify_whatsapp_template_enabled",
        "notify_whatsapp_template_language",
        "notify_whatsapp_template_name_10d",
        "notify_whatsapp_template_name_3d"
     )'
);
$notifyConfig = [];
foreach ($notifyStmt->fetchAll() ?: [] as $row) {
    $notifyConfig[(string) $row['clave']] = trim((string) ($row['valor'] ?? ''));
}

$notifyEmailEnabledRaw = $notifyConfig['notify_channel_email'] ?? ($envVars['NOTIFY_CHANNEL_EMAIL'] ?? 'true');
$notifyEmailEnabled = $asBool($notifyEmailEnabledRaw, true);
$emailFrom = $notifyConfig['notify_email_from_address'] ?? ($envVars['NOTIFY_EMAIL_FROM_ADDRESS'] ?? 'no-reply@veltrix.local');
if (!$notifyEmailEnabled) {
    $warning('Canal email de cobro está desactivado');
} elseif (filter_var($emailFrom, FILTER_VALIDATE_EMAIL)) {
    $ok('Canal email de cobro configurado');
} else {
    $warning('Canal email activo pero falta NOTIFY_EMAIL_FROM_ADDRESS válido');
}

$notifyWhatsappEnabledRaw = $notifyConfig['notify_channel_whatsapp'] ?? ($envVars['NOTIFY_CHANNEL_WHATSAPP'] ?? 'true');
$notifyWhatsappEnabled = $asBool($notifyWhatsappEnabledRaw, true);
$whatsAppProvider = strtolower(trim((string) ($notifyConfig['notify_whatsapp_provider'] ?? ($envVars['WHATSAPP_PROVIDER'] ?? 'meta'))));
if (!in_array($whatsAppProvider, ['meta', 'manychat'], true)) {
    $whatsAppProvider = 'meta';
}
$waApi = $notifyConfig['notify_whatsapp_api_url'] ?? ($envVars['WHATSAPP_API_URL'] ?? '');
$waPhoneId = $notifyConfig['notify_whatsapp_phone_number_id'] ?? ($envVars['WHATSAPP_PHONE_NUMBER_ID'] ?? '');
$waToken = $notifyConfig['notify_whatsapp_access_token'] ?? ($envVars['WHATSAPP_ACCESS_TOKEN'] ?? '');
$manychatApi = $notifyConfig['notify_manychat_api_url'] ?? ($envVars['MANYCHAT_API_URL'] ?? '');
$manychatToken = $notifyConfig['notify_manychat_access_token'] ?? ($envVars['MANYCHAT_ACCESS_TOKEN'] ?? '');
$manychatContactFieldId = $notifyConfig['notify_manychat_contact_field_id'] ?? ($envVars['MANYCHAT_CONTACT_FIELD_ID'] ?? '');
$waTemplateEnabledRaw = $notifyConfig['notify_whatsapp_template_enabled'] ?? ($envVars['WHATSAPP_TEMPLATE_ENABLED'] ?? 'false');
$waTemplateEnabled = $asBool($waTemplateEnabledRaw, false);
$waTemplateLang = trim((string) ($notifyConfig['notify_whatsapp_template_language'] ?? ($envVars['WHATSAPP_TEMPLATE_LANGUAGE'] ?? 'es_CO')));
$waTemplate10d = trim((string) ($notifyConfig['notify_whatsapp_template_name_10d'] ?? ($envVars['WHATSAPP_TEMPLATE_NAME_10D'] ?? '')));
$waTemplate3d = trim((string) ($notifyConfig['notify_whatsapp_template_name_3d'] ?? ($envVars['WHATSAPP_TEMPLATE_NAME_3D'] ?? '')));
$linkedTemplateCount = 0;
try {
    $linkedTemplateCount = (int) $pdo->query(
        'SELECT COUNT(*)
         FROM whatsapp_meta_templates
         WHERE TRIM(COALESCE(uso, "")) <> ""
           AND LOWER(TRIM(uso)) <> "libre"'
    )->fetchColumn();
} catch (\Throwable) {
    $linkedTemplateCount = 0;
}
if (!$notifyWhatsappEnabled) {
    $warning('Canal WhatsApp de cobro está desactivado');
} elseif ($whatsAppProvider === 'manychat') {
    if ($manychatApi !== '' && $manychatToken !== '') {
        $ok('Canal WhatsApp de cobro configurado con ManyChat');
        if ($manychatContactFieldId === '') {
            $warning('ManyChat activo sin MANYCHAT_CONTACT_FIELD_ID; algunos contactos existentes por WhatsApp ID pueden no resolverse por API');
        }
    } else {
        $warning('Canal WhatsApp (ManyChat) activo pero faltan credenciales (API URL / token)');
    }
} elseif ($waApi !== '' && $waPhoneId !== '' && $waToken !== '') {
    $ok('Canal WhatsApp de cobro configurado con Meta');
    if ($waTemplateEnabled) {
        if ($linkedTemplateCount > 0 || ($waTemplate10d !== '' && $waTemplate3d !== '')) {
            $ok('Plantillas WhatsApp automáticas activas en idioma ' . ($waTemplateLang !== '' ? $waTemplateLang : 'es_CO') . ' (enlaces detectados: ' . $linkedTemplateCount . ')');
        } else {
            $warning('Plantillas WhatsApp activadas pero no se detectan enlaces de plantilla (ni dinámica ni legacy 10/3)');
        }
    }
} else {
    $warning('Canal WhatsApp (Meta) activo pero faltan credenciales (API URL / phone_number_id / token)');
}

$backupScript = $basePath . '/scripts/backup_db.sh';
if (is_file($backupScript) && is_executable($backupScript)) {
    $ok('Script de backup presente y ejecutable');
} else {
    $error('El script scripts/backup_db.sh no existe o no es ejecutable');
}

$backupDir = $basePath . '/storage/backups';
if (is_dir($backupDir) && is_writable($backupDir)) {
    $ok('Directorio de backups accesible');
} else {
    $warning('Directorio storage/backups no existe o no es escribible');
}

$latestBackup = null;
if (is_dir($backupDir)) {
    $files = glob($backupDir . '/*.sql.gz') ?: [];
    if ($files !== []) {
        usort($files, static fn ($a, $b) => filemtime($b) <=> filemtime($a));
        $latestBackup = $files[0];
    }
}

if ($latestBackup !== null) {
    $hours = (time() - (int) filemtime($latestBackup)) / 3600;
    if ($hours <= 36) {
        $ok('Backup reciente detectado (' . round($hours, 1) . 'h)');
    } else {
        $warning('Último backup tiene más de 36h (' . round($hours, 1) . 'h)');
    }
} else {
    $warning('No se encontraron backups .sql.gz en storage/backups');
}

$logDir = $basePath . '/storage/logs';
if (is_dir($logDir) && is_writable($logDir)) {
    $ok('Directorio de logs escribible');
} else {
    $error('storage/logs no existe o no tiene permisos de escritura');
}

echo PHP_EOL . "Resumen: {$fail} fallos, {$warn} advertencias." . PHP_EOL;
exit($fail > 0 ? 1 : 0);
