<?php
/**
 * Cursul de referință BNR (https://www.bnr.ro), cu cache local în SQLite.
 *
 * Sursele oficiale:
 *   - ziua curentă:  https://www.bnr.ro/nbrfxrates.xml
 *   - anul întreg:   https://www.bnr.ro/files/xml/years/nbrfxrates{AN}.xml
 *
 * BNR publică doar în zilele lucrătoare, deci pentru o comandă datată sâmbăta
 * (sau într-o sărbătoare) folosim ultimul curs publicat înainte de acea zi și
 * spunem explicit din ce zi e — nu inventăm un curs pentru o zi fără publicare.
 */

require_once __DIR__ . '/db.php';

const BNR_URL_LATEST = 'https://www.bnr.ro/nbrfxrates.xml';
const BNR_URL_YEAR   = 'https://www.bnr.ro/files/xml/years/nbrfxrates%d.xml';
const BNR_TIMEOUT    = 8;

/** Descarcă un URL; returnează corpul sau null la orice eroare. */
function bnr_http_get(string $url): ?string
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => BNR_TIMEOUT,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_USERAGENT      => APP_NAME . ' / curs BNR',
    ]);
    $body = curl_exec($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);

    if ($body === false || $code !== 200) {
        error_log('[bnr] GET ' . $url . ' → HTTP ' . $code . ' ' . $err);
        return null;
    }
    return (string)$body;
}

/**
 * Parsează XML-ul BNR și scrie în cache. Returnează câte zile a găsit.
 * `multiplier` (ex. JPY publicat la 100 de unități) e împărțit aici, ca în DB
 * să avem mereu cursul pentru o singură unitate de valută.
 */
function bnr_store_xml(string $xml): int
{
    $prev = libxml_use_internal_errors(true);
    $doc  = simplexml_load_string($xml);
    libxml_use_internal_errors($prev);
    if ($doc === false) return 0;

    // Namespace-ul implicit diferă între fișiere (anii vechi folosesc
    // http://www.bnr.ro/xsd, cele noi https://…), deci îl citim din document
    // în loc să-l presupunem.
    $ns      = $doc->getDocNamespaces(true);
    $default = $ns[''] ?? '';
    if ($default !== '') {
        $doc->registerXPathNamespace('x', $default);
        $cubes = $doc->xpath('//x:Cube') ?: [];
    } else {
        $cubes = $doc->xpath('//Cube') ?: [];
    }
    if (!$cubes) return 0;

    $pdo  = db();
    $stmt = $pdo->prepare("INSERT OR REPLACE INTO bnr_rates (date, currency, rate) VALUES (?, ?, ?)");

    $own = !$pdo->inTransaction();
    if ($own) $pdo->beginTransaction();
    $days = 0;
    try {
        foreach ($cubes as $cube) {
            $date = (string)$cube['date'];
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) continue;
            $days++;
            foreach ($cube->Rate as $r) {
                $cur = strtoupper(trim((string)$r['currency']));
                $val = (float)str_replace(',', '.', trim((string)$r));
                $mul = (float)($r['multiplier'] ?? 1);
                if ($cur === '' || $val <= 0) continue;
                if ($mul > 0) $val /= $mul;
                $stmt->execute([$date, $cur, $val]);
            }
        }
        if ($own) $pdo->commit();
    } catch (Throwable $e) {
        if ($own && $pdo->inTransaction()) $pdo->rollBack();
        throw $e;
    }
    return $days;
}

/** Când am descărcat ultima oară sursa dată (timestamp unix) sau 0. */
function bnr_last_fetch(string $source): int
{
    $t = db_scalar("SELECT fetched_at FROM bnr_fetch WHERE source = ?", [$source]);
    return $t ? (int)strtotime((string)$t) : 0;
}

function bnr_mark_fetch(string $source): void
{
    db_exec(
        "INSERT OR REPLACE INTO bnr_fetch (source, fetched_at) VALUES (?, ?)",
        [$source, date('Y-m-d H:i:s')]
    );
}

/** Descarcă fișierul anual (o singură dată la $maxAgeSec). */
function bnr_sync_year(int $year, int $maxAgeSec = 21600): bool
{
    $source = 'year:' . $year;
    if (time() - bnr_last_fetch($source) < $maxAgeSec) return false;

    $xml = bnr_http_get(sprintf(BNR_URL_YEAR, $year));
    if ($xml === null) return false;

    // Dacă n-am putut extrage nicio zi, nu marcăm reușita — altfel am aștepta
    // ore întregi până la următoarea încercare, cu cache-ul tot gol.
    if (bnr_store_xml($xml) === 0) {
        error_log('[bnr] fișierul anului ' . $year . ' nu a putut fi parsat');
        return false;
    }
    bnr_mark_fetch($source);
    return true;
}

/** Descarcă doar ziua curentă (fișier mic, pentru actualizări dese). */
function bnr_sync_latest(int $maxAgeSec = 3600): bool
{
    if (time() - bnr_last_fetch('latest') < $maxAgeSec) return false;

    $xml = bnr_http_get(BNR_URL_LATEST);
    if ($xml === null) return false;

    if (bnr_store_xml($xml) === 0) return false;
    bnr_mark_fetch('latest');
    return true;
}

/** Cursul din cache: ultima zi publicată ≤ $date. */
function bnr_cached(string $date, string $currency): ?array
{
    $row = db_select(
        "SELECT date, rate FROM bnr_rates
          WHERE currency = ? AND date <= ?
          ORDER BY date DESC LIMIT 1",
        [$currency, $date]
    );
    return $row ? ['date' => $row[0]['date'], 'rate' => (float)$row[0]['rate']] : null;
}

/**
 * Cursul BNR pentru o zi și o monedă.
 *
 * @return array{
 *   currency:string, rate:float, date:string, requested:string,
 *   exact:bool, stale:bool
 * }|null  null dacă moneda nu e cotată de BNR sau nu avem date deloc.
 *
 * `exact`  = BNR a publicat chiar în ziua cerută
 * `stale`  = ziua cerută e în viitor față de ultima publicare (ex. comandă
 *            datată în avans) — cursul returnat e cel mai recent disponibil.
 */
function bnr_rate(string $date, string $currency): ?array
{
    $currency = strtoupper(trim($currency));
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) $date = date('Y-m-d');
    if ($currency === '' || $currency === 'RON') return null;

    $hit = bnr_cached($date, $currency);

    // Lipsă sau prea vechi față de ziua cerută → încercăm să aducem de la BNR.
    // (BNR publică în zilele lucrătoare; o distanță de peste 5 zile înseamnă
    // aproape sigur că nu avem noi datele, nu că BNR n-a publicat.)
    $needsFetch = ($hit === null) || ((strtotime($date) - strtotime($hit['date'])) > 5 * 86400);
    if ($needsFetch) {
        $year = (int)substr($date, 0, 4);
        bnr_sync_year($year);
        if ($year !== (int)date('Y')) bnr_sync_year((int)date('Y'));
        bnr_sync_latest();
        $hit = bnr_cached($date, $currency);
    }
    if ($hit === null) return null;

    return [
        'currency'  => $currency,
        'rate'      => $hit['rate'],
        'date'      => $hit['date'],
        'requested' => $date,
        'exact'     => $hit['date'] === $date,
        'stale'     => $hit['date'] < $date && strtotime($date) > time(),
    ];
}
