<?php
/**
 * NIS2 Agile — Simulazione Flusso B2B Licenze
 *
 * Testa il ciclo completo: Mktg crea invito → Cliente registrazione ridotta
 * → Provision org → Login → Accesso dashboard + API Key.
 *
 * 6 Scenari:
 *   SIM-B1: Mktg login + creazione invito B2B con dati destinatario
 *   SIM-B2: Validazione invito pubblico → verifica dati pre-compilazione
 *   SIM-B3: Registrazione ridotta cliente (solo password, dati da invito)
 *   SIM-B4: Provision organizzazione post-register → org + API Key
 *   SIM-B5: Login cliente → verifica org caricata + membership
 *   SIM-B6: API Key da provision → dashboard overview + compliance-summary
 *
 * CLI: php simulate-nis2-b2b.php [--reset]
 * Web: public/simulate-b2b.html
 */

declare(strict_types=1);

define('SIM_B2B_VERSION', '1.0.0');
define('MKTG_EMAIL',      'sim-b2b-mktg@nis2agile.it');
define('MKTG_PWD',        'SimB2B2026!');
define('MKTG_NAME',       'Sim B2B Mktg');
define('CLIENTE_EMAIL',   'laura.bianchi@techstart.b2b.demo');
define('CLIENTE_PWD',     'TechStartB2B2026!');
define('CLIENTE_NOME',    'Laura');
define('CLIENTE_COGNOME', 'Bianchi');
define('CLIENTE_FULLNAME','Laura Bianchi');
define('CLIENTE_COMPANY', 'TechStart S.r.l.');
define('CLIENTE_PIVA',    '03456789012');
define('CLIENTE_SECTOR',  'ict');

$_sseMode = (bool)getenv('NIS2_SSE');
define('IS_CLI', php_sapi_name() === 'cli' && !$_sseMode);
define('IS_WEB', !IS_CLI);

if (IS_CLI || $_sseMode) {
    define('API_BASE', getenv('NIS2_API_BASE') ?: 'https://nis2.agile.software/api');
} else {
    $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host  = $_SERVER['HTTP_HOST'] ?? 'localhost';
    define('API_BASE', "{$proto}://{$host}/api");
}

if (IS_WEB) {
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('X-Accel-Buffering: no');
    ob_implicit_flush(true);
    while (ob_get_level()) ob_end_flush();
}

$S = [
    'mktg_jwt'    => null,
    'invite_token'=> null,
    'invite_id'   => null,
    'cliente_jwt' => null,
    'org_id'      => null,
    'api_key'     => null,
    'stats'       => ['pass' => 0, 'skip' => 0, 'fail' => 0, 'warn' => 0],
    'sim_start'   => microtime(true),
];

// ── Output helpers ──────────────────────────────────────────────────────────

function simLog(string $msg, string $type = 'info'): void
{
    static $lastHb = 0;
    $ts = date('H:i:s');
    if (IS_CLI) {
        $p = ['phase'=>"\033[34m══\033[0m",'ok'=>"\033[32m  ✓\033[0m",'skip'=>"\033[90m  →\033[0m",
              'warn'=>"\033[33m  ⚠\033[0m",'error'=>"\033[31m  ✗\033[0m",'info'=>'   '][$type] ?? '   ';
        echo "[$ts] {$p} {$msg}\n"; flush();
    } else {
        $now = time();
        if ($now - $lastHb >= 25) { echo ": heartbeat $ts\n\n"; $lastHb = $now; }
        echo 'data: ' . json_encode(['t' => $type, 'm' => "[$ts] $msg"]) . "\n\n"; flush();
    }
}

function simDone(array $stats): void
{
    $e = round(microtime(true) - $GLOBALS['S']['sim_start'], 1);
    $msg = "B2B completata in {$e}s — ✓{$stats['pass']} →{$stats['skip']} ⚠{$stats['warn']} ✗{$stats['fail']}";
    simLog($msg, IS_CLI ? 'phase' : 'info');
    if (IS_WEB) { echo 'data: ' . json_encode(['t' => 'done', 'stats' => $stats]) . "\n\n"; flush(); }
}

function simPhase(int $n, string $title): void
{
    if (IS_CLI) {
        $line = str_pad("SIM-B{$n}: {$title}", 60);
        echo "\n\033[34m╔════════════════════════════════════════════════════════════╗\033[0m\n";
        echo "\033[34m║ {$line} ║\033[0m\n";
        echo "\033[34m╚════════════════════════════════════════════════════════════╝\033[0m\n";
    } else { simLog("━━ SIM-B{$n}: {$title}", 'phase'); }
}

function ok(string $m): void   { global $S; $S['stats']['pass']++; simLog($m, 'ok'); }
function skip(string $m): void { global $S; $S['stats']['skip']++; simLog($m, 'skip'); }
function fail(string $m): void { global $S; $S['stats']['fail']++; simLog($m, 'error'); }
function warn(string $m): void { global $S; $S['stats']['warn']++; simLog($m, 'warn'); }
function info(string $m): void { simLog($m, 'info'); }

// ── API client ──────────────────────────────────────────────────────────────

function api(string $method, string $path, ?array $body = null,
             ?string $jwt = null, ?int $orgId = null, ?string $apiKey = null): array
{
    $ch = curl_init(API_BASE . $path);
    $h  = ['Content-Type: application/json', 'Accept: application/json'];
    if ($jwt)    $h[] = 'Authorization: Bearer ' . $jwt;
    if ($orgId)  $h[] = 'X-Organization-Id: ' . $orgId;
    if ($apiKey) $h[] = 'X-Api-Key: ' . $apiKey;
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>30,
        CURLOPT_HTTPHEADER=>$h, CURLOPT_CUSTOMREQUEST=>strtoupper($method),
        CURLOPT_SSL_VERIFYPEER=>false, CURLOPT_SSL_VERIFYHOST=>0]);
    if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE));
    $raw  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($raw === false) return ['success'=>false,'error'=>'cURL fallito','_http'=>0];
    $res = json_decode($raw, true) ?? ['success'=>false,'error'=>'JSON invalido','_raw'=>substr($raw,0,200)];
    $res['_http'] = $code;
    return $res;
}

function apiOk(array $res, string $ctx = ''): bool
{
    if (!empty($res['success'])) return true;
    warn('API Error' . ($ctx ? " [$ctx]" : '') . ': ' . ($res['error'] ?? $res['message'] ?? 'errore') . " (HTTP {$res['_http']})");
    return false;
}

// ── DB helpers ──────────────────────────────────────────────────────────────

function dbConnect(): ?PDO
{
    $envFile = __DIR__ . '/.env';
    if (!is_file($envFile)) return null;
    $env = [];
    foreach (file($envFile, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $line) {
        if (str_starts_with(trim($line), '#') || !str_contains($line, '=')) continue;
        [$k, $v] = explode('=', $line, 2);
        $env[trim($k)] = trim($v);
    }
    try {
        return new PDO(
            sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
                $env['DB_HOST']??'localhost', $env['DB_PORT']??'3306', $env['DB_NAME']??'nis2_agile_db'),
            $env['DB_USER']??'', $env['DB_PASS']??'',
            [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]
        );
    } catch (Throwable) { return null; }
}

function dbSeedMktgUser(): bool
{
    $pdo = dbConnect();
    if (!$pdo) return false;
    $hash = password_hash(MKTG_PWD, PASSWORD_DEFAULT);
    $pdo->prepare('INSERT INTO users (email,password_hash,full_name,role,is_active) VALUES (?,?,?,\'super_admin\',1)
                   ON DUPLICATE KEY UPDATE password_hash=VALUES(password_hash),role=\'super_admin\',is_active=1')
        ->execute([MKTG_EMAIL, $hash, MKTG_NAME]);
    return true;
}

function dbResetB2B(): void
{
    $pdo = dbConnect();
    if (!$pdo) { warn('Reset B2B non disponibile (DB non accessibile)'); return; }
    $pdo->prepare("DELETE FROM users WHERE email LIKE '%.b2b.demo%' OR email = ?")->execute([MKTG_EMAIL]);
    $pdo->exec("DELETE FROM organizations WHERE vat_number = '" . CLIENTE_PIVA . "' OR provisioned_by = 'sim-b2b'");
    $pdo->exec("DELETE FROM invites WHERE channel = 'sim-b2b'");
    info('Reset B2B completato');
}

// ── Reset ────────────────────────────────────────────────────────────────────

$args = IS_CLI ? array_slice($argv ?? [], 1) : [];
if (in_array('--reset', $args, true) || (IS_WEB && ($_GET['reset'] ?? '') === '1')) {
    simPhase(0, 'Reset dati B2B demo');
    dbResetB2B();
    simDone($S['stats']); exit;
}

// ══════════════════════════════════════════════════════════════════════════════
// SIMULAZIONE
// ══════════════════════════════════════════════════════════════════════════════

simLog('NIS2 Agile B2B Simulator v' . SIM_B2B_VERSION . ' — ' . API_BASE, 'phase');

// ── SIM-B1: Mktg crea invito ─────────────────────────────────────────────────
simPhase(1, 'Mktg crea invito B2B con dati destinatario');

dbSeedMktgUser()
    ? ok('Operatore mktg seeded: ' . MKTG_EMAIL)
    : warn('DB non accessibile — mktg user deve già esistere');

info('Login operatore mktg...');
$res = api('POST', '/auth/login', ['email' => MKTG_EMAIL, 'password' => MKTG_PWD]);
if (!apiOk($res, 'mktg-login')) { fail('Login mktg fallito'); simDone($S['stats']); exit(1); }
$S['mktg_jwt'] = $res['data']['access_token'];
ok('Login mktg OK (user_id: ' . ($res['data']['user']['id'] ?? '?') . ')');

// Cleanup inviti precedenti
$listRes = api('GET', '/invites?channel=sim-b2b&status=pending', null, $S['mktg_jwt']);
if (!empty($listRes['data']['invites'])) {
    foreach ($listRes['data']['invites'] as $old) api('DELETE', '/invites/' . $old['id'], null, $S['mktg_jwt']);
    info('  Rimossi ' . count($listRes['data']['invites']) . ' inviti pending precedenti');
}

info('Creazione invito B2B piano Professional 12 mesi...');
$res = api('POST', '/invites/create', [
    'plan' => 'professional', 'duration_months' => 12, 'invite_expires_days' => 30,
    'max_uses' => 1, 'label' => 'TechStart - Sim B2B', 'channel' => 'sim-b2b',
    'notes' => 'Invito simulazione B2B — auto-generato',
    'recipient_first_name' => CLIENTE_NOME,  'recipient_last_name' => CLIENTE_COGNOME,
    'recipient_email'      => CLIENTE_EMAIL, 'recipient_company'   => CLIENTE_COMPANY,
    'recipient_vat'        => CLIENTE_PIVA,
], $S['mktg_jwt']);

if (!apiOk($res, 'create-invite')) { fail('Creazione invito fallita'); simDone($S['stats']); exit(1); }
$invite = $res['data']['invites'][0] ?? null;
if (!$invite || empty($invite['token'])) { fail('Token invito mancante'); simDone($S['stats']); exit(1); }

$S['invite_token'] = $invite['token'];
$S['invite_id']    = $invite['id'];
ok('Invito creato: ' . $invite['token_prefix'] . '... (id=' . $invite['id'] . ')');
info('  Piano: ' . $invite['plan'] . ' · Durata: ' . $invite['duration_months'] . ' mesi');
info('  Destinatario: ' . CLIENTE_FULLNAME . ' <' . CLIENTE_EMAIL . '> · P.IVA: ' . CLIENTE_PIVA);
info('  invite_url: ' . ($invite['invite_url'] ?? 'n/a'));

// ── SIM-B2: Valida invito (pubblico, nessuna auth) ───────────────────────────
simPhase(2, 'Validazione invito pubblico (nessuna auth)');

info('POST /auth/validate-invite...');
$res = api('POST', '/auth/validate-invite', ['invite_token' => $S['invite_token']]);
if (!apiOk($res, 'validate-invite')) { fail('Validazione invito fallita'); simDone($S['stats']); exit(1); }
ok('Invito valido (HTTP ' . $res['_http'] . ')');

$vd = $res['data'];
info('  Piano: ' . ($vd['plan'] ?? '?') . ' · Durata: ' . ($vd['duration_months'] ?? '?') . ' mesi');

$recipient = $vd['recipient'] ?? null;
if ($recipient) {
    ok('Dati recipient presenti → form verrà pre-compilato');
    info('  Nome: ' . ($recipient['first_name'] ?? '?') . ' ' . ($recipient['last_name'] ?? '?'));
    info('  Email: ' . ($recipient['email'] ?? '?') . ' · P.IVA: ' . ($recipient['vat'] ?? '?'));
    if (!empty($recipient['first_name']) && !empty($recipient['last_name']) && !empty($recipient['email'])) {
        ok('Dati sufficienti per registrazione ridotta (solo password richiesta al cliente)');
    } else {
        warn('Dati incompleti — form completo sarà mostrato');
    }
} else {
    warn('Nessun dato recipient nell\'invito — form completo mostrato');
}

// Verifica via GET endpoint alternativo
$res2 = api('GET', '/invites/validate?token=' . urlencode($S['invite_token']));
!empty($res2['success'])
    ? ok('GET /invites/validate OK (HTTP ' . $res2['_http'] . ')')
    : warn('GET /invites/validate: ' . ($res2['error'] ?? 'n/a') . ' (HTTP ' . $res2['_http'] . ')');

// ── SIM-B3: Registrazione ridotta (solo password) ────────────────────────────
simPhase(3, 'Registrazione ridotta cliente (dati da invito, solo password)');

$pdo = dbConnect();
if ($pdo) {
    $pdo->prepare('DELETE FROM users WHERE email = ?')->execute([CLIENTE_EMAIL]);
    info('Utente cliente precedente rimosso');
}

info('POST /auth/register con invite_token + solo password...');
$res = api('POST', '/auth/register', [
    'email'        => CLIENTE_EMAIL,
    'password'     => CLIENTE_PWD,
    'full_name'    => CLIENTE_FULLNAME,
    'role'         => 'org_admin',
    'invite_token' => $S['invite_token'],
    'vat_number'   => CLIENTE_PIVA,
]);

if (!apiOk($res, 'register')) { fail('Registrazione cliente fallita'); simDone($S['stats']); exit(1); }

$S['cliente_jwt'] = $res['data']['access_token'] ?? null;
ok('Registrazione ridotta OK (HTTP ' . $res['_http'] . ')');
info('  user_id: ' . ($res['data']['user']['id'] ?? '?') . ' · role: ' . ($res['data']['user']['role'] ?? '?'));

// Verifica: nessuna org pre-provision
$meRes = api('GET', '/auth/me', null, $S['cliente_jwt']);
if (!empty($meRes['success'])) {
    $orgs = $meRes['data']['organizations'] ?? [];
    count($orgs) === 0
        ? ok('Pre-provision: nessuna org → accede a dashboard solo dopo provision')
        : warn('Attenzione: org già presente prima del provision: ' . count($orgs));
}

// ── SIM-B4: Provision org post-register ─────────────────────────────────────
simPhase(4, 'Provision organizzazione post-register (invite_token)');

info('POST /services/provision con invite_token...');
$provBody = [
    'invite_token' => $S['invite_token'],
    'company' => [
        'ragione_sociale'   => CLIENTE_COMPANY,
        'partita_iva'       => CLIENTE_PIVA,
        'forma_giuridica'   => 'S.r.l.',
        'sector'            => CLIENTE_SECTOR,
        'nis2_entity_type'  => 'important',
        'numero_dipendenti' => 35,
        'fatturato_annuo'   => 2500000,
        'sede_legale'       => 'Via Torino 42, 10100 Torino TO',
    ],
    'admin' => [
        'email'    => CLIENTE_EMAIL,
        'full_name'=> CLIENTE_FULLNAME,
        'phone'    => '+39 011 1234567',
        'title'    => 'CEO',
    ],
    'caller' => ['system' => 'sim-b2b'],
];

$res = api('POST', '/services/provision', $provBody);
if (!apiOk($res, 'provision')) { fail('Provision fallita'); simDone($S['stats']); exit(1); }

$prov = $res['data'];
$S['org_id']  = $prov['org_id']  ?? null;
$S['api_key'] = $prov['api_key'] ?? null;

ok('Provision OK (HTTP ' . $res['_http'] . ')');
info('  org_id: '          . $S['org_id']);
info('  org_name: '        . ($prov['org_name'] ?? '?'));
info('  entity_type: '     . ($prov['entity_type'] ?? '?'));
info('  api_key: '         . substr($S['api_key'] ?? '', 0, 24) . '...');
info('  api_key_scopes: '  . implode(', ', $prov['api_key_scopes'] ?? []));
info('  license_expires: ' . ($prov['license_expires_at'] ?? '?'));
info('  invito marcato: '  . ($prov['invite_id'] ?? '?'));

// Idempotenza
$res2 = api('POST', '/services/provision', $provBody);
(!empty($res2['success']) && ($res2['data']['org_id'] ?? null) === $S['org_id'])
    ? ok('Idempotenza OK — stessa org_id: ' . $S['org_id'])
    : warn('Idempotenza: risposta inattesa (HTTP ' . $res2['_http'] . ')');

// ── SIM-B5: Login → verifica org + accesso dashboard ────────────────────────
simPhase(5, 'Login cliente → verifica org attiva e accesso dashboard');

$res = api('POST', '/auth/login', ['email' => CLIENTE_EMAIL, 'password' => CLIENTE_PWD]);
if (!apiOk($res, 'cliente-login')) { fail('Login cliente fallito'); simDone($S['stats']); exit(1); }

$S['cliente_jwt'] = $res['data']['access_token'];
ok('Login cliente OK');

$loginOrgs = $res['data']['organizations'] ?? [];
if (count($loginOrgs) > 0) {
    ok('Organizzazioni nel login: ' . count($loginOrgs));
    foreach ($loginOrgs as $org) {
        info(sprintf('  org_id=%s  nome="%s"  ruolo=%s  settore=%s',
            $org['organization_id'] ?? '?', $org['name'] ?? '?',
            $org['role'] ?? '?', $org['sector'] ?? '?'));
    }
} else {
    fail('Nessuna org nel login — provision non collegato');
}

$meRes = api('GET', '/auth/me', null, $S['cliente_jwt']);
if (!empty($meRes['success'])) {
    ok('GET /auth/me OK — ' . count($meRes['data']['organizations'] ?? []) . ' org(s)');
    info('  must_change_password: ' . (($meRes['data']['must_change_password'] ?? false) ? 'SI' : 'NO (password propria)'));
}

$dash = api('GET', '/dashboard/overview', null, $S['cliente_jwt'], $S['org_id']);
!empty($dash['success'])
    ? ok('Dashboard overview OK (score: ' . ($dash['data']['compliance_score'] ?? 0) . '%)')
    : warn('Dashboard: ' . ($dash['error'] ?? $dash['message'] ?? 'n/a') . ' (HTTP ' . $dash['_http'] . ')');

// ── SIM-B6: API Key M2M ──────────────────────────────────────────────────────
simPhase(6, 'API Key da provision → accesso M2M (compliance-summary, risks-feed)');

if (!$S['api_key']) {
    skip('API Key non disponibile — SIM-B6 saltato');
} else {
    info('Accesso M2M con API Key: ' . substr($S['api_key'], 0, 24) . '...');

    $r = api('GET', '/services/compliance-summary', null, null, $S['org_id'], $S['api_key']);
    !empty($r['success'])
        ? ok('GET /services/compliance-summary OK — score: ' . ($r['data']['overall_score'] ?? 0) . '%')
        : warn('compliance-summary: ' . ($r['error'] ?? 'n/a') . ' (HTTP ' . $r['_http'] . ')');

    $r = api('GET', '/services/risks-feed', null, null, $S['org_id'], $S['api_key']);
    !empty($r['success'])
        ? ok('GET /services/risks-feed OK — rischi: ' . count($r['data']['risks'] ?? []))
        : warn('risks-feed: ' . ($r['error'] ?? 'n/a') . ' (HTTP ' . $r['_http'] . ')');

    $r = api('GET', '/services/status', null, null, $S['org_id'], $S['api_key']);
    if (!empty($r['success'])) {
        ok('GET /services/status OK — API Key attiva e valida');
        info('  org: '     . ($r['data']['organization']['name'] ?? '?'));
        info('  scopes: '  . implode(', ', $r['data']['api_key']['scopes'] ?? []));
        info('  expires: ' . ($r['data']['api_key']['expires_at'] ?? '?'));
    } else {
        warn('services/status: ' . ($r['error'] ?? 'n/a') . ' (HTTP ' . $r['_http'] . ')');
    }

    ok('Flusso M2M completo — sistema partner può sincronizzare via API Key');
}

// ── Riepilogo ────────────────────────────────────────────────────────────────
simLog('', 'info');
simLog('══ RIEPILOGO FLUSSO B2B ═══════════════════════════════════', 'phase');
simLog('Mktg:    ' . MKTG_EMAIL, 'info');
simLog('Invito:  ' . substr($S['invite_token'] ?? 'n/a', 0, 20) . '...', 'info');
simLog('Cliente: ' . CLIENTE_EMAIL, 'info');
simLog('Org:     id=' . ($S['org_id'] ?? 'n/a') . ' — ' . CLIENTE_COMPANY, 'info');
simLog('API Key: ' . substr($S['api_key'] ?? 'n/a', 0, 24) . '...', 'info');
simLog('', 'info');

$st = $S['stats'];
simLog("Risultato: ✓{$st['pass']} pass  →{$st['skip']} skip  ⚠{$st['warn']} warn  ✗{$st['fail']} fail", 'phase');
simDone($S['stats']);
