<?php
/**
 * POST /api/update-app.php
 * Actualizează fișierele aplicației dintr-un pachet .zip (cel produs de
 * api/download-app.php pe o versiune mai nouă).
 *
 * Suprascrie codul, dar NU atinge: data/, logs/, .git, config.local.php, .htpasswd.
 * Funcționează cu ZipArchive dacă există; altfel parsează formatul propriu (store/deflate).
 *
 * Body: package (fișier .zip), csrf_token
 */
require_once __DIR__ . '/../includes/db.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    json_response(['ok' => false, 'error' => 'Doar POST'], 405);
}
if (!csrf_check($_POST['csrf_token'] ?? null)) {
    json_response(['ok' => false, 'error' => 'CSRF invalid'], 403);
}
if (empty($_FILES['package']['tmp_name']) || !is_uploaded_file($_FILES['package']['tmp_name'])) {
    json_response(['ok' => false, 'error' => 'Niciun fișier încărcat.'], 422);
}

$raw = (string)@file_get_contents($_FILES['package']['tmp_name']);
if (substr($raw, 0, 2) !== 'PK') {
    json_response(['ok' => false, 'error' => 'Fișierul nu pare o arhivă .zip.'], 422);
}

/** Extrage entries [relpath => content] dintr-un zip. */
function unzip_entries(string $tmpPath, string $raw): array
{
    $out = [];
    if (class_exists('ZipArchive')) {
        $zip = new ZipArchive();
        if ($zip->open($tmpPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $name = $zip->getNameIndex($i);
                if ($name === false || substr($name, -1) === '/') continue;
                $out[str_replace('\\', '/', $name)] = $zip->getFromIndex($i);
            }
            $zip->close();
            return $out;
        }
    }
    // Fallback PHP pur: parsează local file headers secvențial (fără data descriptor).
    $pos = 0; $len = strlen($raw);
    while ($pos + 4 <= $len && substr($raw, $pos, 4) === "PK\x03\x04") {
        $h = unpack('vmethod/Vskip1/Vcrc/Vcsize/Vusize/vnameLen/vextraLen', substr($raw, $pos + 8, 22));
        // unpack câmpuri: method(8), crc(14), csize(18), usize(22), nameLen(26), extraLen(28)
        $method   = $h['method'];
        $csize    = $h['csize'];
        $nameLen  = $h['nameLen'];
        $extraLen = $h['extraLen'];
        $name = substr($raw, $pos + 30, $nameLen);
        $dataStart = $pos + 30 + $nameLen + $extraLen;
        $comp = substr($raw, $dataStart, $csize);
        $content = ($method === 8) ? @gzinflate($comp) : $comp;
        if ($content !== false && substr($name, -1) !== '/') {
            $out[str_replace('\\', '/', $name)] = $content;
        }
        $pos = $dataStart + $csize;
    }
    return $out;
}

$entries = unzip_entries($_FILES['package']['tmp_name'], $raw);

if (!$entries || !isset($entries['includes/config.php'])) {
    json_response(['ok' => false, 'error' => 'Arhiva nu pare a fi aplicația (lipsește includes/config.php).'], 422);
}

$excludeTop   = ['data', 'logs', '.git', '.idea', '.vscode', 'node_modules'];
$excludeNames = ['config.local.php', '.htpasswd', '.installed'];

$written = 0; $skipped = 0;
$errorsList = [];
foreach ($entries as $rel => $content) {
    if ($content === null || $content === false) { $skipped++; continue; }
    $rel = ltrim($rel, '/');
    if ($rel === '' || strpos($rel, '..') !== false) { $skipped++; continue; }
    $top = explode('/', $rel)[0];
    if (in_array($top, $excludeTop, true) || in_array(basename($rel), $excludeNames, true)) { $skipped++; continue; }

    $dest = APP_ROOT . '/' . $rel;
    $dir  = dirname($dest);
    if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) { $errorsList[] = $rel; continue; }
    if (@file_put_contents($dest, $content) === false) { $errorsList[] = $rel; continue; }
    $written++;
}

json_response([
    'ok'      => empty($errorsList),
    'written' => $written,
    'skipped' => $skipped,
    'errors'  => $errorsList,
    'error'   => $errorsList ? ('Nu am putut scrie ' . count($errorsList) . ' fișiere (permisiuni?).') : null,
]);
