<?php
/**
 * QZ Tray — certificat + semnătură pentru a evita promptul „Allow" repetat.
 *
 * Generăm local (o singură dată) un certificat self-signed + cheie privată RSA.
 * - Certificatul (public) e servit către browser prin api/qz-cert.php.
 * - Fiecare cerere QZ e semnată server-side (api/qz-sign.php) cu cheia privată.
 * Cheia privată NU părăsește serverul și e protejată de acces web (.htaccess).
 *
 * La prima utilizare QZ Tray întreabă o singură dată; bifând „Remember" nu mai
 * întreabă, fiindcă semnătura oferă o identitate stabilă a certificatului.
 */
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/db.php'; // pentru setting_get/setting_set (cheia se păstrează și în DB)

/** Citește o setare direct din DB (fără cache-ul din setting_get). */
function qz_db_get(string $key): ?string
{
    try {
        $v = db_scalar("SELECT value FROM app_settings WHERE key = ?", [$key]);
        return ($v === null || $v === false) ? null : (string)$v;
    } catch (Throwable $e) { return null; }
}
/** Scrie o setare direct în DB (upsert). */
function qz_db_set(string $key, string $val): void
{
    try {
        db_exec(
            "INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)
             ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP",
            [$key, $val]
        );
    } catch (Throwable $e) {}
}

function qz_dir(): string
{
    $d = DATA_PATH . '/qz';
    if (!is_dir($d)) @mkdir($d, 0775, true);
    return $d;
}
function qz_key_path(): string  { return qz_dir() . '/private-key.pem'; }
function qz_cert_path(): string { return qz_dir() . '/digital-certificate.txt'; }

/** Caută un openssl.cnf disponibil (necesar pe Windows/XAMPP pentru CSR/x509). */
function qz_openssl_conf(): ?string
{
    $candidates = [
        getenv('OPENSSL_CONF') ?: '',
        'C:/xampp/php/extras/ssl/openssl.cnf',
        'C:/xampp/apache/conf/openssl.cnf',
        'C:/xampp/php/extras/openssl/openssl.cnf',
    ];
    foreach ($candidates as $c) {
        if ($c && is_file($c)) return $c;
    }
    return null;
}

/** Scrie .htaccess de protecție în folderul qz/. */
function qz_write_htaccess(): void
{
    $ht = qz_dir() . '/.htaccess';
    if (!is_file($ht)) {
        file_put_contents($ht, "Require all denied\n<IfModule !mod_authz_core.c>\nOrder allow,deny\nDeny from all\n</IfModule>\n");
    }
}

/**
 * Asigură existența perechii cheie+certificat.
 * Ordine: fișiere → DB (călătorește cu backup-ul) → generează.
 * Astfel TOATE instalările (local + hosting) folosesc ACELAȘI certificat dacă
 * pornesc din același backup → îl aprobi o singură dată per PC, pe orice domeniu.
 */
function qz_ensure_keypair(): void
{
    $sCert = qz_db_get('qz_cert');
    $sKey  = qz_db_get('qz_key');

    // 1) Fișierele există → migrează-le în DB (dacă lipsesc) ca să intre în backup.
    if (is_file(qz_key_path()) && is_file(qz_cert_path())) {
        if (empty($sCert) || empty($sKey)) {
            qz_db_set('qz_cert', file_get_contents(qz_cert_path()));
            qz_db_set('qz_key',  file_get_contents(qz_key_path()));
        }
        return;
    }

    // 2) Fără fișiere, dar avem cheia în DB (restaurat din backup) → scrie fișierele.
    if (!empty($sCert) && !empty($sKey)) {
        file_put_contents(qz_cert_path(), $sCert);
        file_put_contents(qz_key_path(), $sKey);
        @chmod(qz_key_path(), 0600);
        qz_write_htaccess();
        return;
    }

    // 3) Nimic → generează și salvează în fișiere + DB.
    $opts = ['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, 'digest_alg' => 'sha256'];
    $conf = qz_openssl_conf();
    if ($conf) $opts['config'] = $conf;

    $pkey = openssl_pkey_new($opts);
    if ($pkey === false) {
        throw new RuntimeException('Nu pot genera cheia RSA: ' . openssl_error_string());
    }

    $dn = [
        'commonName'         => 'GestionareNumeProduse QZ',
        'organizationName'   => 'GestionareNumeProduse',
        'countryName'        => 'RO',
    ];
    $csr  = openssl_csr_new($dn, $pkey, $opts);
    $x509 = openssl_csr_sign($csr, null, $pkey, 3650, $opts); // self-signed, 10 ani

    $certOut = '';
    openssl_x509_export($x509, $certOut);
    $keyOut = '';
    openssl_pkey_export($pkey, $keyOut, null, $opts);

    file_put_contents(qz_cert_path(), $certOut);
    file_put_contents(qz_key_path(), $keyOut);
    @chmod(qz_key_path(), 0600);
    qz_write_htaccess();

    // Salvează în DB ca să călătorească cu backup-ul (același cert pe toate instalările).
    qz_db_set('qz_cert', $certOut);
    qz_db_set('qz_key',  $keyOut);
}

/** Certificatul public (PEM) pentru setCertificatePromise. */
function qz_certificate(): string
{
    qz_ensure_keypair();
    return file_get_contents(qz_cert_path());
}

/**
 * Extrage [cert, key] dintr-un conținut de identitate QZ.
 * Acceptă fie .qzkey (JSON {cert,key}), fie PEM cu blocuri CERTIFICATE + PRIVATE KEY
 * (în orice ordine, concatenate — ex. digital-certificate.txt + private-key.pem).
 * @return array{0:string,1:string} cert, key (string gol dacă lipsesc)
 */
function qz_parse_identity(string $raw): array
{
    $j = json_decode($raw, true);
    if (is_array($j) && !empty($j['cert']) && !empty($j['key'])) {
        return [(string)$j['cert'], (string)$j['key']];
    }
    $cert = $key = '';
    if (preg_match('/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/s', $raw, $m)) $cert = $m[0];
    if (preg_match('/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----.*?-----END (?:RSA |EC )?PRIVATE KEY-----/s', $raw, $m)) $key = $m[0];
    return [$cert, $key];
}

/**
 * Scrie o identitate (cert+key) peste tot: DB (călătorește cu backup) + fișiere.
 */
function qz_set_identity(string $cert, string $key): void
{
    qz_db_set('qz_cert', $cert);
    qz_db_set('qz_key',  $key);
    file_put_contents(qz_cert_path(), $cert);
    file_put_contents(qz_key_path(), $key);
    @chmod(qz_key_path(), 0600);
    qz_write_htaccess();
}

/** Semnează `$data` cu cheia privată (RSA-SHA512) și întoarce base64. */
function qz_sign(string $data): string
{
    qz_ensure_keypair();
    $key = openssl_pkey_get_private(file_get_contents(qz_key_path()));
    if ($key === false) {
        throw new RuntimeException('Cheia privată QZ e invalidă.');
    }
    $sig = '';
    if (!openssl_sign($data, $sig, $key, OPENSSL_ALGO_SHA512)) {
        throw new RuntimeException('Semnarea a eșuat: ' . openssl_error_string());
    }
    return base64_encode($sig);
}
