<?php

namespace App\Controllers;

use App\Core\View;
use App\Core\FbAuth;
use App\Core\Versao;

/**
 * Atualização do Sistema — verifica se há versão nova (manifesto JSON publicado
 * em URL configurável), mostra atual × nova e aplica a 1 clique COM confirmação
 * (nunca automático). Baixa o zip, extrai via PowerShell (Expand-Archive) e
 * sobrepõe app/ + public/ com robocopy. Restrito a admin.
 */
class AtualizacaoController
{
    private function guardaAdmin(): void
    {
        if (!FbAuth::isAdmin()) {
            http_response_code(403);
            if (str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/api/')) {
                header('Content-Type: application/json; charset=utf-8');
                echo json_encode(['erro' => 'Acesso restrito ao administrador.'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
            } else {
                echo 'Acesso restrito ao administrador.';
            }
            exit;
        }
    }

    private function root(): string { return dirname(__DIR__, 2); }
    private function configPath(): string { return $this->root() . '/config/atualizacao.php'; }

    private function urlConfigurada(): string
    {
        $p = $this->configPath();
        if (is_file($p)) {
            $c = require $p;
            return trim((string)($c['url'] ?? ''));
        }
        return '';
    }

    private function salvarUrl(string $url): bool
    {
        $url = trim($url);
        $cont = "<?php\n// Configuração da Atualização do Sistema (gerado pela tela).\nreturn [\n    'url' => " . var_export($url, true) . ",\n];\n";
        return @file_put_contents($this->configPath(), $cont) !== false;
    }

    /** GET genérico via curl OU file_get_contents (fallback). Retorna [corpo|null, httpcode, erro]. */
    private function baixar(string $url, ?string $salvarEm = null): array
    {
        // Caminho preferido: cURL (suporta download em fluxo p/ ZIP grande)
        if (function_exists('curl_init')) {
            $ch = curl_init($url);
            $fh = null;
            curl_setopt_array($ch, [
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_MAXREDIRS      => 5,
                CURLOPT_CONNECTTIMEOUT => 15,
                CURLOPT_TIMEOUT        => 120,
                CURLOPT_SSL_VERIFYPEER => true,
                CURLOPT_USERAGENT      => 'PremiumExpressBI-Updater',
            ]);
            if ($salvarEm) {
                $fh = fopen($salvarEm, 'wb');
                curl_setopt($ch, CURLOPT_FILE, $fh);
            } else {
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            }
            $body = curl_exec($ch);
            $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $err  = curl_error($ch);
            curl_close($ch);
            if ($fh) fclose($fh);
            return [$salvarEm ? null : $body, $code, $err];
        }

        // Fallback: file_get_contents com stream context (quando ext-curl não está habilitada)
        $ctx = stream_context_create([
            'http' => [
                'method'     => 'GET',
                'user_agent' => 'PremiumExpressBI-Updater',
                'timeout'    => 120,
                'follow_location' => 1,
                'max_redirects'   => 5,
                'ignore_errors'   => true,
            ],
            'ssl' => [
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ],
        ]);
        $body = @file_get_contents($url, false, $ctx);
        $code = 0;
        $err  = '';
        if (isset($http_response_header) && is_array($http_response_header)) {
            foreach ($http_response_header as $h) {
                if (preg_match('#^HTTP/\S+\s+(\d+)#', $h, $m)) { $code = (int)$m[1]; break; }
            }
        }
        if ($body === false) { $err = 'file_get_contents falhou (sem ext-curl).'; }
        if ($body !== false && $salvarEm) {
            @file_put_contents($salvarEm, $body);
            return [null, $code ?: 200, $err];
        }
        return [$body === false ? null : $body, $code ?: ($body !== false ? 200 : 0), $err];
    }

    /** Busca + valida o manifesto JSON. Retorna array ['ok'=>bool, ...]. */
    private function manifesto(): array
    {
        $url = $this->urlConfigurada();
        if ($url === '') {
            return ['ok' => false, 'motivo' => 'sem_url'];
        }
        [$body, $code, $err] = $this->baixar($url);
        if ($body === null || $body === false || $code >= 400) {
            return ['ok' => false, 'motivo' => 'fetch', 'http' => $code, 'erro' => $err];
        }
        $txt = (string)$body;
        if (substr($txt, 0, 3) === "\xEF\xBB\xBF") $txt = substr($txt, 3); // remove BOM (comum em arquivo salvo no Windows)
        $j = json_decode(trim($txt), true);
        if (!is_array($j) || empty($j['versao'])) {
            return ['ok' => false, 'motivo' => 'json'];
        }
        return [
            'ok'      => true,
            'versao'  => (string)$j['versao'],
            'data'    => (string)($j['data'] ?? ''),
            'notas'   => (string)($j['notas'] ?? ''),
            'url_zip' => (string)($j['url_zip'] ?? ''),
        ];
    }

    // ── Página ────────────────────────────────────────────────────────────────
    public function index(): void
    {
        $this->guardaAdmin();
        $cfg = [];
        $p = $this->root() . '/config/database.php';
        if (is_file($p)) $cfg = require $p;
        View::render('configurar/atualizacao', [
            'titulo'    => 'Atualização do Sistema — Premium Express Gestão',
            'config'    => $cfg,
            'segmento'  => \App\Core\Lojas::resolverSegmento($cfg),
            'versaoAtual' => Versao::ATUAL,
            'versaoData'  => Versao::DATA,
            'urlConfig'   => $this->urlConfigurada(),
            'ano'       => date('Y'),
        ]);
    }

    // ════════════════════════════════════════════════════════════════════════
    //  SERVIDOR DE ATUALIZAÇÃO (público) — este PC publica p/ as outras lojas.
    //  URL do manifesto p/ configurar nos clientes: http://<este-pc>/atualizacoes/versao.json
    // ════════════════════════════════════════════════════════════════════════

    /** GET /atualizacoes/versao.json — manifesto público (versão deste servidor + link do pacote). */
    public function manifestoPublico(): void
    {
        header('Content-Type: application/json; charset=utf-8');
        header('Cache-Control: no-store');
        $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
        $host   = $_SERVER['HTTP_HOST'] ?? 'localhost';
        $nf     = $this->root() . '/storage/atualizacao/notas.txt';
        $notas  = is_file($nf) ? trim((string) @file_get_contents($nf)) : 'Atualização do Premium Express Gestão.';
        echo json_encode([
            'versao'  => Versao::ATUAL,
            'data'    => Versao::DATA,
            'notas'   => $notas,
            'url_zip' => $scheme . '://' . $host . '/atualizacoes/pacote.zip',
        ], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
        exit;
    }

    /** GET /atualizacoes/pacote.zip — entrega o pacote de atualização. */
    public function pacotePublico(): void
    {
        $cands = [
            $this->root() . '/storage/atualizacao/pacote.zip',                 // publicação explícita
            $this->root() . '/installer/dist/PremiumExpressBI_ATUALIZACAO.zip', // build local
        ];
        $zip = '';
        foreach ($cands as $c) { if (is_file($c)) { $zip = $c; break; } }
        if ($zip === '') { http_response_code(404); echo 'Pacote de atualização não encontrado.'; exit; }
        header('Content-Type: application/zip');
        header('Content-Length: ' . filesize($zip));
        header('Content-Disposition: attachment; filename="PremiumExpressBI_ATUALIZACAO.zip"');
        header('Cache-Control: no-store');
        readfile($zip);
        exit;
    }

    // ── API: salvar URL ─────────────────────────────────────────────────────────
    public function salvarUrlApi(): void
    {
        $this->guardaAdmin();
        header('Content-Type: application/json; charset=utf-8');
        $url = trim((string)($_POST['url'] ?? ''));
        if ($url !== '' && !preg_match('#^https?://#i', $url)) {
            echo json_encode(['ok' => false, 'erro' => 'URL deve começar com http:// ou https://'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
            exit;
        }
        $ok = $this->salvarUrl($url);
        echo json_encode(['ok' => $ok], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
        exit;
    }

    // ── API: verificar ──────────────────────────────────────────────────────────
    public function verificarApi(): void
    {
        $this->guardaAdmin();
        header('Content-Type: application/json; charset=utf-8');
        $m = $this->manifesto();
        if (!$m['ok']) {
            $msg = match ($m['motivo']) {
                'sem_url' => 'Nenhuma URL de atualização configurada.',
                'fetch'   => 'Não foi possível acessar a URL de atualização (HTTP ' . ($m['http'] ?? '?') . ').',
                'json'    => 'A URL respondeu, mas o conteúdo não é um manifesto válido.',
                default   => 'Falha ao verificar atualização.',
            };
            echo json_encode(['ok' => false, 'erro' => $msg, 'atual' => Versao::ATUAL], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
            exit;
        }
        $ha = version_compare($m['versao'], Versao::ATUAL, '>');
        echo json_encode([
            'ok'          => true,
            'atual'       => Versao::ATUAL,
            'nova'        => $m['versao'],
            'data'        => $m['data'],
            'notas'       => $m['notas'],
            'tem_zip'     => $m['url_zip'] !== '',
            'atualizar'   => $ha,
        ], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
        exit;
    }

    // ── API: aplicar (só quando o usuário confirma) ──────────────────────────────
    public function aplicarApi(): void
    {
        $this->guardaAdmin();
        header('Content-Type: application/json; charset=utf-8');

        $m = $this->manifesto();
        if (!$m['ok']) { echo json_encode(['ok' => false, 'erro' => 'Manifesto indisponível.'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit; }
        if (!version_compare($m['versao'], Versao::ATUAL, '>')) {
            echo json_encode(['ok' => false, 'erro' => 'Você já está na versão mais recente.'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit;
        }
        if ($m['url_zip'] === '') { echo json_encode(['ok' => false, 'erro' => 'Manifesto sem url_zip.'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit; }

        $root = $this->root();
        $base = $root . '/storage/cache/_update';
        $zip  = $base . '/update.zip';
        $ext  = $base . '/ext';

        // limpa/recria área temporária
        $this->rrmdir($base);
        if (!@mkdir($base, 0777, true) && !is_dir($base)) {
            echo json_encode(['ok' => false, 'erro' => 'Não consegui criar pasta temporária.'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit;
        }

        // 1) baixar
        [, $code, $err] = $this->baixar($m['url_zip'], $zip);
        if (!is_file($zip) || filesize($zip) < 1024 || $code >= 400) {
            echo json_encode(['ok' => false, 'erro' => 'Falha ao baixar o pacote (HTTP ' . $code . ' ' . $err . ').'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit;
        }

        // 2) extrair (PowerShell Expand-Archive — não depende do php_zip)
        $ps = 'powershell -NoProfile -ExecutionPolicy Bypass -Command '
            . '"Expand-Archive -LiteralPath ' . $this->psq($zip) . ' -DestinationPath ' . $this->psq($ext) . ' -Force"';
        @exec($ps . ' 2>&1', $o1, $rc1);
        if ($rc1 !== 0 || !is_dir($ext . '/app') || !is_dir($ext . '/public')) {
            echo json_encode(['ok' => false, 'erro' => 'Falha ao extrair o pacote (o zip precisa conter app/ e public/).'], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit;
        }

        // 3) sobrepor app/ e public/ (robocopy = mescla e sobrescreve corretamente)
        $falhas = [];
        foreach (['app', 'public'] as $dir) {
            $rc = 0; $out = [];
            // robocopy roda pelo cmd -> usa ASPAS DUPLAS (aspas simples dão erro 16).
            @exec('robocopy ' . $this->cmdq($ext . '/' . $dir) . ' ' . $this->cmdq($root . '/' . $dir)
                  . ' /E /NFL /NDL /NJH /NJS /NP /R:1 /W:1 2>&1', $out, $rc);
            if ($rc >= 8) $falhas[] = $dir; // robocopy: <8 = sucesso
        }
        if ($falhas) {
            echo json_encode(['ok' => false, 'erro' => 'Falha ao copiar: ' . implode(', ', $falhas)], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); exit;
        }

        // 4) limpar caches (código novo passa a valer)
        if (function_exists('opcache_reset')) @opcache_reset();
        foreach (glob($root . '/storage/cache/*.json') ?: [] as $f) @unlink($f);

        // 5) limpeza
        $this->rrmdir($base);

        echo json_encode(['ok' => true, 'nova' => $m['versao']], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
        exit;
    }

    /** Aspas simples p/ caminho no PowerShell (Expand-Archive). */
    private function psq(string $path): string
    {
        $path = str_replace('/', '\\', $path);
        return "'" . str_replace("'", "''", $path) . "'";
    }

    /** Aspas duplas p/ caminho no cmd/robocopy (aspas simples dão erro 16). */
    private function cmdq(string $path): string
    {
        return '"' . str_replace('/', '\\', $path) . '"';
    }

    private function rrmdir(string $dir): void
    {
        if (!is_dir($dir)) return;
        foreach (scandir($dir) ?: [] as $f) {
            if ($f === '.' || $f === '..') continue;
            $p = $dir . '/' . $f;
            is_dir($p) ? $this->rrmdir($p) : @unlink($p);
        }
        @rmdir($dir);
    }
}
