<?php
// license_guard.php (v2 — hotfix signature)
// ============================================================
// Correções:
// 1. Verifica assinatura sobre o JSON BRUTO do banco (não re-encoda),
//    porque PostgreSQL JSONB normaliza/reordena chaves e quebra a assinatura
// 2. Se a verificação do cache falha, REVALIDA via API em vez de bloquear
//    (cache corrompido não é prova de tampering — pode ser corrupção benigna)
// ============================================================

require_once __DIR__ . '/license_public_key.php';

const LICENSE_GRACE_PERIOD_SECONDS = 86400;
const LICENSE_HTTP_TIMEOUT         = 10;
const LICENSE_HTTP_CONNECT_TIMEOUT = 5;

function license_guard_or_die(): void
{
    if (license_guard_is_bypass_page()) return;

    $currentDomain = license_guard_current_domain();

    try {
        $meta = license_guard_load_metadata();
    } catch (Throwable $e) {
        license_guard_show_not_configured($e->getMessage());
    }

    if (empty($meta['license_key'])) {
        license_guard_show_not_configured('Licença não configurada.');
    }

    $now            = time();
    $cachedUntilTs  = !empty($meta['cached_until']) ? strtotime($meta['cached_until']) : 0;
    $cachePayloadJson = (string)($meta['last_check_payload'] ?? '');
    $cacheSignature   = (string)($meta['last_check_signature'] ?? '');

    // 1. Cache válido (não vencido) e tem dados?
    if ($cachedUntilTs > $now && $cachePayloadJson !== '') {
        // Verifica assinatura sobre a STRING ORIGINAL (não re-encodada)
        if (license_guard_verify_signature_raw($cachePayloadJson, $cacheSignature)) {
            $cachePayload = json_decode($cachePayloadJson, true);
            if (is_array($cachePayload)) {
                $allowed = $cachePayload['allowed_domains'] ?? [];
                if (in_array($currentDomain, $allowed, true)) {
                    return; // ✅ libera
                }
                license_guard_show_blocked('domain_mismatch',
                    "Este domínio ($currentDomain) não está autorizado pela licença.");
            }
        }
        // Cache não validou — não bloqueia, cai pra revalidação via API
        // (pode ser cache corrompido de versão antiga / JSONB normalizado)
    }

    // 2. Sem cache válido — chama servidor central
    $response = license_guard_call_validate($meta['license_key'], $currentDomain);

    if (!empty($response['ok'])) {
        $payload   = $response['payload']   ?? null;
        $signature = $response['signature'] ?? '';

        if (!is_array($payload)) {
            license_guard_show_blocked('invalid_response', 'Resposta do servidor inesperada.');
        }

        // Re-encoda com as MESMAS flags que o servidor usou pra assinar
        $payloadJson = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        if (!license_guard_verify_signature_raw($payloadJson, $signature)) {
            license_guard_show_blocked('signature_invalid',
                'Assinatura do servidor de licenças inválida. Verifique a integridade do sistema.');
        }

        $allowed = $payload['allowed_domains'] ?? [];
        if (!in_array($currentDomain, $allowed, true)) {
            license_guard_show_blocked('domain_mismatch',
                "Este domínio ($currentDomain) não está autorizado pela licença.");
        }

        // Persiste o JSON EXATO que foi verificado (não re-encoda depois)
        try {
            license_guard_save_metadata($meta['license_key'], $payload, $payloadJson, $signature);
        } catch (Throwable $e) {
            error_log('license_guard: falha ao salvar cache: ' . $e->getMessage());
        }

        return; // ✅ libera
    }

    // 3. Erro de rede — graceful degradation
    if (!empty($response['network_error'])) {
        $graceUntil = $cachedUntilTs + LICENSE_GRACE_PERIOD_SECONDS;
        if ($cachedUntilTs > 0 && $graceUntil > $now && $cachePayloadJson !== '') {
            if (license_guard_verify_signature_raw($cachePayloadJson, $cacheSignature)) {
                $cachePayload = json_decode($cachePayloadJson, true);
                if (is_array($cachePayload)) {
                    $allowed = $cachePayload['allowed_domains'] ?? [];
                    if (in_array($currentDomain, $allowed, true)) {
                        return; // ✅ libera em modo grace
                    }
                }
            }
        }
        license_guard_show_blocked('network_error',
            'Não conseguimos contatar o servidor de licenças e o cache local expirou. '
          . 'Verifique sua internet ou aguarde alguns minutos.');
    }

    // 4. Servidor disse "inválido"
    $error   = $response['error']   ?? 'invalid';
    $message = $response['message'] ?? 'Licença inválida.';
    license_guard_show_blocked($error, $message);
}

// ============================================================
// HELPERS
// ============================================================

function license_guard_is_bypass_page(): bool
{
    $uri = $_SERVER['REQUEST_URI'] ?? '';
    if (stripos($uri, '/onboarding/')      !== false) return true;
    if (stripos($uri, 'license_blocked')   !== false) return true;
    if (stripos($uri, 'license_setup')     !== false) return true;
    return false;
}

function license_guard_current_domain(): string
{
    $host = $_SERVER['HTTP_HOST'] ?? '';
    $host = preg_replace('/:\d+$/', '', $host);
    return strtolower(trim($host));
}

function license_guard_root_domain(): string
{
    $host = license_guard_current_domain();
    if (preg_match('/^(admin|app)\.(.+)$/i', $host, $m)) {
        return $m[2];
    }
    return $host;
}

function license_guard_onboarding_url(): string
{
    $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $root  = license_guard_root_domain();
    return $proto . '://' . $root . '/onboarding/index.php';
}

function license_guard_db(): PDO
{
    if (function_exists('db')) return db();

    // Fallback: tenta autocarregar db.php / config.php do mesmo diretório
    // (em caso de license_guard ser chamado antes do bootstrap normal)
    license_guard_try_autoload_config();

    if (function_exists('db')) return db();

    if (!defined('DB_HOST') || !defined('DB_USER') || !defined('DB_PASSWORD') || !defined('DB_NAME')) {
        throw new RuntimeException('Configurações de banco não definidas.');
    }
    $port = defined('DB_PORT')    ? DB_PORT    : '6543';
    $ssl  = defined('DB_SSLMODE') ? DB_SSLMODE : 'require';
    $dsn  = sprintf('pgsql:host=%s;port=%s;dbname=%s;sslmode=%s', DB_HOST, $port, DB_NAME, $ssl);
    return new PDO($dsn, DB_USER, DB_PASSWORD, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_TIMEOUT            => 8,
    ]);
}

/**
 * Tenta carregar config.php / db.php do mesmo diretório do license_guard.
 * Usado como fallback quando o license_guard é chamado antes do bootstrap.
 *
 * DEFENSIVO:
 *   - Se as constantes do banco já estão definidas → não tenta carregar
 *     (evita redeclaration error caso o bootstrap principal use 'require' sem '_once')
 *   - Usa ob_start pra capturar output do config (session, headers, etc)
 *   - Usa include_once + try/catch pra não derrubar a página
 */
function license_guard_try_autoload_config(): void
{
    if (function_exists('db')) return;

    // Se já temos as constantes de banco, NÃO recarrega o config.
    // Confiamos no bootstrap normal pra carregar db() em outra hora;
    // o license_guard_db() vai criar PDO próprio com as constantes existentes.
    if (defined('DB_HOST') && defined('DB_USER') && defined('DB_PASSWORD') && defined('DB_NAME')) {
        return;
    }

    $candidates = [
        __DIR__ . '/db.php',
        __DIR__ . '/config.php',
    ];

    foreach ($candidates as $f) {
        if (!file_exists($f) || !is_readable($f)) continue;

        ob_start();
        try {
            include_once $f;
        } catch (Throwable $e) {
            // ignora erros — vai cair no throw padrão se nada funcionar
        }
        ob_end_clean();

        if (function_exists('db')) return;
    }
}

function license_guard_load_metadata(): array
{
    $st = license_guard_db()->prepare(
        'SELECT license_key, license_status, allowed_domains, expires_at,
                last_check_at, last_check_payload, last_check_signature, cached_until,
                customer_email, customer_name, plan
         FROM system_metadata WHERE id = 1 LIMIT 1'
    );
    $st->execute();
    $row = $st->fetch();
    if (!$row) {
        license_guard_db()->prepare(
            "INSERT INTO system_metadata (id, license_status) VALUES (1, 'pending') ON CONFLICT (id) DO NOTHING"
        )->execute();
        return ['license_key' => null];
    }
    return $row;
}

/**
 * Salva no banco. IMPORTANTE: `last_check_payload` agora é TEXT,
 * salvamos o JSON exato que foi verificado (sem re-encodar).
 */
function license_guard_save_metadata(string $key, array $payload, string $payloadJson, string $signature): void
{
    $cacheTtl    = (int)($payload['cache_ttl'] ?? 21600);
    $cachedUntil = date('c', time() + $cacheTtl);
    $expiresAt   = !empty($payload['expires_at']) ? $payload['expires_at'] : null;
    $allowed     = $payload['allowed_domains'] ?? [];

    $sql = "UPDATE system_metadata SET
                license_key           = :k,
                license_status        = :st,
                allowed_domains       = :dom::jsonb,
                expires_at            = :exp,
                last_check_at         = NOW(),
                last_check_payload    = :pl,
                last_check_signature  = :sig,
                cached_until          = :cu,
                customer_email        = :ce,
                customer_name         = :cn,
                plan                  = :pn
            WHERE id = 1";

    $st = license_guard_db()->prepare($sql);
    $st->execute([
        ':k'   => $key,
        ':st'  => $payload['status'] ?? 'active',
        ':dom' => json_encode($allowed, JSON_UNESCAPED_UNICODE),
        ':exp' => $expiresAt,
        ':pl'  => $payloadJson, // ← string original do JSON assinado
        ':sig' => $signature,
        ':cu'  => $cachedUntil,
        ':ce'  => $payload['customer_email'] ?? null,
        ':cn'  => $payload['customer_name']  ?? null,
        ':pn'  => $payload['plan']           ?? null,
    ]);
}

function license_guard_call_validate(string $licenseKey, string $domain): array
{
    if (!defined('LICENSE_API_URL') || !LICENSE_API_URL) {
        return ['ok' => false, 'network_error' => true, 'error' => 'LICENSE_API_URL não configurada'];
    }

    $url = rtrim(LICENSE_API_URL, '/') . '/api/v1/validate.php';
    $ch  = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(['license_key' => $licenseKey, 'domain' => $domain]),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => LICENSE_HTTP_CONNECT_TIMEOUT,
        CURLOPT_TIMEOUT        => LICENSE_HTTP_TIMEOUT,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_FOLLOWLOCATION => false,
    ]);
    $resp = curl_exec($ch);
    $err  = curl_error($ch);
    curl_close($ch);

    if ($resp === false || $err !== '') {
        return ['ok' => false, 'network_error' => true, 'error' => $err ?: 'curl_error'];
    }

    $data = json_decode($resp, true);
    if (!is_array($data)) {
        return ['ok' => false, 'network_error' => true, 'error' => 'invalid_json_response'];
    }
    return $data;
}

/**
 * Verifica a assinatura RSA sobre uma STRING JSON exata (não re-encoda).
 * Esta é a forma robusta — operate sobre os bytes originais.
 */
function license_guard_verify_signature_raw(string $json, string $signatureB64): bool
{
    if ($json === '' || $signatureB64 === '') return false;
    $sig = base64_decode($signatureB64, true);
    if ($sig === false) return false;
    $publicKey = defined('LICENSE_PUBLIC_KEY') ? LICENSE_PUBLIC_KEY : '';
    if ($publicKey === '') return false;
    $result = @openssl_verify($json, $sig, $publicKey, OPENSSL_ALGO_SHA256);
    return $result === 1;
}

// ============================================================
// TELAS DE BLOQUEIO
// ============================================================

function license_guard_show_blocked(string $errorCode, string $message): void
{
    if (headers_sent()) { echo $message; exit; }
    http_response_code(403);
    license_guard_render_block_page('🔒 Licença inválida', $errorCode, $message, false);
    exit;
}

function license_guard_show_not_configured(string $reason = ''): void
{
    if (headers_sent()) {
        header('Location: ' . license_guard_onboarding_url());
        exit;
    }
    http_response_code(503);
    license_guard_render_block_page('⚙️ Sistema não configurado', 'not_configured', $reason, true);
    exit;
}

function license_guard_render_block_page(string $title, string $code, string $message, bool $showOnboardingLink): void
{
    $rootDomain    = license_guard_root_domain();
    $currentDomain = license_guard_current_domain();
    $onboarding    = license_guard_onboarding_url();
    ?>
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($title) ?></title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, Arial;
       background: linear-gradient(135deg, #211843 0%, #15102e 100%);
       min-height: 100vh; color: #e5e7eb;
       display: flex; align-items: center; justify-content: center; padding: 20px; }
.card { max-width: 540px; width: 100%;
        background: rgba(255,255,255,0.04);
        border: 1px solid rgba(117,239,255,0.18);
        border-radius: 20px; padding: 36px 32px;
        backdrop-filter: blur(20px);
        box-shadow: 0 20px 60px rgba(0,0,0,0.5);
        text-align: center; }
.icon { font-size: 3rem; margin-bottom: 16px; display: inline-block; animation: pulse 2s infinite; }
@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.06); } }
h1 { font-size: 1.45rem; margin-bottom: 8px; }
.code { display: inline-block; background: rgba(244,63,94,0.12);
        border: 1px solid rgba(244,63,94,0.3); color: #fda4af;
        font-family: ui-monospace, monospace; font-size: 0.78rem;
        padding: 3px 10px; border-radius: 999px; margin: 8px 0 16px;
        text-transform: uppercase; letter-spacing: 0.5px; }
.message { color: #cbd5e1; line-height: 1.6; margin-bottom: 24px; font-size: 0.95rem; }
.info-box { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.05);
            border-radius: 10px; padding: 12px 14px; margin-bottom: 20px;
            text-align: left; font-size: 0.8rem; color: #9aa4b2;
            font-family: ui-monospace, monospace; }
.info-box strong { color: #75EFFF; }
.btn { display: inline-block; background: linear-gradient(135deg, #75EFFF 0%, #43d9e8 100%);
       color: #211843; text-decoration: none; padding: 12px 28px; border-radius: 12px;
       font-weight: 800; font-size: 0.95rem; transition: transform .15s, box-shadow .15s; }
.btn:hover { transform: translateY(-1px); box-shadow: 0 10px 24px rgba(117,239,255,0.3); }
.footer { margin-top: 26px; padding-top: 18px; border-top: 1px solid rgba(255,255,255,0.06);
          color: #6b7280; font-size: 0.78rem; }
</style>
</head>
<body>
<div class="card">
    <div class="icon"><?= $showOnboardingLink ? '⚙️' : '🔒' ?></div>
    <h1><?= htmlspecialchars($title) ?></h1>
    <span class="code"><?= htmlspecialchars($code) ?></span>
    <p class="message"><?= htmlspecialchars($message) ?></p>
    <div class="info-box">
        <strong>Domínio acessado:</strong> <?= htmlspecialchars($currentDomain) ?><br>
        <strong>Quando:</strong> <?= date('d/m/Y H:i:s') ?>
    </div>
    <?php if ($showOnboardingLink): ?>
        <a href="<?= htmlspecialchars($onboarding) ?>" class="btn">⚙️ Abrir instalador →</a>
    <?php else: ?>
        <p style="font-size: 0.82rem; color: #9aa4b2; line-height: 1.5;">
            Entre em contato com o suporte se acredita que esta mensagem é um engano.
        </p>
    <?php endif; ?>
    <div class="footer">Sistema protegido por licença Evolua White Label</div>
</div>
</body>
</html>
    <?php
}
