<?php
// index.php — OAuth2 ANAF eFactura: obtinere token automat + compatibilitate retro
// FR-1 (propagare state), FR-2 (sesiune primara), FR-3 (stocare tokenuri),
// FR-4 (retro-compat fara state), FR-10 (securitate), FR-11 (protectie stocare)
//
// Modificat: 2026-07-07 — implementare flux automat (stocare fisier + pagina succes)

// === Credentiale ANAF (pastrate din versiunea anterioara) ===
$client_id     = 'f4c59081e488ac326f32f8ab52620023e996dd82d7d01163';
$client_secret = 'e65b9127acac254c5b9bfbbe68b9a6ca07b0d6df6dbb0023e996dd82d7d01163';
$redirect_uri  = 'https://romfast.ro/oauth2/';

// === Director tokenuri (FR-11) ===
// oauth2/tokens/ — protejat explicit cu .htaccess (Require all denied), deoarece
// se afla in interiorul docroot-ului (public_html/oauth2/tokens), nu deasupra lui.
// Codul creaza directorul si .htaccess-ul daca lipsesc (ex. prima instalare)
$tokens_dir = __DIR__ . '/tokens';
if (!is_dir($tokens_dir)) {
    @mkdir($tokens_dir, 0700, true);
}
$tokens_htaccess = $tokens_dir . '/.htaccess';
if (!file_exists($tokens_htaccess)) {
    @file_put_contents($tokens_htaccess, "Require all denied\n");
}

// ===================================================================
// Functii helper
// ===================================================================

// Validare format state: lungime fixa 64, doar [A-Za-z0-9] (protectie path traversal - NFR-1)
function validState($state) {
    return is_string($state)
        && strlen($state) === 64
        && (bool)preg_match('/^[A-Za-z0-9]+$/', $state);
}

// Afisare pagina HTML simpla (§3.3: UTF-8 cu diacritice, H1 mare scanabil,
// font >=16px, contrast inalt, un singur job per pagina, fara linkuri/butoane)
function showPage($page_title, $h1_text, $h1_color, $body_text) {
    header('Content-Type: text/html; charset=utf-8');
    echo '<!DOCTYPE html>' . "\n";
    echo '<html lang="ro"><head>' . "\n";
    echo '<meta charset="utf-8">' . "\n";
    echo '<meta name="viewport" content="width=device-width,initial-scale=1">' . "\n";
    echo '<title>' . htmlspecialchars($page_title, ENT_QUOTES, 'UTF-8') . '</title>' . "\n";
    echo '<style>' . "\n";
    echo 'body{font-family:Arial,Helvetica,sans-serif;font-size:18px;margin:3em auto;' . "\n";
    echo '     max-width:640px;padding:0 1.5em;background:#f7f7f7;color:#222}' . "\n";
    echo 'h1{font-size:2em;color:' . $h1_color . ';margin-bottom:0.5em}' . "\n";
    echo 'p{font-size:1em;line-height:1.7;margin:0}' . "\n";
    echo '.brand{font-size:0.8em;color:#999;margin-bottom:2.5em}' . "\n";
    echo '</style>' . "\n";
    echo '</head><body>' . "\n";
    echo '<p class="brand">ROA Romfast</p>' . "\n";
    echo '<h1>' . htmlspecialchars($h1_text,   ENT_QUOTES, 'UTF-8') . '</h1>' . "\n";
    echo '<p>'  . htmlspecialchars($body_text,  ENT_QUOTES, 'UTF-8') . '</p>'  . "\n";
    echo '</body></html>' . "\n";
}

// ===================================================================
// Citire explicita parametri GET (FR-10: elimina parse_str care injecta variabile)
// ===================================================================
$refresh_token     = isset($_GET['refresh_token'])      ? (string)$_GET['refresh_token']      : '';
$code              = isset($_GET['code'])               ? (string)$_GET['code']               : '';
$state_param       = isset($_GET['state'])              ? (string)$_GET['state']              : '';
$error_param       = isset($_GET['error'])              ? (string)$_GET['error']              : '';
$error_description = isset($_GET['error_description'])  ? (string)$_GET['error_description']  : '';

// ===================================================================
// Ramura REFRESH TOKEN — neatinsa functional (FR-4)
// Apelata din ROA direct cu ?refresh_token=... pentru prelungirea tokenului
// ===================================================================
if (!empty($refresh_token)) {
    $url = 'https://logincert.anaf.ro/anaf-oauth2/v1/token';
    $fields = [
        'client_id'     => $client_id,
        'client_secret' => $client_secret,
        'refresh_token' => $refresh_token,
        'redirect_uri'  => $redirect_uri,
        'grant_type'    => 'refresh_token',
    ];
    $fields_string = http_build_query($fields);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL,            $url);
    curl_setopt($ch, CURLOPT_POST,           true);
    curl_setopt($ch, CURLOPT_POSTFIELDS,     $fields_string);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    curl_setopt($ch, CURLOPT_TIMEOUT,        30);

    $jsonobj = curl_exec($ch);
    curl_close($ch);

    header('Content-Type: application/json');
    // La eroare curl: corp gol, identic cu comportamentul vechi (FR-4: ramura neatinsa)
    echo ($jsonobj !== false) ? $jsonobj : '';
    exit();
}

// ===================================================================
// Configurare cookie de sesiune PHP (FR-2)
// session_set_cookie_params TREBUIE sa preceada session_start pe AMBELE intrari:
// - intrarea initiala (fara ?code): browser deschis de ROACONT
// - callback-ul ANAF (cu ?code): navigare top-level cross-site
//
// SameSite=Lax este obligatoriu: callback-ul ANAF este o navigare top-level dintr-un
// alt domeniu (logincert.anaf.ro -> romfast.ro) — cu SameSite=Strict cookie-ul nu ar
// fi trimis in callback si legarea sesiune ar esua silentios.
// ===================================================================
if (PHP_VERSION_ID >= 70300) {
    // Sintaxa cu array: disponibila incepand cu PHP 7.3
    session_set_cookie_params([
        'lifetime' => 0,
        'path'     => '/',
        'secure'   => true,
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
} else {
    // Fallback PHP < 7.3: SameSite injectat ca sufix al parametrului path
    session_set_cookie_params(0, '/; SameSite=Lax', '', true, true);
}
session_start();

// ===================================================================
// Callback de eroare ANAF (?error=...) — FR-10
// Tratare explicita; nu se re-redirecteaza la authorize (ar produce bucla)
// ===================================================================
if (!empty($error_param)) {
    // Extrage state-ul din sesiune pentru a scrie fisierul de eroare
    $sess_state = null;
    if (!empty($_SESSION['roa_state']) && is_array($_SESSION['roa_state'])) {
        $sess = $_SESSION['roa_state'];
        if (!empty($sess['value']) && (time() - (int)$sess['ts']) < 900) {
            $sess_state = $sess['value'];
        }
    }
    unset($_SESSION['roa_state']);
    session_write_close();

    // Scrie fisier de eroare pentru state-ul din sesiune (daca exista si valid)
    if ($sess_state !== null && validState($sess_state)) {
        $fname      = hash('sha256', $sess_state);
        $token_file = $tokens_dir . '/' . $fname . '.json';
        $tmp_file   = $tokens_dir . '/' . $fname . '.tmp';
        // Anti-suprascriere: nu suprascrie un fisier de succes existent (FR-10)
        if (!file_exists($token_file)) {
            $err_data = json_encode([
                'error'             => $error_param,
                'error_description' => $error_description,
            ]);
            if ($err_data !== false && file_put_contents($tmp_file, $err_data) !== false) {
                @rename($tmp_file, $token_file);
            }
        }
    }

    // Pagina de eroare (§3.3: un job, fara retry link, fara re-redirect)
    $cause = !empty($error_description) ? $error_description : $error_param;
    showPage(
        'Autorizare nereușită',
        'Autorizarea nu a reușit',
        '#c0392b',
        $cause . ' Reveniți în aplicația ROA — de acolo puteți relua operația sau folosi completarea manuală.'
    );
    exit();
}

// ===================================================================
// Intrare INITIALA — nicio sesiune, fara ?code: redirect catre ANAF authorize
// ===================================================================
if (empty($code)) {
    if (!empty($state_param)) {
        // Validare format state inainte de orice operatie (protectie path traversal)
        if (!validState($state_param)) {
            session_write_close();
            // L1: H1 conform §3.3 ("Autorizarea nu a reușit" pentru toate paginile de eroare)
            showPage(
                'Autorizare nereușită',
                'Autorizarea nu a reușit',
                '#c0392b',
                'Parametru de sesiune invalid. Reluați operația din aplicația ROA.'
            );
            exit();
        }

        // Salveaza state in sesiune cu timestamp (mecanism PRIMAR de legare)
        $_SESSION['roa_state'] = ['value' => $state_param, 'ts' => time()];
        session_write_close();

    } else {
        // FR-2 igienă sesiune: fara state in URL = flux manual/retro-compat
        // Curata orice roa_state ramas dintr-o tentativa automata anterioara esuata
        // (altfel, la callback, am afisa pagina de succes fara tokenuri = fund de sac)
        unset($_SESSION['roa_state']);
        session_write_close();
    }

    // URL authorize ANAF — client_secret NU se trimite la authorize (FR-10)
    // client_secret apartine doar schimbului code->token (POST, fara URL)
    $url_auth  = 'https://logincert.anaf.ro/anaf-oauth2/v1/authorize';
    $url_auth .= '?client_id='          . urlencode($client_id);
    $url_auth .= '&response_type=code';
    $url_auth .= '&redirect_uri='       . urlencode($redirect_uri);
    $url_auth .= '&token_content_type=jwt';
    // FR-1: propagare state la ANAF daca exista
    // Nota: doc ANAF (pag.23) spune "State se lasa necompletat" — propagarea e incerta
    // dar trimitem oricum; legarea primara ramane sesiunea PHP
    if (!empty($state_param)) {
        $url_auth .= '&state=' . urlencode($state_param);
    }

    header('Location: ' . $url_auth);
    exit();
}

// ===================================================================
// Callback cu cod de autorizare (?code=...) — schimb code -> token
// ===================================================================

// FR-2: identifica state-ul din sesiune (mecanismul PRIMAR de legare)
// state-ul din sesiune = cel generat de ROACONT si salvat la intrarea initiala
$sess_state = null;
if (!empty($_SESSION['roa_state']) && is_array($_SESSION['roa_state'])) {
    $sess = $_SESSION['roa_state'];
    if (!empty($sess['value']) && (time() - (int)$sess['ts']) < 900) {
        // roa_state valid (sub 15 minute)
        $sess_state = $sess['value'];
    }
    // roa_state mai vechi de 15 minute se ignora (si se sterge mai jos)
}
// Consuma state-ul din sesiune (indiferent de validitate — evita re-utilizare)
unset($_SESSION['roa_state']);

// FR-2: validare CSRF oportunistica
// Daca ANAF a propagat state in callback (comportament incert), trebuie sa coincida cu sesiunea
// Nepotrivire = posibil CSRF sau sesiune gresita -> eroare, fara stocare tokenuri
if (!empty($state_param) && $sess_state !== null) {
    if ($state_param !== $sess_state) {
        session_write_close();
        showPage(
            'Eroare de securitate',
            'Autorizarea nu a reușit',
            '#c0392b',
            'Identificatorul de sesiune nu corespunde. Operația a fost anulată din motive de securitate. ' .
            'Reveniți în aplicația ROA și reluați de acolo.'
        );
        exit();
    }
}

// FR-11 / C10: elibereaza lock-ul de sesiune inainte de curl
// ANAF poate raspunde lent; nu tinem lock-ul de sesiune blocat in acest timp
session_write_close();

// Schimb cod de autorizare -> token (FR-10: cu timeouts + tratare erori)
$url_token = 'https://logincert.anaf.ro/anaf-oauth2/v1/token';
$fields_token = [
    'client_id'          => $client_id,
    'client_secret'      => $client_secret,
    'code'               => $code,
    'redirect_uri'       => $redirect_uri,
    'grant_type'         => 'authorization_code',
    'token_content_type' => 'jwt',
];
$fields_string = http_build_query($fields_token);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,            $url_token);
curl_setopt($ch, CURLOPT_POST,           true);
curl_setopt($ch, CURLOPT_POSTFIELDS,     $fields_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);  // FR-10: timeout conectare
curl_setopt($ch, CURLOPT_TIMEOUT,        30);  // FR-10: timeout total

$jsonobj    = curl_exec($ch);
$curl_errno = curl_errno($ch);
$http_code  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// FR-10: tratare explicita erori curl / HTTP != 200 / JSON de eroare
// (inainte: un esec afisa tokenuri goale)
$error_msg  = null;   // text uman (pagina browser + error_description)
$err_code   = null;   // cod scurt ASCII citit de ROACONT (loJson.error -> M12)
$token_data = null;

if ($curl_errno !== 0) {
    $err_code  = 'curl_error';
    $error_msg = 'Eroare de rețea la contactarea ANAF (curl errno: ' . (int)$curl_errno . ').';
} elseif ($http_code !== 200) {
    $err_code  = 'http_error';
    $error_msg = 'ANAF a răspuns cu codul HTTP ' . (int)$http_code . '.';
} else {
    $token_data = json_decode((string)$jsonobj, true);
    if ($token_data === null) {
        $err_code  = 'json_invalid';
        $error_msg = 'Răspunsul ANAF nu a putut fi decodificat (JSON invalid).';
    } elseif (!empty($token_data['error'])) {
        $err_code = $token_data['error'];   // cod brut ANAF: invalid_grant, access_denied etc.
        $err_desc = !empty($token_data['error_description']) ? $token_data['error_description'] : '';
        $error_msg = 'ANAF: ' . $err_code . ($err_desc ? ' — ' . $err_desc : '') . '.';
    }
}

// ===================================================================
// Scriere fisier tokenuri (FR-3, FR-11)
// Se efectueaza DOAR daca sesiunea avea un state valid (flux automat)
// ===================================================================
if ($sess_state !== null && validState($sess_state)) {
    $fname      = hash('sha256', $sess_state);
    $token_file = $tokens_dir . '/' . $fname . '.json';
    $tmp_file   = $tokens_dir . '/' . $fname . '.tmp';

    if ($error_msg !== null) {
        // Eroare: scrie fisier de eroare
        // FR-10 anti-suprascriere: nu suprascrie un token de succes existent
        // (ex. refresh pe pagina callback dupa succes: code expirat -> eroare -> nu stergem tokenul bun)
        if (!file_exists($token_file)) {
            $err_data = json_encode([
                'error'             => $err_code,    // cod scurt ASCII citit de ROACONT (fara mojibake)
                'error_description' => $error_msg,   // text uman UTF-8 (diagnostic)
            ]);
            if ($err_data !== false && file_put_contents($tmp_file, $err_data) !== false) {
                @rename($tmp_file, $token_file);
            }
        }
    } else {
        // Succes: scriere atomica a tokenurilor
        $json_out  = json_encode($token_data, JSON_UNESCAPED_SLASHES);
        $write_ok  = false;
        if ($json_out !== false && file_put_contents($tmp_file, $json_out) !== false) {
            // @rename() pe acelasi filesystem = atomic (pick.php nu poate citi JSON partial)
            if (@rename($tmp_file, $token_file)) {
                $write_ok = true;
            } else {
                @unlink($tmp_file); // curata .tmp la esec rename; TTL il curata oricum
            }
        }
        if (!$write_ok) {
            // Scriere esuata — afisam eroare (nu succes fals); ROACONT va detecta timeout
            $error_msg = 'Tokenul ANAF a fost primit dar nu a putut fi salvat temporar pe server. Reveniți în aplicație și reluați operația.';
        }
    }
}

// ===================================================================
// Afisare pagina rezultat (§3.3)
// ===================================================================
if ($sess_state !== null) {
    // Flux automat (state in sesiune): afiseaza succes sau eroare
    // Tokenurile NU apar in pagina — sunt ridicate de pick.php
    if ($error_msg !== null) {
        showPage(
            'Autorizare nereușită',
            'Autorizarea nu a reușit',
            '#c0392b',
            $error_msg .
            ' Reveniți în aplicația ROA — de acolo puteți relua operația sau folosi completarea manuală.'
        );
    } else {
        // §3.3 pagina succes: H1 verde, corp cu instructiunea de revenire in ROA
        showPage(
            'Autorizare reușită',
            'Autorizare reușită',
            '#27ae60',
            'Reveniți în aplicația ROA — tokenul se preia automat. Puteți închide această pagină.'
        );
    }
} else {
    // FR-4 retro-compat: fara state in sesiune = versiune veche ROACONT sau sesiune expirata
    // Comportamentul ACTUAL ramas neschimbat: tokenurile apar in pagina pentru copiere manuala
    if ($error_msg !== null) {
        echo '<p>Eroare la obtinerea tokenului: ' . htmlspecialchars($error_msg, ENT_QUOTES, 'UTF-8') . '</p>';
    } else {
        echo '<p>Copiati ANAF Access Token si Refresh Token de pe liniile urmatoare si completati-le in aplicatie</p>';
        echo '<p><b>Access Token:</b></p><p>';
        echo htmlspecialchars((string)(isset($token_data['access_token'])  ? $token_data['access_token']  : ''), ENT_QUOTES, 'UTF-8');
        echo '</p>';
        echo '<p><b>Refresh Token:</b></p><p>';
        echo htmlspecialchars((string)(isset($token_data['refresh_token']) ? $token_data['refresh_token'] : ''), ENT_QUOTES, 'UTF-8');
        echo '</p>';
    }
}
