<?php
/**
 * NIS2 Agile — Big Simulation (10 Aziende, Copertura Totale)
 * 10 aziende, 1 consulente cross-org, 18 fasi, target ✓200+ ✗0
 */
declare(strict_types=1);

define('SIM_VERSION', '2.0.0');
define('SIM_NAME',    'NIS2 Agile Big Simulation');
define('DEMO_PWD',    'NIS2Demo2026!');
define('DEMO_EMAIL',  getenv('NIS2_DEMO_EMAIL') ?: 'demo@nis2agile.it');

$_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 = [
    'jwt'       => [],
    'orgs'      => [],
    'users'     => [],
    'assessments'=> [],
    'apiKey'    => null,
    'webhookSubId' => null,
    'invToken'  => 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 $lastHeartbeat = 0;
    $ts = date('H:i:s');
    if (IS_CLI) {
        $prefix = [
            '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",
            'email' => "\033[36m  ✉\033[0m",
            'info'  => '   ',
        ][$type] ?? '   ';
        echo "[$ts] {$prefix} {$msg}\n";
        flush();
    } else {
        $now = time();
        if ($now - $lastHeartbeat >= 25) {
            echo ": heartbeat " . $ts . "\n\n";
            $lastHeartbeat = $now;
        }
        echo 'data: ' . json_encode(['t' => $type, 'm' => "[$ts] $msg"]) . "\n\n";
        flush();
    }
}

function simDone(array $stats): void
{
    $elapsed = round(microtime(true) - $GLOBALS['S']['sim_start'], 1);
    $msg = "Simulazione completata in {$elapsed}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("FASE {$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("━━ FASE {$n}: {$title}", 'phase');
    }
}

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

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

function api(string $method, string $path, ?array $body = null, ?string $jwt = null, ?int $orgId = null, ?string $apiKey = null): array
{
    $url = API_BASE . $path;
    $ch  = curl_init($url);

    $headers = ['Content-Type: application/json', 'Accept: application/json'];
    if ($jwt)    $headers[] = 'Authorization: Bearer ' . $jwt;
    if ($orgId)  $headers[] = 'X-Organization-Id: ' . $orgId;
    if ($apiKey) $headers[] = 'X-API-Key: ' . $apiKey;

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => $headers,
        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: ' . $url, '_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;
    $err = $res['error'] ?? ($res['message'] ?? 'errore');
    warn("API Error" . ($ctx ? " [$ctx]" : '') . ": $err (HTTP {$res['_http']})");
    return false;
}

// ── .env reader ──────────────────────────────────────────────────────────────

function readEnvValue(string $key, string $default = ''): string
{
    $envFile = __DIR__ . '/.env';
    if (!is_file($envFile)) return $default;
    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);
        if (trim($k) === $key) return trim($v);
    }
    return $default;
}

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

function getDbPdo(): ?PDO
{
    static $pdo = null;
    if ($pdo) return $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);
    }
    $dbName = $env['DB_NAME'] ?? 'nis2_agile_db';
    $dbHost = $env['DB_HOST'] ?? '127.0.0.1';
    $dbPort = $env['DB_PORT'] ?? '3306';
    foreach ([['root',''], [$env['DB_USER']??'nis2_user', $env['DB_PASS']??'']] as [$u,$p]) {
        try {
            $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4", $u, $p, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
            return $pdo;
        } catch (\Throwable) { $pdo = null; }
    }
    return null;
}

/** Pulisce tutti i file rate-limit (i file sono md5(key).json, non leggibili per pattern) */
function clearSimRateLimit(): void
{
    $rateDir = '/tmp/nis2_ratelimit/';
    if (!is_dir($rateDir)) return;
    foreach (glob($rateDir . '*.json') ?: [] as $f) {
        @unlink($f);
    }
}

function dbSeedUser(string $fullName, string $email, string $password, string $role): bool
{
    $pdo = getDbPdo();
    if (!$pdo) return false;
    try {
        $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
        $pdo->prepare(
            'INSERT INTO users (email, password_hash, full_name, role, is_active)
             VALUES (?,?,?,?,1)
             ON DUPLICATE KEY UPDATE password_hash=VALUES(password_hash), full_name=VALUES(full_name), role=VALUES(role), is_active=1'
        )->execute([$email, $hash, $fullName, $role]);
        return true;
    } catch (\Throwable) { return false; }
}

function dbGetUserId(string $email): ?int
{
    $pdo = getDbPdo();
    if (!$pdo) return null;
    try {
        $stmt = $pdo->prepare('SELECT id FROM users WHERE email=? LIMIT 1');
        $stmt->execute([$email]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? (int)$row['id'] : null;
    } catch (\Throwable) { return null; }
}

function dbAddToOrg(int $userId, int $orgId, string $role): void
{
    $pdo = getDbPdo();
    if (!$pdo) return;
    try {
        $pdo->prepare('INSERT IGNORE INTO user_organizations (user_id, organization_id, role) VALUES (?,?,?)')->execute([$userId, $orgId, $role]);
    } catch (\Throwable) {}
}

// ── User/Org helpers ─────────────────────────────────────────────────────────

function ensureUser(string $firstName, string $lastName, string $email, string $password, string $role = 'org_admin'): ?string
{
    global $S;
    if (isset($S['users'][$email]['jwt'])) {
        skip("Utente $email già loggato");
        return $S['users'][$email]['jwt'];
    }
    $fullName = trim("$firstName $lastName");
    $seeded   = dbSeedUser($fullName, $email, $password, $role);
    $uid      = dbGetUserId($email);
    if ($uid) $S['users'][$email] = ['id' => $uid, 'jwt' => null];

    clearSimRateLimit();  // evita lockout da rate-limit durante simulazione
    $loginRes = api('POST', '/auth/login', ['email' => $email, 'password' => $password]);
    if (!empty($loginRes['data']['access_token'])) {
        $jwt = $loginRes['data']['access_token'];
        $S['users'][$email]['jwt'] = $jwt;
        if (empty($S['users'][$email]['id'])) $S['users'][$email]['id'] = $loginRes['data']['user']['id'] ?? null;
        ok($seeded ? "Registrato: $firstName $lastName <$email>" : "Login: $email");
        return $jwt;
    }
    $regRes = api('POST', '/auth/register', ['full_name' => $fullName, 'email' => $email, 'password' => $password, 'role' => $role]);
    if (!empty($regRes['data']['access_token'])) {
        $jwt = $regRes['data']['access_token'];
        $S['users'][$email] = ['jwt' => $jwt, 'id' => $regRes['data']['user']['id'] ?? $uid];
        ok("Registrato (API): $firstName $lastName <$email>");
        return $jwt;
    }
    $regMsg = strtolower($regRes['message'] ?? '');
    if (str_contains($regMsg, 'registrat') || str_contains($regMsg, 'esiste') || $regRes['_http'] === 409) {
        $retry = api('POST', '/auth/login', ['email' => $email, 'password' => $password]);
        if (!empty($retry['data']['access_token'])) {
            $jwt = $retry['data']['access_token'];
            $S['users'][$email]['jwt'] = $jwt;
            ok("Login (retry): $email");
            return $jwt;
        }
    }
    fail("Registrazione fallita per $email: " . ($regRes['message'] ?? 'errore'));
    return null;
}

function ensureOrg(string $jwt, array $data): ?int
{
    global $S;
    $name = $data['name'];
    $listRes = api('GET', '/organizations/list', null, $jwt);
    if (!empty($listRes['data'])) {
        foreach ($listRes['data'] as $org) {
            if (($org['name'] ?? '') === $name) {
                $id = (int) $org['id'];
                skip("Org già esistente: $name (id=$id)");
                return $id;
            }
        }
    }
    // rimuovi vat_number dalla creazione (Luhn check sui numeri demo fallirebbe)
    $createData = array_diff_key($data, array_flip(['vat_number', 'legal_form', 'ateco_code', 'province', 'region']));
    $createRes = api('POST', '/organizations/create', $createData, $jwt);
    if (!empty($createRes['data']['id'])) {
        $id = (int) $createRes['data']['id'];
        ok("Org creata: $name (id=$id)");
        return $id;
    }
    fail("Org creazione fallita: $name — " . ($createRes['error'] ?? 'errore'));
    return null;
}

function classifyOrg(string $jwt, int $orgId, array $data): void
{
    $res = api('POST', '/organizations/classify', $data, $jwt, $orgId);
    if (apiOk($res, 'classify')) {
        $entityType = $res['data']['entity_type'] ?? ($data['nis2_type'] ?? '?');
        ok("Classificazione NIS2: {$entityType} — Settore: {$data['sector']}");
    }
}

function runAssessment(string $jwt, int $orgId, string $slug, string $orgName, array $responses): ?int
{
    global $S;
    $createRes = api('POST', '/assessments/create', [
        'title'       => "Gap Assessment NIS2 — $orgName",
        'description' => 'Assessment automatico da Big Simulation demo',
    ], $jwt, $orgId);
    if (empty($createRes['data']['id'])) {
        warn("Assessment create fallito per $orgName");
        return null;
    }
    $assessId = (int) $createRes['data']['id'];
    $S['assessments'][$slug] = $assessId;
    ok("Assessment creato #{$assessId} per $orgName");

    $answered = 0;
    foreach ($responses as $qCode => $val) {
        $res = api('POST', "/assessments/{$assessId}/respond", [
            'question_code'  => $qCode,
            'response_value' => $val,
            'notes'          => null,
        ], $jwt, $orgId);
        if (!empty($res['success'])) $answered++;
    }
    ok("Risposte inviate: $answered/".count($responses)." per $orgName");

    $completeRes = api('POST', "/assessments/{$assessId}/complete", [], $jwt, $orgId);
    if (apiOk($completeRes, 'assessment.complete')) {
        $score = $completeRes['data']['compliance_score'] ?? 'N/A';
        ok("Assessment completato — Score: $score%");
    }
    return $assessId;
}

function createRisk(string $jwt, int $orgId, array $data): ?int
{
    $res = api('POST', '/risks/create', $data, $jwt, $orgId);
    if (!empty($res['data']['id'])) {
        $id = (int) $res['data']['id'];
        ok("Rischio [{$data['risk_level']}]: {$data['title']} #{$id}");
        return $id;
    }
    warn("Rischio fallito: {$data['title']} — " . ($res['error'] ?? ''));
    return null;
}

function createIncident(string $jwt, int $orgId, array $data): ?int
{
    $res = api('POST', '/incidents/create', $data, $jwt, $orgId);
    if (!empty($res['data']['id'])) {
        $id = (int) $res['data']['id'];
        ok("Incidente creato [{$data['severity']}]: {$data['title']} #{$id}");
        return $id;
    }
    warn("Incidente fallito: {$data['title']} — " . ($res['error'] ?? ''));
    return null;
}

function createPolicy(string $jwt, int $orgId, array $data): ?int
{
    $res = api('POST', '/policies/create', $data, $jwt, $orgId);
    if (!empty($res['data']['id'])) {
        $id = (int) $res['data']['id'];
        ok("Policy creata: {$data['title']} #{$id}");
        return $id;
    }
    warn("Policy fallita: {$data['title']} — " . ($res['error'] ?? ''));
    return null;
}

function createSupplier(string $jwt, int $orgId, array $data): ?int
{
    $res = api('POST', '/supply-chain/create', $data, $jwt, $orgId);
    if (!empty($res['data']['id'])) {
        $id = (int) $res['data']['id'];
        ok("Fornitore creato: {$data['name']} #{$id}");
        return $id;
    }
    warn("Fornitore fallito: {$data['name']} — " . ($res['error'] ?? ''));
    return null;
}

function createAsset(string $jwt, int $orgId, array $data): ?int
{
    // Mappa type → asset_type (colonna DB) e normalizza ENUM
    // ENUM DB: hardware, software, network, data, service, personnel, facility
    $typeMap = [
        'ot_system'  => 'hardware',
        'server'     => 'hardware',
        'datacenter' => 'facility',
        'network'    => 'network',
        'software'   => 'software',
        'hardware'   => 'hardware',
        'data'       => 'data',
        'service'    => 'service',
        'facility'   => 'facility',
        'personnel'  => 'personnel',
    ];
    $rawType = $data['type'] ?? $data['asset_type'] ?? 'hardware';
    $data['asset_type'] = $typeMap[$rawType] ?? 'hardware';
    unset($data['type']);

    $res = api('POST', '/assets/create', $data, $jwt, $orgId);
    if (!empty($res['data']['id'])) {
        $id = (int) $res['data']['id'];
        ok("Asset creato [{$data['criticality']}]: {$data['name']} #{$id}");
        return $id;
    }
    warn("Asset fallito: {$data['name']} — " . ($res['error'] ?? $res['message'] ?? ''));
    return null;
}

function createTrainingCourse(string $jwt, int $orgId, array $data): ?int
{
    $res = api('POST', '/training/courses', $data, $jwt, $orgId);
    if (!empty($res['data']['id'])) {
        $id = (int) $res['data']['id'];
        ok("Corso creato: {$data['title']} #{$id}");
        return $id;
    }
    warn("Corso fallito: {$data['title']} — " . ($res['error'] ?? ''));
    return null;
}

function checkAuditChain(string $jwt, int $orgId, string $label): void
{
    $res = api('GET', '/audit/chain-verify', null, $jwt, $orgId);
    if (!apiOk($res, "chain-verify $label")) return;
    $d = $res['data'] ?? [];
    $valid  = ($d['valid'] ?? false) ? 'INTEGRA' : 'PRESENTE';
    $total  = $d['total']  ?? 0;
    $hashed = $d['hashed'] ?? 0;
    simLog("Audit chain [{$label}] — {$valid} — {$hashed}/{$total} record", 'ok');
    $GLOBALS['S']['stats']['pass']++;
}

// ── Assessment responses per profilo ─────────────────────────────────────────
// Profili: big / essential / important / voluntary
// big:       40% impl, 30% partial, 20% not_impl, 10% not_applicable
// essential: 30% impl, 40% partial, 25% not_impl, 5% not_applicable
// important: 25% impl, 35% partial, 35% not_impl, 5% not_applicable
// voluntary: 20% impl, 30% partial, 40% not_impl, 10% not_applicable

function getBigAssessmentResponses(string $profile): array
{
    $cats = ['RM', 'IH', 'BC', 'SC', 'SD', 'EA', 'TR', 'CR', 'AM', 'MA'];
    $codes = [];
    foreach ($cats as $cat) {
        for ($i = 1; $i <= 8; $i++) {
            $codes[] = sprintf('%s%02d', $cat, $i);
        }
    }
    // 80 codici totali
    $dist = match ($profile) {
        'big'       => ['implemented' => 32, 'partial' => 24, 'not_implemented' => 16, 'not_applicable' => 8],
        'essential' => ['implemented' => 24, 'partial' => 32, 'not_implemented' => 20, 'not_applicable' => 4],
        'important' => ['implemented' => 20, 'partial' => 28, 'not_implemented' => 28, 'not_applicable' => 4],
        default     => ['implemented' => 16, 'partial' => 24, 'not_implemented' => 32, 'not_applicable' => 8],
    };
    $vals = [];
    foreach ($dist as $v => $n) {
        for ($i = 0; $i < $n; $i++) $vals[] = $v;
    }
    $result = [];
    foreach ($codes as $i => $code) {
        $result[$code] = $vals[$i] ?? 'not_implemented';
    }
    return $result;
}

// ── Auto-Reset ───────────────────────────────────────────────────────────────

function autoResetDemo(): void
{
    $host = readEnvValue('DB_HOST', 'localhost');
    $port = readEnvValue('DB_PORT', '3306');
    $name = readEnvValue('DB_NAME', 'nis2_agile_db');
    $user = readEnvValue('DB_USER', 'nis2_user');
    $pass = readEnvValue('DB_PASS', '');

    try {
        $pdo = new PDO("mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4", $user, $pass,
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 10]);
    } catch (\Throwable $e) {
        simLog('Auto-reset: connessione DB fallita — ' . $e->getMessage(), 'warn');
        return;
    }

    $pdo->exec('SET FOREIGN_KEY_CHECKS=0');

    $orgTables = [
        'incident_timeline'       => 'incident_id IN (SELECT id FROM incidents WHERE organization_id > 4)',
        'incidents'               => 'organization_id > 4',
        'risk_treatments'         => 'risk_id IN (SELECT id FROM risks WHERE organization_id > 4)',
        'risks'                   => 'organization_id > 4',
        'assessment_responses'    => 'assessment_id IN (SELECT id FROM assessments WHERE organization_id > 4)',
        'assessments'             => 'organization_id > 4',
        'policies'                => 'organization_id > 4',
        'suppliers'               => 'organization_id > 4',
        'training_assignments'    => 'course_id IN (SELECT id FROM training_courses WHERE organization_id > 4)',
        'training_courses'        => 'organization_id > 4',
        'assets'                  => 'organization_id > 4',
        'compliance_controls'     => 'organization_id > 4',
        'evidence_files'          => 'organization_id > 4',
        'capa_actions'            => 'ncr_id IN (SELECT id FROM non_conformities WHERE organization_id > 4)',
        'non_conformities'        => 'organization_id > 4',
        'whistleblowing_timeline' => 'report_id IN (SELECT id FROM whistleblowing_reports WHERE organization_id > 4)',
        'whistleblowing_reports'  => 'organization_id > 4',
        'normative_ack'           => 'organization_id > 4',
        'webhook_deliveries'      => 'subscription_id IN (SELECT id FROM webhook_subscriptions WHERE organization_id > 4)',
        'webhook_subscriptions'   => 'organization_id > 4',
        'api_keys'                => 'organization_id > 4',
        'feedback_reports'        => 'organization_id > 4',
        'ai_interactions'         => 'organization_id > 4',
    ];

    $deleted = 0;
    foreach ($orgTables as $table => $where) {
        try {
            $n = $pdo->exec("DELETE FROM `{$table}` WHERE {$where}");
            $deleted += ($n ?: 0);
        } catch (\Throwable $e) {
            simLog("  skip {$table}: " . $e->getMessage(), 'skip');
        }
    }

    try { $pdo->exec("DELETE FROM email_log WHERE recipient LIKE '%.demo%'"); } catch (\Throwable) {}
    try { $pdo->exec('DELETE FROM user_organizations WHERE organization_id > 4'); } catch (\Throwable) {}
    try {
        $pdo->exec("DELETE rt FROM refresh_tokens rt JOIN users u ON rt.user_id = u.id WHERE u.email LIKE '%.demo%'");
    } catch (\Throwable) {}
    try { $n = $pdo->exec("DELETE FROM users WHERE email LIKE '%.demo%'"); $deleted += ($n ?: 0); } catch (\Throwable) {}
    try { $n = $pdo->exec('DELETE FROM organizations WHERE id > 4'); $deleted += ($n ?: 0); } catch (\Throwable) {}

    $pdo->exec('SET FOREIGN_KEY_CHECKS=1');

    $rateDir = '/tmp/nis2_ratelimit/';
    $cleared = 0;
    if (is_dir($rateDir)) {
        foreach (glob($rateDir . '*.json') ?: [] as $f) {
            if (@unlink($f)) $cleared++;
        }
    }

    $orgCount = $pdo->query('SELECT COUNT(*) FROM organizations')->fetchColumn();
    simLog("Auto-reset completato — {$deleted} record rimossi, {$orgCount} org base mantenute" . ($cleared ? ", {$cleared} rate-limit file" : ''), 'ok');
}

// ── Policy category map ───────────────────────────────────────────────────────
function mapPolicyCategory(string $type): string
{
    return [
        'cryptography'          => 'encryption',
        'data_protection'       => 'information_security',
        'risk_management'       => 'vulnerability_management',
        'supply_chain_security' => 'supply_chain',
        'fraud_prevention'      => 'access_control',
        'network_security'      => 'network_security',
    ][$type] ?? $type;
}

// ── Risk level helper ─────────────────────────────────────────────────────────
function riskLevel(int $likelihood, int $impact): string
{
    $score = $likelihood * $impact;
    return match(true) {
        $score >= 20 => 'critical',
        $score >= 12 => 'high',
        $score >= 6  => 'medium',
        default      => 'low',
    };
}

// ═══════════════════════════════════════════════════════════════════════════════
// DEFINIZIONE 10 AZIENDE DEMO
// ═══════════════════════════════════════════════════════════════════════════════

$COMPANIES = [

// ── A) InfraTech Italia S.p.A. — BIG COMPANY ─────────────────────────────────
'infratech' => [
    'name' => 'InfraTech Italia S.p.A.',
    'legal_form' => 'S.p.A.',
    'vat_number' => '12345678901',
    'ateco_code' => '61.10',
    'sector' => 'digital_infra',
    'nis2_type' => 'essential',
    'employees' => 5800,
    'annual_turnover' => 820000000,
    'city' => 'Milano', 'province' => 'MI', 'region' => 'Lombardia',
    'profile' => 'big',
    'users' => [
        ['first'=>'Dott. Marco',   'last'=>'Visconti',  'email'=>'ceo@infratech-spa.demo',    'role'=>'org_admin'],
        ['first'=>'Ing. Serena',   'last'=>'Colombo',   'email'=>'ciso@infratech-spa.demo',   'role'=>'compliance_manager'],
        ['first'=>'Dott. Giorgio', 'last'=>'Ferri',     'email'=>'board@infratech-spa.demo',  'role'=>'board_member'],
        ['first'=>'Elena',         'last'=>'Mazzi',     'email'=>'auditor@infratech-spa.demo','role'=>'auditor'],
        ['first'=>'Luca',          'last'=>'Pavan',     'email'=>'staff@infratech-spa.demo',  'role'=>'employee'],
    ],
    'risks' => [
        ['title'=>'Ransomware su backbone nazionale',                   'category'=>'cyber',        'likelihood'=>5,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Compromissione OT/SCADA nodi rete',                  'category'=>'cyber',        'likelihood'=>4,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Attacco DDoS volumetrico su DNS primario',           'category'=>'operational',  'likelihood'=>5,'impact'=>4,'nis2_article'=>'21.2.c'],
        ['title'=>'Zero-day su firewall perimetrale (CVE critica)',     'category'=>'cyber',        'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.e'],
        ['title'=>'Insider threat: dipendente con accesso privilegiato','category'=>'human',        'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.i'],
        ['title'=>'Supply chain software — fornitore firmware compromesso','category'=>'supply_chain','likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.d'],
        ['title'=>'Data breach 2M utenti finali',                       'category'=>'compliance',   'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Failure Business Continuity — DC primario fuori',   'category'=>'operational',  'likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.c'],
        ['title'=>'Phishing dirigenti (CEO Fraud / BEC)',               'category'=>'human',        'likelihood'=>4,'impact'=>3,'nis2_article'=>'21.2.g'],
        ['title'=>'Furto certificati TLS wildcard',                     'category'=>'cyber',        'likelihood'=>2,'impact'=>4,'nis2_article'=>'21.2.h'],
    ],
    'policies' => [
        ['title'=>'Politica Gestione Incidenti — Art.21.2.b NIS2',      'type'=>'incident_response',  'nis2_article'=>'21.2.b'],
        ['title'=>'Politica Controllo Accessi e MFA — Art.21.2.j NIS2', 'type'=>'access_control',     'nis2_article'=>'21.2.j'],
        ['title'=>'Politica Crittografia e PKI — Art.21.2.h NIS2',      'type'=>'cryptography',       'nis2_article'=>'21.2.h'],
        ['title'=>'Politica Business Continuity DC — Art.21.2.c NIS2',  'type'=>'business_continuity','nis2_article'=>'21.2.c'],
        ['title'=>'Politica Supply Chain Sicurezza — Art.21.2.d NIS2',  'type'=>'supply_chain_security','nis2_article'=>'21.2.d'],
        ['title'=>'Politica Sicurezza Reti e Sistemi — Art.21.2.a NIS2','type'=>'network_security',   'nis2_article'=>'21.2.a'],
    ],
    'suppliers' => [
        ['name'=>'TelecomHW S.p.A.',    'service_type'=>'hardware_vendor',  'risk_level'=>'critical','critical'=>1],
        ['name'=>'SecurityOps S.r.l.',  'service_type'=>'security_service', 'risk_level'=>'high',    'critical'=>1],
        ['name'=>'CloudBackbone EU',     'service_type'=>'cloud_provider',   'risk_level'=>'critical','critical'=>1],
        ['name'=>'SoftwareCore AG',      'service_type'=>'software_vendor',  'risk_level'=>'high',    'critical'=>1],
        ['name'=>'LogisticIT S.r.l.',   'service_type'=>'managed_service',  'risk_level'=>'medium',  'critical'=>0],
        ['name'=>'CertSecurity S.r.l.', 'service_type'=>'consulting',       'risk_level'=>'low',     'critical'=>0],
    ],
    'assets' => [
        ['name'=>'Backbone fibra ottica nazionale',     'type'=>'network',    'criticality'=>'critical'],
        ['name'=>'Data Center Milano Nord',             'type'=>'datacenter', 'criticality'=>'critical'],
        ['name'=>'Sistema OT/SCADA gestione rete',     'type'=>'ot_system',  'criticality'=>'critical'],
        ['name'=>'Piattaforma DNS primaria/secondaria', 'type'=>'server',     'criticality'=>'high'],
        ['name'=>'VPN corporate + MFA infrastruttura', 'type'=>'software',   'criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'NIS2 Sicurezza Infrastrutture Critiche',  'mandatory'=>true, 'duration_hours'=>8],
        ['title'=>'Incident Response & Art.23 Procedure',    'mandatory'=>true, 'duration_hours'=>4],
    ],
],

// ── B) MedSalute Network S.p.A. ──────────────────────────────────────────────
'medsalute' => [
    'name' => 'MedSalute Network S.p.A.',
    'legal_form' => 'S.p.A.',
    'vat_number' => '11223344556',
    'ateco_code' => '86.10',
    'sector' => 'health',
    'nis2_type' => 'essential',
    'employees' => 2200,
    'annual_turnover' => 95000000,
    'city' => 'Roma', 'province' => 'RM', 'region' => 'Lazio',
    'profile' => 'essential',
    'users' => [
        ['first'=>'Dott. Antonio','last'=>'Russo',   'email'=>'admin@medsalute-spa.demo',   'role'=>'org_admin'],
        ['first'=>'Dott.ssa Marta','last'=>'De Luca','email'=>'ciso@medsalute-spa.demo',    'role'=>'compliance_manager'],
        ['first'=>'Prof. Luigi',   'last'=>'Farina',  'email'=>'auditor@medsalute-spa.demo', 'role'=>'auditor'],
    ],
    'risks' => [
        ['title'=>'Violazione privacy pazienti (GDPR+NIS2)',    'category'=>'compliance', 'likelihood'=>4,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Fermo HIS (Hospital Information System)',    'category'=>'operational','likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.c'],
        ['title'=>'Supply chain LIS — laboratorio analisi compromesso','category'=>'supply_chain','likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.d'],
        ['title'=>'Phishing staff medico (credential harvesting)','category'=>'human',   'likelihood'=>5,'impact'=>3,'nis2_article'=>'21.2.g'],
        ['title'=>'IoT medici non patchati (infusori, monitor)','category'=>'cyber',     'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.e'],
        ['title'=>'GDPR breach — accesso abusivo cartelle VIP', 'category'=>'cyber',     'likelihood'=>4,'impact'=>4,'nis2_article'=>'21.2.i'],
    ],
    'policies' => [
        ['title'=>'Politica Protezione Dati Sanitari — Art.21.2.b NIS2+GDPR','type'=>'data_protection',   'nis2_article'=>'21.2.b'],
        ['title'=>'Politica Business Continuity Sistemi Clinici',             'type'=>'business_continuity','nis2_article'=>'21.2.c'],
        ['title'=>'Politica Controllo Accessi Cartelle Cliniche',             'type'=>'access_control',    'nis2_article'=>'21.2.j'],
    ],
    'suppliers' => [
        ['name'=>'HealthSoft LIS S.r.l.','service_type'=>'software_vendor','risk_level'=>'critical','critical'=>1],
        ['name'=>'MedDevice HIS S.p.A.', 'service_type'=>'hardware_vendor','risk_level'=>'critical','critical'=>1],
        ['name'=>'CloudBackup Med',       'service_type'=>'cloud_provider', 'risk_level'=>'medium',  'critical'=>0],
        ['name'=>'IoT Medical EU S.r.l.', 'service_type'=>'hardware_vendor','risk_level'=>'high',    'critical'=>1],
    ],
    'assets' => [
        ['name'=>'HIS Centrale (Hospital Information System)','type'=>'software',   'criticality'=>'critical'],
        ['name'=>'Server Database Pazienti',                  'type'=>'server',     'criticality'=>'critical'],
        ['name'=>'Rete WiFi Clinica (IoT medici)',            'type'=>'network',    'criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Privacy e GDPR in Ambito Sanitario','mandatory'=>true,'duration_hours'=>4],
    ],
],

// ── C) DistribuzionePlus S.p.A. ───────────────────────────────────────────────
'distribuzione' => [
    'name' => 'DistribuzionePlus S.p.A.',
    'legal_form' => 'S.p.A.',
    'vat_number' => '22334455667',
    'ateco_code' => '35.13',
    'sector' => 'energy',
    'nis2_type' => 'essential',
    'employees' => 1400,
    'annual_turnover' => 210000000,
    'city' => 'Torino', 'province' => 'TO', 'region' => 'Piemonte',
    'profile' => 'essential',
    'users' => [
        ['first'=>'Ing. Paolo',   'last'=>'Mancini',   'email'=>'admin@distribuzione-spa.demo', 'role'=>'org_admin'],
        ['first'=>'Dott. Chiara', 'last'=>'Venturi',   'email'=>'ciso@distribuzione-spa.demo',  'role'=>'compliance_manager'],
        ['first'=>'Ing. Stefano', 'last'=>'Barbieri',  'email'=>'board@distribuzione-spa.demo', 'role'=>'board_member'],
    ],
    'risks' => [
        ['title'=>'SCADA compromise — distribuzione energia',        'category'=>'cyber',      'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Blackout selettivo doloso su RTU',               'category'=>'operational','likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.c'],
        ['title'=>'Supply chain firmware — contatori intelligenti', 'category'=>'supply_chain','likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.d'],
        ['title'=>'DDoS su sistemi RTU sottostazioni',              'category'=>'cyber',      'likelihood'=>4,'impact'=>4,'nis2_article'=>'21.2.c'],
        ['title'=>'Insider threat tecnico campo SCADA',             'category'=>'human',      'likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.i'],
        ['title'=>'Physical breach — sottostazione non presidiata', 'category'=>'physical',   'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.f'],
    ],
    'policies' => [
        ['title'=>'Politica Sicurezza OT/SCADA — Art.21.2.a NIS2',      'type'=>'risk_management',    'nis2_article'=>'21.2.a'],
        ['title'=>'Politica Business Continuity Rete Elettrica',         'type'=>'business_continuity','nis2_article'=>'21.2.c'],
        ['title'=>'Politica Sicurezza Supply Chain Energetica',          'type'=>'supply_chain_security','nis2_article'=>'21.2.d'],
    ],
    'suppliers' => [
        ['name'=>'SCADA Vendor Italia S.r.l.','service_type'=>'ot_vendor',       'risk_level'=>'critical','critical'=>1],
        ['name'=>'TelcoEnergy S.p.A.',        'service_type'=>'network_provider','risk_level'=>'high',    'critical'=>1],
        ['name'=>'BackupPower EU',             'service_type'=>'cloud_provider',  'risk_level'=>'medium',  'critical'=>0],
        ['name'=>'CyberSec Energia S.r.l.',   'service_type'=>'consulting',      'risk_level'=>'low',     'critical'=>0],
    ],
    'assets' => [
        ['name'=>'Sistema SCADA distribuzione principale','type'=>'ot_system','criticality'=>'critical'],
        ['name'=>'RTU Sottostazioni (45 unità)',          'type'=>'hardware', 'criticality'=>'critical'],
        ['name'=>'Centro Controllo Operativo (CCO)',      'type'=>'datacenter','criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Sicurezza Sistemi OT/ICS per Operatori Energia','mandatory'=>true,'duration_hours'=>6],
    ],
],

// ── D) BancaRegionale Digitale S.p.A. ────────────────────────────────────────
'bancaregionale' => [
    'name' => 'BancaRegionale Digitale S.p.A.',
    'legal_form' => 'S.p.A.',
    'vat_number' => '33445566778',
    'ateco_code' => '64.19',
    'sector' => 'banking',
    'nis2_type' => 'essential',
    'employees' => 3100,
    'annual_turnover' => 180000000,
    'city' => 'Milano', 'province' => 'MI', 'region' => 'Lombardia',
    'profile' => 'essential',
    'users' => [
        ['first'=>'Dott. Fabio',  'last'=>'Brunetti',  'email'=>'admin@bancaregionale-spa.demo',  'role'=>'org_admin'],
        ['first'=>'Dott. Sara',   'last'=>'Lombardi',  'email'=>'ciso@bancaregionale-spa.demo',   'role'=>'compliance_manager'],
        ['first'=>'Dott. Marco',  'last'=>'Torriani',  'email'=>'auditor@bancaregionale-spa.demo','role'=>'auditor'],
    ],
    'risks' => [
        ['title'=>'Frode bonifici (Business Email Compromise)',      'category'=>'cyber',     'likelihood'=>4,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Credential stuffing su internet banking',         'category'=>'cyber',     'likelihood'=>5,'impact'=>4,'nis2_article'=>'21.2.i'],
        ['title'=>'API banking compromise (Open Banking)',           'category'=>'cyber',     'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.e'],
        ['title'=>'Anomalia messaggistica SWIFT',                    'category'=>'operational','likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'Ransomware su core banking system',               'category'=>'cyber',     'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'DDoS su app mobile (500k utenti)',                'category'=>'operational','likelihood'=>4,'impact'=>3,'nis2_article'=>'21.2.c'],
    ],
    'policies' => [
        ['title'=>'Politica Gestione Incidenti Banking — Art.21.2.b', 'type'=>'incident_response','nis2_article'=>'21.2.b'],
        ['title'=>'Politica Controllo Accessi e PSD2 — Art.21.2.j',  'type'=>'access_control',   'nis2_article'=>'21.2.j'],
        ['title'=>'Politica Crittografia e SWIFT Security',           'type'=>'cryptography',     'nis2_article'=>'21.2.h'],
        ['title'=>'Politica Anti-Frode e Monitoraggio Transazioni',   'type'=>'fraud_prevention', 'nis2_article'=>'21.2.b'],
    ],
    'suppliers' => [
        ['name'=>'CoreBanking Systems AG',   'service_type'=>'software_vendor', 'risk_level'=>'critical','critical'=>1],
        ['name'=>'PaymentGateway Italia',    'service_type'=>'managed_service', 'risk_level'=>'high',    'critical'=>1],
        ['name'=>'SOC Esterno Cyber S.r.l.', 'service_type'=>'security_service','risk_level'=>'high',    'critical'=>1],
        ['name'=>'Cloud DR Finance EU',      'service_type'=>'cloud_provider',  'risk_level'=>'medium',  'critical'=>0],
    ],
    'assets' => [
        ['name'=>'Core Banking System (CBS)','type'=>'software',  'criticality'=>'critical'],
        ['name'=>'App Mobile Banking (500k utenti)','type'=>'software','criticality'=>'critical'],
        ['name'=>'Data Center Primario Banking','type'=>'datacenter','criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Cybersecurity nel Settore Bancario e PSD2','mandatory'=>true,'duration_hours'=>6],
    ],
],

// ── E) AquaPura Servizi S.r.l. ────────────────────────────────────────────────
'aquapura' => [
    'name' => 'AquaPura Servizi S.r.l.',
    'legal_form' => 'S.r.l.',
    'vat_number' => '44556677889',
    'ateco_code' => '36.00',
    'sector' => 'water',
    'nis2_type' => 'essential',
    'employees' => 380,
    'annual_turnover' => 28000000,
    'city' => 'Venezia', 'province' => 'VE', 'region' => 'Veneto',
    'profile' => 'essential',
    'users' => [
        ['first'=>'Ing. Davide','last'=>'Cattaneo','email'=>'admin@aquapura-srl.demo','role'=>'org_admin'],
        ['first'=>'Dott. Irene','last'=>'Silvestri','email'=>'ciso@aquapura-srl.demo','role'=>'compliance_manager'],
    ],
    'risks' => [
        ['title'=>'Compromissione impianto trattamento acque',    'category'=>'cyber',      'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'SCADA acquedotto — accesso non autorizzato',  'category'=>'cyber',      'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'DoS sistemi di controllo potabilità',         'category'=>'operational','likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.c'],
        ['title'=>'Supply chain reagenti — fornitore compromesso','category'=>'supply_chain','likelihood'=>2,'impact'=>4,'nis2_article'=>'21.2.d'],
        ['title'=>'Physical intrusion impianto depurazione',     'category'=>'physical',   'likelihood'=>2,'impact'=>4,'nis2_article'=>'21.2.f'],
    ],
    'policies' => [
        ['title'=>'Politica Gestione Incidenti Acquedotto','type'=>'incident_response',   'nis2_article'=>'21.2.b'],
        ['title'=>'Politica Business Continuity Idrica',    'type'=>'business_continuity','nis2_article'=>'21.2.c'],
    ],
    'suppliers' => [
        ['name'=>'AquaSCADA S.r.l.',      'service_type'=>'ot_vendor',     'risk_level'=>'critical','critical'=>1],
        ['name'=>'Laboratorio Analisi H2O','service_type'=>'consulting',   'risk_level'=>'medium',  'critical'=>0],
        ['name'=>'Manutenzione Impianti',  'service_type'=>'managed_service','risk_level'=>'medium','critical'=>0],
    ],
    'assets' => [
        ['name'=>'Sistema SCADA acquedotto principale','type'=>'ot_system','criticality'=>'critical'],
        ['name'=>'Impianto trattamento acque centrale','type'=>'hardware', 'criticality'=>'critical'],
    ],
    'training' => [
        ['title'=>'Sicurezza Sistemi Idrici e OT','mandatory'=>true,'duration_hours'=>4],
    ],
],

// ── F) LogisticaRapida S.r.l. ─────────────────────────────────────────────────
'logistica' => [
    'name' => 'LogisticaRapida S.r.l.',
    'legal_form' => 'S.r.l.',
    'vat_number' => '55667788990',
    'ateco_code' => '49.41',
    'sector' => 'transport',
    'nis2_type' => 'important',
    'employees' => 620,
    'annual_turnover' => 45000000,
    'city' => 'Bologna', 'province' => 'BO', 'region' => 'Emilia-Romagna',
    'profile' => 'important',
    'users' => [
        ['first'=>'Dott. Riccardo','last'=>'Fontana','email'=>'admin@logisticarapida-srl.demo','role'=>'org_admin'],
        ['first'=>'Ing. Valentina','last'=>'Greco',  'email'=>'ciso@logisticarapida-srl.demo', 'role'=>'compliance_manager'],
    ],
    'risks' => [
        ['title'=>'GPS spoofing su flotta veicoli',              'category'=>'cyber',      'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.b'],
        ['title'=>'Fleet management system compromise',          'category'=>'cyber',      'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.b'],
        ['title'=>'Ransomware su TMS (Transport Management)',    'category'=>'cyber',      'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.b'],
        ['title'=>'Supply chain carrier — partner malicious',   'category'=>'supply_chain','likelihood'=>2,'impact'=>3,'nis2_article'=>'21.2.d'],
        ['title'=>'API partner — injection su dati spedizione', 'category'=>'cyber',      'likelihood'=>2,'impact'=>3,'nis2_article'=>'21.2.e'],
    ],
    'policies' => [
        ['title'=>'Politica Sicurezza Flotta e TMS','type'=>'incident_response',    'nis2_article'=>'21.2.b'],
        ['title'=>'Politica Supply Chain Logistica', 'type'=>'supply_chain_security','nis2_article'=>'21.2.d'],
    ],
    'suppliers' => [
        ['name'=>'TMS Provider Cloud',    'service_type'=>'software_vendor', 'risk_level'=>'high',  'critical'=>1],
        ['name'=>'GPS Tracking EU',       'service_type'=>'hardware_vendor', 'risk_level'=>'medium','critical'=>0],
        ['name'=>'Carrier Network Italia','service_type'=>'managed_service', 'risk_level'=>'low',   'critical'=>0],
    ],
    'assets' => [
        ['name'=>'TMS (Transport Management System)','type'=>'software','criticality'=>'critical'],
        ['name'=>'Fleet GPS Tracking (620 veicoli)', 'type'=>'hardware','criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Cybersecurity nei Trasporti e GPS','mandatory'=>true,'duration_hours'=>3],
    ],
],

// ── G) SmartCity Solutions S.r.l. ─────────────────────────────────────────────
'smartcity' => [
    'name' => 'SmartCity Solutions S.r.l.',
    'legal_form' => 'S.r.l.',
    'vat_number' => '66778899001',
    'ateco_code' => '62.09',
    'sector' => 'ict_services',
    'nis2_type' => 'important',
    'employees' => 145,
    'annual_turnover' => 12000000,
    'city' => 'Firenze', 'province' => 'FI', 'region' => 'Toscana',
    'profile' => 'important',
    'users' => [
        ['first'=>'Dott. Andrea',  'last'=>'Pellegrini','email'=>'admin@smartcity-srl.demo','role'=>'org_admin'],
        ['first'=>'Ing. Federica', 'last'=>'Marini',    'email'=>'ciso@smartcity-srl.demo', 'role'=>'compliance_manager'],
    ],
    'risks' => [
        ['title'=>'IoT smart city exploit (sensori stradali)','category'=>'cyber',      'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.b'],
        ['title'=>'Data lake breach (dati mobilità cittadini)','category'=>'compliance','likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.b'],
        ['title'=>'API pubblica abusata (smart parking)',     'category'=>'cyber',      'likelihood'=>4,'impact'=>3,'nis2_article'=>'21.2.e'],
        ['title'=>'Supply chain sensori ambientali',          'category'=>'supply_chain','likelihood'=>2,'impact'=>3,'nis2_article'=>'21.2.d'],
    ],
    'policies' => [
        ['title'=>'Politica Sicurezza IoT e Smart City','type'=>'incident_response','nis2_article'=>'21.2.b'],
        ['title'=>'Politica Protezione Dati Urbani',    'type'=>'data_protection',  'nis2_article'=>'21.2.b'],
    ],
    'suppliers' => [
        ['name'=>'IoT Platform EU S.r.l.',  'service_type'=>'hardware_vendor','risk_level'=>'high',  'critical'=>1],
        ['name'=>'Cloud Analytics Smart',   'service_type'=>'cloud_provider', 'risk_level'=>'medium','critical'=>0],
    ],
    'assets' => [
        ['name'=>'Piattaforma IoT Smart City (2000 sensori)','type'=>'ot_system','criticality'=>'critical'],
        ['name'=>'Data Lake mobilità urbana',               'type'=>'server',   'criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Sicurezza IoT e Città Intelligenti','mandatory'=>true,'duration_hours'=>3],
    ],
],

// ── H) EduDigital S.r.l. — PMI voluntary ─────────────────────────────────────
'edudigital' => [
    'name' => 'EduDigital S.r.l.',
    'legal_form' => 'S.r.l.',
    'vat_number' => '77889900112',
    'ateco_code' => '85.59',
    'sector' => 'ict_services',
    'nis2_type' => 'voluntary',
    'voluntary_compliance' => true,
    'employees' => 80,
    'annual_turnover' => 6000000,
    'city' => 'Bologna', 'province' => 'BO', 'region' => 'Emilia-Romagna',
    'profile' => 'voluntary',
    'users' => [
        ['first'=>'Dott. Matteo','last'=>'Ferrario','email'=>'admin@edudigital-srl.demo','role'=>'org_admin'],
        ['first'=>'Ing. Elisa',  'last'=>'Conti',   'email'=>'ciso@edudigital-srl.demo', 'role'=>'compliance_manager'],
    ],
    'risks' => [
        ['title'=>'Breach dati studenti (GDPR minori)',       'category'=>'compliance','likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.b'],
        ['title'=>'Piattaforma LMS down durante esami',      'category'=>'operational','likelihood'=>3,'impact'=>3,'nis2_article'=>'21.2.c'],
        ['title'=>'Phishing docenti — credenziali rubate',   'category'=>'human',     'likelihood'=>4,'impact'=>3,'nis2_article'=>'21.2.g'],
    ],
    'policies' => [
        ['title'=>'Politica Protezione Dati Studenti — GDPR','type'=>'data_protection','nis2_article'=>'21.2.b'],
    ],
    'suppliers' => [
        ['name'=>'LMS Provider Cloud S.r.l.','service_type'=>'software_vendor','risk_level'=>'high',  'critical'=>1],
        ['name'=>'Cloud Hosting EduTech',    'service_type'=>'cloud_provider', 'risk_level'=>'medium','critical'=>0],
    ],
    'assets' => [
        ['name'=>'LMS Platform (8000 studenti)','type'=>'software','criticality'=>'critical'],
        ['name'=>'Database dati studenti',      'type'=>'server',  'criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Privacy e GDPR per Operatori Educativi','mandatory'=>true,'duration_hours'=>3],
    ],
],

// ── I) AgriTech Innovazione S.r.l. — PMI voluntary ───────────────────────────
'agritech' => [
    'name' => 'AgriTech Innovazione S.r.l.',
    'legal_form' => 'S.r.l.',
    'vat_number' => '88990011223',
    'ateco_code' => '01.61',
    'sector' => 'food',
    'nis2_type' => 'voluntary',
    'voluntary_compliance' => true,
    'employees' => 45,
    'annual_turnover' => 3500000,
    'city' => 'Verona', 'province' => 'VR', 'region' => 'Veneto',
    'profile' => 'voluntary',
    'users' => [
        ['first'=>'Dott. Giuseppe','last'=>'Caruso', 'email'=>'admin@agritech-srl.demo','role'=>'org_admin'],
        ['first'=>'Dott. Paola',   'last'=>'Monti',  'email'=>'ciso@agritech-srl.demo', 'role'=>'compliance_manager'],
    ],
    'risks' => [
        ['title'=>'Sensori IoT campo compromessi (dati coltura)','category'=>'cyber',      'likelihood'=>3,'impact'=>3,'nis2_article'=>'21.2.b'],
        ['title'=>'Sistema ERP agricolo — accesso non auth',    'category'=>'cyber',      'likelihood'=>2,'impact'=>3,'nis2_article'=>'21.2.i'],
        ['title'=>'Supply chain tracciabilità — dati falsificati','category'=>'supply_chain','likelihood'=>2,'impact'=>4,'nis2_article'=>'21.2.d'],
    ],
    'policies' => [
        ['title'=>'Politica Sicurezza IoT Agricoltura','type'=>'incident_response','nis2_article'=>'21.2.b'],
    ],
    'suppliers' => [
        ['name'=>'AgriIoT Platform S.r.l.','service_type'=>'hardware_vendor','risk_level'=>'medium','critical'=>0],
        ['name'=>'ERP AgriSoft',           'service_type'=>'software_vendor','risk_level'=>'medium','critical'=>0],
    ],
    'assets' => [
        ['name'=>'Rete IoT sensori campo (200 unità)','type'=>'ot_system','criticality'=>'high'],
        ['name'=>'ERP Sistema gestione agricola',     'type'=>'software', 'criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Sicurezza IoT in Agricoltura','mandatory'=>false,'duration_hours'=>2],
    ],
],

// ── J) ManufacturingPro S.p.A. ────────────────────────────────────────────────
'manufacturing' => [
    'name' => 'ManufacturingPro S.p.A.',
    'legal_form' => 'S.p.A.',
    'vat_number' => '99001122334',
    'ateco_code' => '28.22',
    'sector' => 'manufacturing',
    'nis2_type' => 'important',
    'employees' => 950,
    'annual_turnover' => 78000000,
    'city' => 'Brescia', 'province' => 'BS', 'region' => 'Lombardia',
    'profile' => 'important',
    'users' => [
        ['first'=>'Ing. Roberto',  'last'=>'Vitali',   'email'=>'admin@manufacturingpro-spa.demo',  'role'=>'org_admin'],
        ['first'=>'Dott. Claudia', 'last'=>'Negri',    'email'=>'ciso@manufacturingpro-spa.demo',   'role'=>'compliance_manager'],
        ['first'=>'Dott. Enrico',  'last'=>'Saraceni', 'email'=>'auditor@manufacturingpro-spa.demo','role'=>'auditor'],
    ],
    'risks' => [
        ['title'=>'OT fabbrica compromessa (linea produzione fermata)','category'=>'cyber',      'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'ERP SAP credential leak',                           'category'=>'cyber',      'likelihood'=>3,'impact'=>4,'nis2_article'=>'21.2.i'],
        ['title'=>'Supply chain componenti — fornitore backdoor',     'category'=>'supply_chain','likelihood'=>2,'impact'=>5,'nis2_article'=>'21.2.d'],
        ['title'=>'Ransomware su sistemi produzione',                 'category'=>'cyber',      'likelihood'=>3,'impact'=>5,'nis2_article'=>'21.2.b'],
        ['title'=>'GDPR lavoratori — dati biometrici timbrature',     'category'=>'compliance', 'likelihood'=>3,'impact'=>3,'nis2_article'=>'21.2.b'],
    ],
    'policies' => [
        ['title'=>'Politica Sicurezza OT Produzione — Art.21.2.a',  'type'=>'risk_management',    'nis2_article'=>'21.2.a'],
        ['title'=>'Politica Controllo Accessi ERP',                  'type'=>'access_control',     'nis2_article'=>'21.2.j'],
        ['title'=>'Politica Business Continuity Produzione',         'type'=>'business_continuity','nis2_article'=>'21.2.c'],
    ],
    'suppliers' => [
        ['name'=>'SAP Hosting Partner S.r.l.','service_type'=>'managed_service','risk_level'=>'critical','critical'=>1],
        ['name'=>'OT Vendor Automazione',     'service_type'=>'ot_vendor',      'risk_level'=>'critical','critical'=>1],
        ['name'=>'Logistica Componenti EU',   'service_type'=>'logistics',      'risk_level'=>'medium',  'critical'=>0],
        ['name'=>'Cert ISO 27001 S.r.l.',     'service_type'=>'consulting',     'risk_level'=>'low',     'critical'=>0],
    ],
    'assets' => [
        ['name'=>'Linea produzione OT (4 impianti)','type'=>'ot_system','criticality'=>'critical'],
        ['name'=>'ERP SAP S/4HANA',               'type'=>'software', 'criticality'=>'critical'],
        ['name'=>'Rete IT/OT segregata fabbrica', 'type'=>'network',  'criticality'=>'high'],
    ],
    'training' => [
        ['title'=>'Cybersecurity in Ambienti Industriali OT/IT','mandatory'=>true,'duration_hours'=>5],
    ],
],

]; // end $COMPANIES

// Consulente cross-org
$CONSULTANT = [
    'first' => 'Avv. Federica', 'last' => 'Montanari',
    'email' => 'consultant@nis2agile-big.demo',
    'role'  => 'consultant',
];

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 0 — Health Check + Auto-Reset
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(0, 'Health Check API');
$ch = curl_init(str_replace('/api', '', API_BASE) . '/api-status.php');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>10,CURLOPT_SSL_VERIFYPEER=>false,CURLOPT_SSL_VERIFYHOST=>0]);
$raw = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
$hRes = json_decode($raw ?: '{}', true) ?? [];
if ($code === 200 && !empty($hRes['status'])) {
    ok("API health: {$hRes['status']} — DB: " . ($hRes['database'] ?? '?'));
} else {
    warn("API health check degradato (HTTP $code) — continuo comunque");
}

simPhase(0, 'Auto-Reset Dati Demo Precedenti');
autoResetDemo();

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 1 — Registrazione & Onboarding (10 aziende)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(1, 'Registrazione & Onboarding — 10 Aziende NIS2');

foreach ($COMPANIES as $slug => $comp) {
    simLog("── [{$comp['nis2_type']}] {$comp['name']} ({$comp['sector']}) {$comp['employees']} dip", 'phase');

    $adminDef = $comp['users'][0];
    $jwt = ensureUser($adminDef['first'], $adminDef['last'], $adminDef['email'], DEMO_PWD, 'org_admin');
    if (!$jwt) continue;
    $S['orgs'][$slug] = ['name' => $comp['name'], 'jwt' => $jwt, 'id' => null];

    $orgId = ensureOrg($jwt, [
        'name'               => $comp['name'],
        'legal_form'         => $comp['legal_form'],
        'vat_number'         => $comp['vat_number'],
        'ateco_code'         => $comp['ateco_code'],
        'sector'             => $comp['sector'],
        'employee_count'     => $comp['employees'],
        'annual_turnover_eur'=> $comp['annual_turnover'],
        'city'               => $comp['city'],
        'province'           => $comp['province'],
        'region'             => $comp['region'],
        'country'            => 'IT',
    ]);
    if (!$orgId) continue;
    $S['orgs'][$slug]['id'] = $orgId;

    // Aggiorna dati org
    $upRes = api('PUT', "/organizations/{$orgId}", [
        'employee_count'      => $comp['employees'],
        'annual_turnover_eur' => $comp['annual_turnover'],
        'vat_number'          => $comp['vat_number'],
    ], $jwt, $orgId);
    apiOk($upRes, "org.update.$slug") && ok("Dati org aggiornati: #{$orgId}");

    // Classificazione NIS2
    $classData = [
        'sector'             => $comp['sector'],
        'employee_count'     => $comp['employees'],
        'annual_turnover_eur'=> $comp['annual_turnover'],
        'nis2_type'          => $comp['nis2_type'],
    ];
    if (!empty($comp['voluntary_compliance'])) {
        $classData['voluntary_compliance'] = true;
    }
    classifyOrg($jwt, $orgId, $classData);

    // Seed utenti extra + aggiunta alla org via DB
    if (count($comp['users']) > 1) {
        for ($i = 1; $i < count($comp['users']); $i++) {
            $u = $comp['users'][$i];
            $fullName = trim("{$u['first']} {$u['last']}");
            $seeded = dbSeedUser($fullName, $u['email'], DEMO_PWD, $u['role']);
            $uid = dbGetUserId($u['email']);
            if ($uid) {
                dbAddToOrg($uid, $orgId, $u['role']);
                $S['users'][$u['email']] = ['id' => $uid, 'jwt' => null];
                ok("Utente seeded+aggiunto: {$fullName} [{$u['role']}] → org #{$orgId}");
            } else {
                warn("Utente seed fallito: {$u['email']}");
            }
        }
    }
}

// Consulente cross-org
simLog("── Consulente cross-org: {$CONSULTANT['first']} {$CONSULTANT['last']}", 'phase');
$consultantJwt = ensureUser($CONSULTANT['first'], $CONSULTANT['last'], $CONSULTANT['email'], DEMO_PWD, 'consultant');
if ($consultantJwt) {
    $S['users'][$CONSULTANT['email']]['jwt'] = $consultantJwt;
    foreach ($S['orgs'] as $slug => $orgData) {
        if (empty($orgData['id'])) continue;
        $uid = $S['users'][$CONSULTANT['email']]['id'] ?? null;
        if ($uid) {
            dbAddToOrg($uid, $orgData['id'], 'consultant');
            ok("Consulente aggiunto a org #{$orgData['id']} ({$orgData['name']})");
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 2 — Gap Assessment 80 Domande (tutte le 10 aziende)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(2, 'Gap Assessment 80 Domande Art.21 NIS2 — 10 Aziende');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) { skip("Skip assessment $slug: org non creata"); continue; }
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];
    $responses = getBigAssessmentResponses($comp['profile']);
    $assessId  = runAssessment($jwt, $orgId, $slug, $comp['name'], $responses);

    // AI analyze per infratech (opzionale — warn se rate-limited)
    if ($slug === 'infratech' && $assessId) {
        $aiRes = api('POST', "/assessments/{$assessId}/ai-analyze", [], $jwt, $orgId);
        if (!empty($aiRes['success'])) {
            ok("Assessment AI analyze completato per InfraTech");
        } else {
            warn("Assessment AI analyze: " . ($aiRes['error'] ?? $aiRes['message'] ?? 'rate-limited o timeout — skip'));
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 3 — Risk Register (tutte le 10 aziende)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(3, 'Risk Register — 55+ Rischi NIS2');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];
    simLog("Rischi [{$comp['name']}] ({$comp['profile']}):", 'info');

    $riskIds = [];
    foreach ($comp['risks'] as $riskDef) {
        $l = $riskDef['likelihood']; $i = $riskDef['impact'];
        $score = $l * $i;
        $level = riskLevel($l, $i);
        $riskId = createRisk($jwt, $orgId, [
            'title'        => $riskDef['title'],
            'category'     => $riskDef['category'],
            'likelihood'   => $l,
            'impact'       => $i,
            'risk_score'   => $score,
            'risk_level'   => $level,
            'nis2_article' => $riskDef['nis2_article'],
            'description'  => "Rischio identificato — {$riskDef['title']}. Contesto: {$comp['name']}, settore {$comp['sector']}.",
        ]);
        if ($riskId) $riskIds[$level][] = $riskId;
    }

    // Aggiungi treatments per rischi critical/high
    foreach (['critical', 'high'] as $lvl) {
        foreach ($riskIds[$lvl] ?? [] as $riskId) {
            $tRes = api('POST', "/risks/{$riskId}/treatments", [
                'treatment_type'  => 'mitigate',
                'description'     => 'Piano di mitigazione approvato da CISO. Implementazione in 60gg.',
                'status'          => 'in_progress',
                'due_date'        => date('Y-m-d', strtotime('+60 days')),
            ], $jwt, $orgId);
            apiOk($tRes, "risk.treatment.$slug") && ok("Treatment aggiunto a risk #{$riskId} [$lvl]");
        }
    }

    // AI suggest per infratech
    if ($slug === 'infratech') {
        $aiRes = api('POST', '/risks/ai-suggest', [
            'sector' => $comp['sector'],
            'context'=> 'Infrastruttura digitale critica, 5800 dipendenti, backbone fibra nazionale',
        ], $jwt, $orgId);
        if (!empty($aiRes['success'])) {
            ok("AI risk suggest per InfraTech: " . count($aiRes['data']['risks'] ?? []) . " suggerimenti");
        } else {
            warn("AI risk suggest: " . ($aiRes['message'] ?? 'rate-limited — skip'));
        }
    }

    // Verifica matrice rischi
    $matrixRes = api('GET', '/risks/matrix', null, $jwt, $orgId);
    apiOk($matrixRes, "risks.matrix.$slug") && ok("Matrice rischi verificata per {$comp['name']}");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 4 — Policy (tutte le 10 aziende)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(4, 'Policy NIS2 Art.21 — 25+ Policy');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    foreach ($comp['policies'] as $polDef) {
        $polCategory = mapPolicyCategory($polDef['type']);
        $polId = createPolicy($jwt, $orgId, [
            'title'        => $polDef['title'],
            'category'     => $polCategory,
            'nis2_article' => $polDef['nis2_article'],
            'content'      => "Politica aziendale in conformità Art.{$polDef['nis2_article']} Direttiva NIS2 (EU 2022/2555) e D.Lgs.138/2024. Versione 1.0 — generata da Big Simulation demo.",
            'status'       => 'draft',
        ]);
        if ($polId) {
            $approveRes = api('POST', "/policies/{$polId}/approve", [
                'notes' => 'Approvazione automatica da Big Simulation demo NIS2',
            ], $jwt, $orgId);
            apiOk($approveRes, "policy.approve") && ok("Policy approvata: {$polDef['title']}");
        }
    }

    // AI generate aggiuntiva per infratech
    if ($slug === 'infratech') {
        $aiPolRes = api('POST', '/policies/ai-generate', [
            'category'     => 'vulnerability_management',
            'nis2_article' => '21.2.e',
            'context'      => 'Infrastruttura telecomunicazioni critica, 5800 dipendenti',
        ], $jwt, $orgId);
        if (!empty($aiPolRes['success'])) {
            ok("AI policy generate per InfraTech: vulnerability_management");
        } else {
            warn("AI policy generate: " . ($aiPolRes['message'] ?? 'rate-limited — skip'));
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 5 — Supply Chain (tutte le 10 aziende)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(5, 'Supply Chain — 30+ Fornitori Critici');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    foreach ($comp['suppliers'] as $supDef) {
        $supId = createSupplier($jwt, $orgId, [
            'name'          => $supDef['name'],
            'service_type'  => $supDef['service_type'],
            'criticality'   => $supDef['risk_level'],
            'nis2_relevant' => 1,
            'contact_email' => DEMO_EMAIL,
        ]);
        if ($supId) {
            $highRisk = in_array($supDef['risk_level'], ['high', 'critical']);
            $assessRes = api('POST', "/supply-chain/{$supId}/assess", [
                'assessment_responses' => [
                    ['question'=>'Controlli sicurezza implementati',     'weight'=>3,'value'=>'yes'],
                    ['question'=>'Procedura gestione incidenti',         'weight'=>2,'value'=>$highRisk ? 'yes' : 'partial'],
                    ['question'=>'Conformità GDPR',                      'weight'=>2,'value'=>'yes'],
                    ['question'=>'Clausole NIS2 nel contratto',          'weight'=>2,'value'=>$supDef['critical'] ? 'yes' : 'partial'],
                    ['question'=>'Audit sicurezza ultimi 12 mesi',       'weight'=>1,'value'=>$highRisk ? 'yes' : 'partial'],
                    ['question'=>'Piano business continuity documentato','weight'=>2,'value'=>'partial'],
                ],
            ], $jwt, $orgId);
            apiOk($assessRes, "supplier.assess") && ok("Assessment fornitore: {$supDef['name']}");
        }
    }

    // Verifica risk overview
    $roRes = api('GET', '/supply-chain/risk-overview', null, $jwt, $orgId);
    apiOk($roRes, "supply-chain.risk-overview.$slug") && ok("Risk overview supply chain: {$comp['name']}");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 6 — Training (tutte le 10 aziende)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(6, 'Training & Formazione — 10 Aziende');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    foreach ($comp['training'] as $courseDef) {
        $courseId = createTrainingCourse($jwt, $orgId, [
            'title'          => $courseDef['title'],
            'description'    => "Corso {$comp['name']} — settore {$comp['sector']}. Conformità NIS2 Art.21.2.g.",
            'mandatory'      => $courseDef['mandatory'],
            'duration_hours' => $courseDef['duration_hours'],
            'category'       => 'nis2_compliance',
        ]);
        if ($courseId) {
            // Assegna a tutti gli utenti dell'azienda
            foreach ($comp['users'] as $u) {
                $uid = $S['users'][$u['email']]['id'] ?? null;
                if (!$uid) $uid = dbGetUserId($u['email']);
                if ($uid) {
                    $assignRes = api('POST', '/training/assign', [
                        'course_id' => $courseId,
                        'user_ids'  => [$uid],
                        'due_date'  => date('Y-m-d', strtotime('+30 days')),
                    ], $jwt, $orgId);
                    if (!empty($assignRes['success'])) {
                        ok("Corso assegnato: {$u['email']} → {$courseDef['title']}");
                    } else {
                        warn("Assign training: " . ($assignRes['error'] ?? $assignRes['message'] ?? 'errore'));
                    }
                }
            }
        }
    }

    // Verifica compliance status
    $csRes = api('GET', '/training/compliance-status', null, $jwt, $orgId);
    apiOk($csRes, "training.compliance.$slug") && ok("Training compliance status: {$comp['name']}");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 7 — Asset (tutte le 10 aziende, dettaglio infratech)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(7, 'Inventario Asset — 25+ Asset Critici');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    foreach ($comp['assets'] as $assetDef) {
        createAsset($jwt, $orgId, [
            'name'        => $assetDef['name'],
            'type'        => $assetDef['type'],
            'criticality' => $assetDef['criticality'],
            'description' => "Asset {$comp['name']} — {$assetDef['name']}. Settore {$comp['sector']}.",
            'status'      => 'active',
        ]);
    }

    // Verifica dependency map per infratech
    if ($slug === 'infratech') {
        $depRes = api('GET', '/assets/dependency-map', null, $jwt, $orgId);
        apiOk($depRes, "assets.dependency-map") && ok("Asset dependency map InfraTech verificata");
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 8 — Incidenti con Timeline Art.23
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(8, 'Incidenti Art.23 NIS2 — Timeline 24h/72h/30d');

// ── InfraTech — Incidente 1: Ransomware Backbone ─────────────────────────────
if (!empty($S['orgs']['infratech']['id'])) {
    $jwt   = $S['orgs']['infratech']['jwt'];
    $orgId = $S['orgs']['infratech']['id'];
    simLog('InfraTech — Incidente 1: Ransomware Backbone Nazionale', 'phase');

    $inc1 = createIncident($jwt, $orgId, [
        'title'                => 'Ransomware su backbone nazionale fibra ottica',
        'description'          => 'SIEM ha rilevato alle 03:47 attività anomala su cluster routing nazionale. Analisi forense: LockBit 3.0 ha crittografato sistemi di gestione backbone. 2 milioni di utenti finali impattati. Downtime stimato 12h.',
        'severity'             => 'critical',
        'classification'       => 'cyber_attack',
        'affected_services'    => 'Backbone fibra, DNS primario, routing BGP',
        'affected_users_count' => 2000000,
        'malicious_action'     => 1,
        'is_significant'       => 1,
        'detected_at'          => date('Y-m-d H:i:s', strtotime('-2 days')),
        'status'               => 'analyzing',
    ]);
    if ($inc1) {
        // AI classify
        $aiClassRes = api('POST', "/incidents/{$inc1}/ai-classify", [], $jwt, $orgId);
        if (!empty($aiClassRes['success'])) ok("AI classify incidente #{$inc1}");
        else warn("AI classify: " . ($aiClassRes['message'] ?? 'rate-limited — skip'));

        // Timeline detection
        api('POST', "/incidents/{$inc1}/timeline", [
            'event_type' => 'update', 'description' => 'Rilevato da SIEM alle 03:47. Alert correlato su 14 indicatori IOC. Team IR attivato.',
        ], $jwt, $orgId) && ok("Timeline: detection");

        // Early warning 24h
        $ewRes = api('POST', "/incidents/{$inc1}/early-warning", [
            'notes' => 'Early warning 24h inviato ad ACN/CSIRT. Incidente significativo: >500 utenti, cyber, durata >4h.',
        ], $jwt, $orgId);
        apiOk($ewRes, 'inc1.early-warning') && ok("Early warning 24h Art.23 inviato — ACN");

        // Timeline containment
        api('POST', "/incidents/{$inc1}/timeline", [
            'event_type' => 'update', 'description' => 'Contenimento: isolamento segmenti compromessi. Failover su DR site attivato.',
        ], $jwt, $orgId);

        // Notification 72h
        $notRes = api('POST', "/incidents/{$inc1}/notification", [
            'notes' => 'Notifica 72h ACN+CSIRT. Impatto: 2M utenti, backbone fibra, 12h downtime. Ransomware LockBit 3.0.',
        ], $jwt, $orgId);
        apiOk($notRes, 'inc1.notification') && ok("Notifica 72h Art.23 completata");

        // Timeline recovery
        api('POST', "/incidents/{$inc1}/timeline", [
            'event_type' => 'update', 'description' => 'Recovery: ripristino da backup cold standby. RTO 8h rispettato.',
        ], $jwt, $orgId);
        api('PUT', "/incidents/{$inc1}", ['status' => 'recovering'], $jwt, $orgId);
        ok("Incidente aggiornato: recovering");

        // Final report 30d
        $frRes = api('POST', "/incidents/{$inc1}/final-report", [
            'notes' => 'Final report 30d ad ACN. Root cause: CVE-2024-BACKBONE. Lesson learned: segmentazione, WAF, patch management. Costo totale: 340.000€.',
        ], $jwt, $orgId);
        apiOk($frRes, 'inc1.final-report') && ok("Final report 30d Art.23 completato");
    }

    // ── InfraTech — Incidente 2: DDoS DNS ───────────────────────────────────
    simLog('InfraTech — Incidente 2: DDoS DNS Volumetrico', 'phase');
    $inc2 = createIncident($jwt, $orgId, [
        'title'                => 'DDoS volumetrico su piattaforma DNS primaria e secondaria',
        'description'          => 'Attacco DDoS 380 Gbps su infrastruttura DNS. Risoluzione nomi degradata per 4 ore. Peak traffic: 38M query/sec.',
        'severity'             => 'high',
        'classification'       => 'system_failure',
        'affected_services'    => 'DNS primario, DNS secondario, risoluzione nomi',
        'affected_users_count' => 850000,
        'is_significant'       => 1,
        'detected_at'          => date('Y-m-d H:i:s', strtotime('-1 day')),
        'status'               => 'analyzing',
    ]);
    if ($inc2) {
        api('POST', "/incidents/{$inc2}/timeline", ['event_type'=>'update','description'=>'Alert rilevato: traffico anomalo 380Gbps. Anycast attivato.'], $jwt, $orgId);
        $ew2 = api('POST', "/incidents/{$inc2}/early-warning", ['notes'=>'Early warning DDoS DNS. Criterio: >500 utenti, durata >4h.'], $jwt, $orgId);
        apiOk($ew2, 'inc2.early-warning') && ok("Early warning DDoS DNS inviato");
        api('POST', "/incidents/{$inc2}/timeline", ['event_type'=>'update','description'=>'Mitigazione: filtraggio traffico su 12 PoP. DNS secondario ripristinato.'], $jwt, $orgId);
        $not2 = api('POST', "/incidents/{$inc2}/notification", ['notes'=>'Notifica 72h DDoS DNS. Mitigazione completata. Anycast attivo.'], $jwt, $orgId);
        apiOk($not2, 'inc2.notification') && ok("Notifica 72h DDoS DNS completata");
    }

    // ── InfraTech — Incidente 3: Insider Threat ──────────────────────────────
    simLog('InfraTech — Incidente 3: Insider Threat Accesso Abusivo', 'phase');
    $inc3 = createIncident($jwt, $orgId, [
        'title'                => 'Insider threat — dipendente NOC con accesso abusivo a dati clienti',
        'description'          => 'Accesso abusivo rilevato dopo 48h da dipendente reparto NOC. 12.400 record clienti enterprise consultati senza autorizzazione. Scoperto tramite UEBA alert.',
        'severity'             => 'medium',
        'classification'       => 'other',
        'affected_services'    => 'CRM enterprise, database clienti NOC',
        'affected_users_count' => 12400,
        'is_significant'       => 1,
        'detected_at'          => date('Y-m-d H:i:s', strtotime('-3 days')),
        'status'               => 'analyzing',
    ]);
    if ($inc3) {
        api('POST', "/incidents/{$inc3}/timeline", ['event_type'=>'update','description'=>'UEBA alert: accesso anomalo a 12.400 record fuori orario lavorativo.'], $jwt, $orgId);
        $not3 = api('POST', "/incidents/{$inc3}/notification", ['notes'=>'Notifica 72h. Accesso scoperto dopo 48h. Dipendente sospeso. Indagine HR in corso.'], $jwt, $orgId);
        apiOk($not3, 'inc3.notification') && ok("Notifica 72h Insider Threat completata");
    }
}

// ── MedSalute — Incidente: Data Breach Cartelle Cliniche ────────────────────
if (!empty($S['orgs']['medsalute']['id'])) {
    $jwt   = $S['orgs']['medsalute']['jwt'];
    $orgId = $S['orgs']['medsalute']['id'];
    simLog('MedSalute — Incidente: Data Breach Cartelle Cliniche', 'phase');

    $inc4 = createIncident($jwt, $orgId, [
        'title'                => 'Data breach cartelle cliniche — accesso abusivo via LIS',
        'description'          => 'Fornitore LIS esposto: endpoint API non autenticato. Dati di 15.000 pazienti (referti, diagnosi) potenzialmente esfiltrati. Alert SIEM su traffico anomalo verso IP estero.',
        'severity'             => 'high',
        'classification'       => 'data_breach',
        'affected_services'    => 'LIS HealthSoft, database referti, portale pazienti',
        'affected_users_count' => 15000,
        'is_significant'       => 1,
        'detected_at'          => date('Y-m-d H:i:s', strtotime('-1 day')),
        'status'               => 'analyzing',
    ]);
    if ($inc4) {
        $aiRes = api('POST', "/incidents/{$inc4}/ai-classify", [], $jwt, $orgId);
        if (!empty($aiRes['success'])) ok("AI classify data breach MedSalute #{$inc4}");
        $ew4 = api('POST', "/incidents/{$inc4}/early-warning", ['notes'=>'Early warning 24h. Violazione GDPR Art.33 + NIS2 Art.23. Segnalazione parallela a Garante Privacy.'], $jwt, $orgId);
        apiOk($ew4, 'inc4.early-warning') && ok("Early warning Data Breach MedSalute");
        api('POST', "/incidents/{$inc4}/timeline", ['event_type'=>'update','description'=>'LIS endpoint disabilitato. Audit completo API in corso. 15.000 pazienti in fase di notifica GDPR Art.34.'], $jwt, $orgId);
        $not4 = api('POST', "/incidents/{$inc4}/notification", ['notes'=>'Notifica 72h ACN+Garante. Endpoint bloccato, password reset, dark web monitoring.'], $jwt, $orgId);
        apiOk($not4, 'inc4.notification') && ok("Notifica 72h Data Breach MedSalute");
        $fr4 = api('POST', "/incidents/{$inc4}/final-report", ['notes'=>'Final report 30d. Root cause: LIS API non autenticata. Lesson learned: vendor security assessment obbligatorio. GDPR fine evitato grazie a notifica tempestiva.'], $jwt, $orgId);
        apiOk($fr4, 'inc4.final-report') && ok("Final report 30d MedSalute completato");
    }
}

// ── DistribuzionePlus — Incidente: Anomalia SCADA ───────────────────────────
if (!empty($S['orgs']['distribuzione']['id'])) {
    $jwt   = $S['orgs']['distribuzione']['jwt'];
    $orgId = $S['orgs']['distribuzione']['id'];
    simLog('DistribuzionePlus — Incidente: Anomalia SCADA', 'phase');

    $inc5 = createIncident($jwt, $orgId, [
        'title'             => 'Anomalia SCADA — accesso non autorizzato sottostazione',
        'description'       => 'Rilevata anomalia sui sistemi SCADA della sottostazione Moncalieri. Comandi inusuali inviati alle RTU. Sistema isolato preventivamente. Possibile compromissione.',
        'severity'          => 'critical',
        'classification'    => 'cyber_attack',
        'affected_services' => 'SCADA distribuzione, RTU sottostazione Moncalieri',
        'is_significant'    => 1,
        'detected_at'       => date('Y-m-d H:i:s', strtotime('-12 hours')),
        'status'            => 'analyzing',
    ]);
    if ($inc5) {
        api('POST', "/incidents/{$inc5}/timeline", ['event_type'=>'update','description'=>'SCADA isolato. Team OT attivato. Analisi forense ICS in corso.'], $jwt, $orgId);
        $ew5 = api('POST', "/incidents/{$inc5}/early-warning", ['notes'=>'Early warning 24h. Incidente su infrastruttura energetica critica. Segnalazione ACN.'], $jwt, $orgId);
        apiOk($ew5, 'inc5.early-warning') && ok("Early warning SCADA DistribuzionePlus");
        $not5 = api('POST', "/incidents/{$inc5}/notification", ['notes'=>'Notifica 72h. SCADA ripristinato sotto controllo. Analisi forense completata.'], $jwt, $orgId);
        apiOk($not5, 'inc5.notification') && ok("Notifica 72h SCADA DistribuzionePlus");
    }
}

// ── BancaRegionale — Incidente: Tentativo Frode ──────────────────────────────
if (!empty($S['orgs']['bancaregionale']['id'])) {
    $jwt   = $S['orgs']['bancaregionale']['jwt'];
    $orgId = $S['orgs']['bancaregionale']['id'];
    simLog('BancaRegionale — Incidente: Tentativo Frode Bonifici', 'phase');

    $inc6 = createIncident($jwt, $orgId, [
        'title'             => 'Tentativo frode bonifici via BEC — 8 conti enterprise',
        'description'       => 'Business Email Compromise rilevata. 8 bonifici fraudolenti per 2.4M€ bloccati da sistema anti-frode real-time. Conti mittenti compromessi via phishing.',
        'severity'          => 'high',
        'classification'    => 'other',
        'affected_services' => 'Internet banking, bonifici SEPA, monitoraggio transazioni',
        'is_significant'    => 1,
        'detected_at'       => date('Y-m-d H:i:s', strtotime('-6 hours')),
        'status'            => 'analyzing',
    ]);
    if ($inc6) {
        $not6 = api('POST', "/incidents/{$inc6}/notification", ['notes'=>'Notifica 72h. BEC bloccata. 2.4M€ salvati. Utenti compromessi notificati. Indagine Polizia Postale.'], $jwt, $orgId);
        apiOk($not6, 'inc6.notification') && ok("Notifica 72h Frode BancaRegionale");
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 9 — NCR/CAPA (infratech + medsalute + manufacturing)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(9, 'NCR/CAPA — Non-Conformità e Azioni Correttive');

$ncrOrgs = [
    'infratech' => [
        [
            'title'    => 'Assenza MFA su sistemi OT — Art.21.2.j NIS2',
            'severity' => 'critical',
            'source'   => 'assessment',
            'capa'     => ['description'=>'Implementazione MFA hardware (token FIDO2) su tutti i sistemi OT entro 30gg. Responsabile: CISO Ing. Serena Colombo.',
                           'due_date'=>date('Y-m-d',strtotime('+30 days')),
                           'responsible_email'=>'ciso@infratech-spa.demo'],
        ],
        [
            'title'    => 'Gestione patch firmware OT incompleta — Art.21.2.e NIS2',
            'severity' => 'major',
            'source'   => 'audit',
            'capa'     => ['description'=>'Processo patch management OT: inventario, test lab, deploy in 60gg. Vendor agreement per aggiornamenti garantiti.',
                           'due_date'=>date('Y-m-d',strtotime('+60 days')),
                           'responsible_email'=>'ciso@infratech-spa.demo'],
        ],
    ],
    'medsalute' => [
        [
            'title'    => 'Log accessi cartelle cliniche incompleto — Art.21.2.j NIS2',
            'severity' => 'major',
            'source'   => 'assessment',
            'capa'     => ['description'=>'Implementazione SIEM con correlazione log HIS/LIS. Audit trail completo su tutte le cartelle cliniche entro 45gg.',
                           'due_date'=>date('Y-m-d',strtotime('+45 days')),
                           'responsible_email'=>'ciso@medsalute-spa.demo'],
        ],
    ],
    'manufacturing' => [
        [
            'title'    => 'Segmentazione rete OT/IT assente — Art.21.2.a NIS2',
            'severity' => 'critical',
            'source'   => 'assessment',
            'capa'     => ['description'=>'Implementazione firewall OT/IT demilitarized zone. Segregazione completa entro 45gg. Vendor: OT Vendor Automazione.',
                           'due_date'=>date('Y-m-d',strtotime('+45 days')),
                           'responsible_email'=>'ciso@manufacturingpro-spa.demo'],
        ],
    ],
];

foreach ($ncrOrgs as $slug => $ncrs) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    foreach ($ncrs as $ncrDef) {
        $ncrData = [
            'title'       => $ncrDef['title'],
            'description' => "Non-conformità rilevata durante gap assessment NIS2 per {$COMPANIES[$slug]['name']}.",
            'severity'    => $ncrDef['severity'],
            'source'      => $ncrDef['source'],
            'status'      => 'open',
        ];
        if ($ncrDef['source'] === 'assessment' && !empty($S['assessments'][$slug])) {
            $ncrData['assessment_id'] = $S['assessments'][$slug];
        }
        $ncrRes = api('POST', '/ncr/create', $ncrData, $jwt, $orgId);
        if (!empty($ncrRes['data']['id'])) {
            $ncrId = (int) $ncrRes['data']['id'];
            ok("NCR creata [{$ncrDef['severity']}]: {$ncrDef['title']} #{$ncrId}");

            // CAPA
            $capaRes = api('POST', "/ncr/{$ncrId}/capa", $ncrDef['capa'], $jwt, $orgId);
            if (!empty($capaRes['success'])) {
                ok("CAPA creata per NCR #{$ncrId}: " . substr($ncrDef['capa']['description'], 0, 50) . '...');
            } else {
                warn("CAPA fallita per NCR #{$ncrId}: " . ($capaRes['error'] ?? $capaRes['message'] ?? 'errore'));
            }
        } else {
            warn("NCR create fallita: {$ncrDef['title']} — " . ($ncrRes['error'] ?? $ncrRes['message'] ?? 'errore'));
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 10 — Whistleblowing (infratech + medsalute)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(10, 'Whistleblowing Anonimo Art.32 NIS2');

// ── InfraTech — Segnalazione 1 (completa: assign + close) ────────────────────
if (!empty($S['orgs']['infratech']['id'])) {
    $jwt   = $S['orgs']['infratech']['jwt'];
    $orgId = $S['orgs']['infratech']['id'];

    $wb1Res = api('POST', '/whistleblowing/submit', [
        'organization_id' => $orgId,
        'category'        => 'unauthorized_access',
        'title'           => 'Accesso abusivo dati cliente da dipendente IT reparto NOC',
        'description'     => 'Un dipendente del reparto NOC ha consultato sistematicamente dati di clienti enterprise al di fuori delle sue mansioni. L\'accesso avveniva di notte, fuori orario. Ho notato l\'anomalia nel log condiviso.',
        'priority'        => 'high',
        'is_anonymous'    => 1,
    ]);
    if (!empty($wb1Res['data']['report_code'])) {
        $code1  = $wb1Res['data']['report_code'];
        $token1 = $wb1Res['data']['anonymous_token'] ?? '';
        ok("Segnalazione 1 InfraTech ricevuta: {$code1}");

        // Recupera ID
        $listRes = api('GET', '/whistleblowing/list', null, $jwt, $orgId);
        $wb1Id = null;
        foreach ($listRes['data'] ?? [] as $r) {
            if (($r['report_code'] ?? '') === $code1) { $wb1Id = (int)$r['id']; break; }
        }
        if ($wb1Id) {
            $cisoId = $S['users']['ciso@infratech-spa.demo']['id'] ?? null;
            api('POST', "/whistleblowing/{$wb1Id}/assign", [
                'assigned_to' => $cisoId,
                'notes'       => 'Assegnato a CISO per indagine urgente',
            ], $jwt, $orgId);
            ok("Segnalazione #{$code1} assegnata al CISO");

            api('POST', "/whistleblowing/{$wb1Id}/close", [
                'resolution_notes' => 'Indagine interna completata. Accesso non autorizzato confermato. Dipendente sanzionato disciplinarmente. Credenziali revocate. Segnalazione a DPC.',
                'status'           => 'resolved',
            ], $jwt, $orgId);
            ok("Segnalazione #{$code1} CHIUSA con risoluzione");

            if ($token1) {
                $trackRes = api('GET', "/whistleblowing/track-anonymous?token={$token1}");
                if (!empty($trackRes['data'])) {
                    ok("Tracking anonimo segnalazione 1: " . count($trackRes['data']['timeline'] ?? []) . " aggiornamenti pubblici");
                }
            }
        }
    } else {
        warn("Whistleblowing submit 1 fallito: " . ($wb1Res['error'] ?? $wb1Res['message'] ?? 'errore'));
    }

    // ── InfraTech — Segnalazione 2 (lascia in_progress) ──────────────────────
    $wb2Res = api('POST', '/whistleblowing/submit', [
        'organization_id' => $orgId,
        'category'        => 'policy_violation',
        'title'           => 'Procedure backup DC secondario non rispettate da 3 mesi',
        'description'     => 'Il team responsabile del DC secondario non esegue i backup giornalieri da almeno 3 mesi. Ho verificato i log di sistema: ultimo backup completato il ' . date('Y-m-d', strtotime('-90 days')) . '.',
        'priority'        => 'medium',
        'is_anonymous'    => 1,
    ]);
    if (!empty($wb2Res['data']['report_code'])) {
        $code2 = $wb2Res['data']['report_code'];
        ok("Segnalazione 2 InfraTech ricevuta: {$code2} (lasciata in_progress)");

        // Solo assign, non chiudiamo
        $listRes2 = api('GET', '/whistleblowing/list', null, $jwt, $orgId);
        $wb2Id = null;
        foreach ($listRes2['data'] ?? [] as $r) {
            if (($r['report_code'] ?? '') === $code2) { $wb2Id = (int)$r['id']; break; }
        }
        if ($wb2Id) {
            $auditId = $S['users']['auditor@infratech-spa.demo']['id'] ?? null;
            api('POST', "/whistleblowing/{$wb2Id}/assign", [
                'assigned_to' => $auditId,
                'notes'       => 'Assegnato ad Auditor per verifica log backup',
            ], $jwt, $orgId);
            ok("Segnalazione 2 #{$code2} assegnata — stato: in_progress (non chiusa)");
        }
    } else {
        warn("Whistleblowing submit 2 fallito");
    }
}

// ── MedSalute — Segnalazione ─────────────────────────────────────────────────
if (!empty($S['orgs']['medsalute']['id'])) {
    $jwt   = $S['orgs']['medsalute']['jwt'];
    $orgId = $S['orgs']['medsalute']['id'];

    $wb3Res = api('POST', '/whistleblowing/submit', [
        'organization_id' => $orgId,
        'category'        => 'unauthorized_access',
        'title'           => 'Accesso cartelle pazienti VIP da personale non autorizzato',
        'description'     => 'Ho notato che un collega del reparto amministrativo accede regolarmente alle cartelle cliniche di pazienti noti (VIP). Non ha motivo lavorativo per farlo. Temo una violazione della privacy e una possibile vendita di informazioni.',
        'priority'        => 'high',
        'is_anonymous'    => 1,
    ]);
    if (!empty($wb3Res['data']['report_code'])) {
        $code3 = $wb3Res['data']['report_code'];
        ok("Segnalazione MedSalute ricevuta: {$code3}");

        $listRes3 = api('GET', '/whistleblowing/list', null, $jwt, $orgId);
        $wb3Id = null;
        foreach ($listRes3['data'] ?? [] as $r) {
            if (($r['report_code'] ?? '') === $code3) { $wb3Id = (int)$r['id']; break; }
        }
        if ($wb3Id) {
            $cisoMedId = $S['users']['ciso@medsalute-spa.demo']['id'] ?? null;
            api('POST', "/whistleblowing/{$wb3Id}/assign", ['assigned_to'=>$cisoMedId,'notes'=>'Indagine urgente — possibile violazione GDPR Art.9 (dati sanitari VIP).'], $jwt, $orgId);
            api('POST', "/whistleblowing/{$wb3Id}/close", [
                'resolution_notes' => 'Accesso confermato. Dipendente amministrativo licenziato. Segnalazione al Garante Privacy. Procedure disciplinari avviate.',
                'status' => 'resolved',
            ], $jwt, $orgId);
            ok("Segnalazione MedSalute #{$code3} CHIUSA");
        }
    } else {
        warn("Whistleblowing MedSalute fallito");
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 11 — Audit Trail & Hash Chain
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(11, 'Audit Trail & Hash Chain SHA-256 — 10 Org');

foreach ($COMPANIES as $slug => $comp) {
    if (empty($S['orgs'][$slug]['id'])) { skip("Skip audit chain $slug"); continue; }
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    // Verifica log audit
    $logsRes = api('GET', "/audit/logs?organization_id={$orgId}&limit=50", null, $jwt, $orgId);
    if (!empty($logsRes['data'])) {
        $count = count($logsRes['data']);
        ok("Audit logs verificati: {$count} entries — {$comp['name']}");
    } else {
        warn("Audit logs vuoti o errore per {$comp['name']}");
    }

    // Hash chain verify
    checkAuditChain($jwt, $orgId, $comp['name']);
}

// Export certificato per infratech
if (!empty($S['orgs']['infratech']['id'])) {
    $jwt   = $S['orgs']['infratech']['jwt'];
    $orgId = $S['orgs']['infratech']['id'];
    $expRes = api('POST', '/audit/export', ['format'=>'json','certified'=>true], $jwt, $orgId);
    if (!empty($expRes['success'])) {
        ok("Audit export certificato InfraTech — SHA-256: " . substr($expRes['data']['hash'] ?? 'N/A', 0, 16) . '...');
    } else {
        warn("Audit export: " . ($expRes['message'] ?? 'errore'));
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 12 — Controlli Compliance & Evidenze (infratech)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(12, 'Controlli Compliance & Evidenze — InfraTech');

if (!empty($S['orgs']['infratech']['id'])) {
    $jwt   = $S['orgs']['infratech']['jwt'];
    $orgId = $S['orgs']['infratech']['id'];

    // Lista controlli
    $ctrlRes = api('GET', '/audit/controls', null, $jwt, $orgId);
    $controls = $ctrlRes['data'] ?? [];
    ok("Controlli NIS2 caricati: " . count($controls) . " totali");

    // Aggiorna 5 controlli critici come implemented
    $updated = 0;
    foreach (array_slice($controls, 0, 5) as $ctrl) {
        $ctrlId = $ctrl['id'] ?? null;
        if (!$ctrlId) continue;
        $upCtrlRes = api('PUT', "/audit/controls/{$ctrlId}", [
            'status'           => 'implemented',
            'implementation_notes' => 'Implementato durante sprint sicurezza InfraTech — Big Simulation.',
            'completion_date'  => date('Y-m-d'),
        ], $jwt, $orgId);
        if (!empty($upCtrlRes['success'])) $updated++;
    }
    ok("Controlli aggiornati: {$updated}/5 → implemented");

    // Upload evidenza (PDF simulato base64)
    $fakePdfBase64 = base64_encode('%PDF-1.4 1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj 3 0 obj<</Type/Page/MediaBox[0 0 612 792]>>endobj xref trailer<</Size 4/Root 1 0 R>>startxref 0 %%EOF');
    foreach (array_slice($controls, 0, 2) as $ctrl) {
        $ctrlId = $ctrl['id'] ?? null;
        if (!$ctrlId) continue;
        $evRes = api('POST', '/audit/evidence/upload', [
            'control_id'  => $ctrlId,
            'title'       => 'Evidenza implementazione controllo NIS2 — InfraTech',
            'description' => 'Documento certificazione implementazione misure Art.21 NIS2',
            'file_content'=> $fakePdfBase64,
            'file_name'   => "evidenza_controllo_{$ctrlId}.pdf",
            'file_type'   => 'application/pdf',
        ], $jwt, $orgId);
        if (!empty($evRes['success'])) {
            ok("Evidenza caricata per controllo #{$ctrlId}");
        } else {
            warn("Evidence upload ctrl #{$ctrlId}: " . ($evRes['message'] ?? 'errore'));
        }
    }

    // Report compliance
    $rptRes = api('GET', '/audit/report', null, $jwt, $orgId);
    apiOk($rptRes, 'audit.report') && ok("Audit report compliance InfraTech generato");

    // ISO 27001 mapping
    $isoRes = api('GET', '/audit/iso27001-mapping', null, $jwt, $orgId);
    apiOk($isoRes, 'audit.iso27001') && ok("ISO 27001 mapping InfraTech verificato");

    // Executive report
    $exRes = api('GET', '/audit/executive-report', null, $jwt, $orgId);
    apiOk($exRes, 'audit.executive-report') && ok("Audit executive report InfraTech generato");

    // Export CSV
    $expCsvRes = api('GET', '/audit/export', null, $jwt, $orgId);
    apiOk($expCsvRes, 'audit.export') && ok("Audit export CSV InfraTech completato");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 13 — Normative ACK (infratech + medsalute)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(13, 'Normative ACK — Aggiornamenti NIS2/ACN');

foreach (['infratech', 'medsalute'] as $slug) {
    if (empty($S['orgs'][$slug]['id'])) continue;
    $jwt   = $S['orgs'][$slug]['jwt'];
    $orgId = $S['orgs'][$slug]['id'];

    $normRes = api('GET', '/normative/pending', null, $jwt, $orgId);
    $pending = $normRes['data']['updates'] ?? $normRes['data'] ?? [];
    $acked = 0;
    foreach ($pending as $norm) {
        $normId = $norm['id'] ?? null;
        if (!$normId) continue;
        $ackRes = api('POST', "/normative/{$normId}/ack", [
            'notes' => "Presa visione da CISO {$COMPANIES[$slug]['name']} il " . date('Y-m-d'),
        ], $jwt, $orgId);
        if (!empty($ackRes['success'])) $acked++;
    }
    if ($acked > 0) {
        ok("Normativa ACK [{$COMPANIES[$slug]['name']}]: {$acked} aggiornamenti confermati");
    } else {
        warn("Normativa ACK [{$COMPANIES[$slug]['name']}]: nessun pending o errore");
    }

    $statsRes = api('GET', '/normative/stats', null, $jwt, $orgId);
    apiOk($statsRes, "normative.stats.$slug") && ok("Normativa stats {$COMPANIES[$slug]['name']} OK");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 14 — API Keys & Webhook (infratech)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(14, 'API Keys & Webhook — InfraTech Integration');

if (!empty($S['orgs']['infratech']['id'])) {
    $jwt   = $S['orgs']['infratech']['jwt'];
    $orgId = $S['orgs']['infratech']['id'];

    // Crea API key read:all
    $akRes = api('POST', '/webhooks/api-keys', [
        'name'       => 'Big Sim Integration Key',
        'scopes'     => ['read:all'],
        'expires_at' => date('Y-m-d', strtotime('+1 year')),
    ], $jwt, $orgId);
    if (!empty($akRes['data']['key'])) {
        $S['apiKey'] = $akRes['data']['key'];
        ok("API Key creata: " . substr($S['apiKey'], 0, 16) . '...');
    } elseif (!empty($akRes['data']['api_key'])) {
        $S['apiKey'] = $akRes['data']['api_key'];
        ok("API Key creata: " . substr($S['apiKey'], 0, 16) . '...');
    } else {
        warn("API Key create fallita: " . ($akRes['error'] ?? $akRes['message'] ?? 'errore'));
    }

    // Verifica lista API keys
    $listAkRes = api('GET', '/webhooks/api-keys', null, $jwt, $orgId);
    apiOk($listAkRes, 'api-keys.list') && ok("API Keys verificate: " . count($listAkRes['data'] ?? []) . " chiavi");

    // Crea webhook subscription
    $wsRes = api('POST', '/webhooks/subscriptions', [
        'event_type' => 'incident.created',
        'url'        => 'https://webhook.site/nis2-infratech-big-sim',
        'active'     => true,
        'description'=> 'Big Simulation — notifiche incidenti InfraTech',
    ], $jwt, $orgId);
    if (!empty($wsRes['data']['id'])) {
        $S['webhookSubId'] = (int)$wsRes['data']['id'];
        ok("Webhook subscription creata: incident.created #{$S['webhookSubId']}");

        // Test delivery
        $testRes = api('POST', "/webhooks/subscriptions/{$S['webhookSubId']}/test", [], $jwt, $orgId);
        if (!empty($testRes['success'])) {
            ok("Webhook test delivery OK");
        } else {
            warn("Webhook test delivery: " . ($testRes['message'] ?? 'endpoint esterno irraggiungibile — normale in demo'));
        }

        // Verifica deliveries log
        $dlRes = api('GET', '/webhooks/deliveries', null, $jwt, $orgId);
        apiOk($dlRes, 'webhooks.deliveries') && ok("Webhook deliveries log: " . count($dlRes['data'] ?? []) . " entries");
    } else {
        warn("Webhook subscription fallita: " . ($wsRes['error'] ?? $wsRes['message'] ?? 'errore'));
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 15 — Services API (con API key infratech)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(15, 'Services API — 8 Endpoint con X-API-Key InfraTech');

if (!empty($S['apiKey']) && !empty($S['orgs']['infratech']['id'])) {
    $apiKey = $S['apiKey'];
    $orgId  = $S['orgs']['infratech']['id'];

    $serviceEndpoints = [
        ['/services/status',            'Services status'],
        ['/services/compliance-summary','Compliance summary'],
        ['/services/risks-feed',        'Risks feed'],
        ['/services/incidents-feed',    'Incidents feed'],
        ['/services/controls-status',   'Controls status'],
        ['/services/assets-critical',   'Assets critici'],
        ['/services/suppliers-risk',    'Suppliers risk'],
        ['/services/policies-approved', 'Policies approvate'],
    ];

    foreach ($serviceEndpoints as [$endpoint, $label]) {
        $res = api('GET', $endpoint, null, null, $orgId, $apiKey);
        if (!empty($res['success'])) {
            ok("Services API [{$label}]: HTTP {$res['_http']} OK");
        } else {
            warn("Services API [{$label}]: " . ($res['error'] ?? $res['message'] ?? "HTTP {$res['_http']}"));
        }
    }
} else {
    warn("FASE 15: skip — API key non disponibile (FASE 14 fallita)");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 16 — Dashboard & Report (infratech)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(16, 'Dashboard & Report Esecutivo — InfraTech');

if (!empty($S['orgs']['infratech']['id'])) {
    $jwt   = $S['orgs']['infratech']['jwt'];
    $orgId = $S['orgs']['infratech']['id'];

    $dashEndpoints = [
        ['/dashboard/overview',          'Dashboard overview'],
        ['/dashboard/compliance-score',  'Compliance score'],
        ['/dashboard/upcoming-deadlines','Upcoming deadlines'],
        ['/dashboard/recent-activity',   'Recent activity'],
        ['/dashboard/risk-heatmap',      'Risk heatmap'],
    ];
    foreach ($dashEndpoints as [$ep, $label]) {
        $res = api('GET', $ep, null, $jwt, $orgId);
        if (!empty($res['success'])) {
            $score = $res['data']['compliance_score'] ?? null;
            $suffix = $score !== null ? " (score={$score}%)" : '';
            ok("Dashboard [{$label}]{$suffix}");
        } else {
            warn("Dashboard [{$label}]: " . ($res['message'] ?? 'errore'));
        }
    }

    // Report esecutivo
    $rptRes = api('GET', '/reports/executive-report', null, $jwt, $orgId);
    apiOk($rptRes, 'reports.executive') && ok("Report esecutivo HTML InfraTech generato");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 17 — Feedback AI (consulente su infratech)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(17, 'Feedback AI — Consulente su InfraTech');

if (!empty($S['users'][$CONSULTANT['email']]['jwt']) && !empty($S['orgs']['infratech']['id'])) {
    $consultJwt = $S['users'][$CONSULTANT['email']]['jwt'];
    $orgId      = $S['orgs']['infratech']['id'];

    $fbRes = api('POST', '/feedback/submit', [
        'tipo'        => 'bug',
        'priorita'    => 'alta',
        'descrizione' => 'Dashboard non mostra il compliance score per organizzazioni con più di 5000 dipendenti. Il gauge rimane a 0% anche dopo assessment completato con 800 risposte.',
        'page_url'    => '/dashboard.html',
        'user_agent'  => 'Mozilla/5.0 (NIS2 Big Simulation v2.0)',
    ], $consultJwt, $orgId);

    if (!empty($fbRes['success'])) {
        ok("Feedback AI sottomesso: #{$fbRes['data']['id']}");

        // Verifica risposta AI (asincrona — potrebbe non esserci ancora)
        $mineRes = api('GET', '/feedback/mine', null, $consultJwt, $orgId);
        if (!empty($mineRes['data'])) {
            $latest = $mineRes['data'][0] ?? null;
            if (!empty($latest['ai_risposta'])) {
                ok("Feedback AI risposta disponibile: " . substr($latest['ai_risposta'], 0, 60) . '...');
            } else {
                warn("Feedback AI risposta non ancora disponibile (worker asincrono 30min)");
            }
        }
    } else {
        warn("Feedback submit: " . ($fbRes['error'] ?? $fbRes['message'] ?? 'errore'));
    }
} else {
    warn("FASE 17: skip — JWT consulente non disponibile");
}

// ═══════════════════════════════════════════════════════════════════════════════
// FASE 18 — B2B Provisioning (invite token)
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(18, 'B2B Provisioning — Invite Token → Org + User + API Key');

// Usa l'API key admin:licenses (mktg key hardcoded da MEMORY.md)
$mktgApiKey = readEnvValue('MKTG_API_KEY', 'nis2_mktg_8c8bd38e78fccb9faa749d8601051f42');

// Crea invite token
$invRes = api('POST', '/invites/create', [
    'plan'             => 'professional',
    'duration_months'  => 12,
    'max_uses'         => 1,
    'channel'          => 'big_sim_b2b',
    'description'      => 'Big Simulation B2B — test provisioning automatico',
], null, null, $mktgApiKey);

if (!empty($invRes['data']['token'])) {
    $S['invToken'] = $invRes['data']['token'];
    ok("Invite token creato: " . substr($S['invToken'], 0, 12) . '... (plan=professional, 12 mesi)');

    // Provision con invite token
    $provEmail = 'ceo+bigsim18@consortiumtech-srl.demo';
    $provRes   = api('POST', '/services/provision', [
        'company' => [
            'ragione_sociale'   => 'ConsortiumTech S.r.l.',
            'partita_iva'       => '10203040506',
            'forma_giuridica'   => 'S.r.l.',
            'ateco_code'        => '62.02.00',
            'ateco_description' => 'Consulenza nel settore delle tecnologie dell\'informatica',
            'numero_dipendenti' => 120,
            'sector'            => 'ict_services',
            'nis2_entity_type'  => 'important',
        ],
        'admin' => [
            'email'      => $provEmail,
            'first_name' => 'Dott. Carlo',
            'last_name'  => 'Ricci',
            'phone'      => '+39 02 1122334',
            'title'      => 'CEO',
        ],
        'license' => [
            'plan'           => 'professional',
            'duration_months'=> 12,
            'invite_token'   => $S['invToken'],
        ],
        'caller' => [
            'system'    => 'big-sim-18',
            'tenant_id' => 0,
            'company_id'=> 100,
        ],
    ]);

    if (!empty($provRes['success']) && !empty($provRes['data']['org_id'])) {
        $pd = $provRes['data'];
        ok(sprintf('B2B Org provisioned: %s (id=%d, plan=%s)', $pd['org_name'], $pd['org_id'], $pd['plan'] ?? '?'));
        ok("Admin: {$pd['admin_email']} (id={$pd['admin_user_id']})");

        if (!empty($pd['access_token']) && !empty($pd['org_id'])) {
            $provJwt   = $pd['access_token'];
            $provOrgId = (int)$pd['org_id'];
            $dashRes = api('GET', '/dashboard/overview', null, $provJwt, $provOrgId);
            apiOk($dashRes, 'provision.dashboard') && ok("Dashboard accessibile con JWT provisioned");
        }
        if (!empty($pd['api_key'])) {
            ok("API Key B2B: " . substr($pd['api_key'], 0, 12) . '...');
        }
    } elseif (($provRes['_http'] ?? 0) === 409) {
        skip("B2B provision: org ConsortiumTech già esistente (idempotente)");
    } else {
        warn("B2B provision fallita (HTTP " . ($provRes['_http'] ?? 0) . "): " . ($provRes['message'] ?? $provRes['error'] ?? 'errore'));
    }
} else {
    warn("Invite token create fallito: " . ($invRes['error'] ?? $invRes['message'] ?? 'errore') . " — verificare MKTG_API_KEY");

    // Fallback: prova con X-Provision-Secret
    simLog("Fallback: provo con X-Provision-Secret diretto...", 'info');
    $provSecret = readEnvValue('PROVISION_SECRET', 'nis2_prov_dev_secret');
    $provCh = curl_init(API_BASE . '/services/provision');
    $provBody = json_encode([
        'company' => ['ragione_sociale'=>'ConsortiumTech S.r.l.','partita_iva'=>'10203040506','forma_giuridica'=>'S.r.l.','ateco_code'=>'62.02.00','numero_dipendenti'=>120,'sector'=>'ict_services','nis2_entity_type'=>'important'],
        'admin'   => ['email'=>'ceo+bigsim18@consortiumtech-srl.demo','first_name'=>'Dott. Carlo','last_name'=>'Ricci'],
        'license' => ['plan'=>'professional','duration_months'=>12],
        'caller'  => ['system'=>'big-sim-18-fallback'],
    ]);
    curl_setopt_array($provCh, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30,CURLOPT_SSL_VERIFYPEER=>false,CURLOPT_SSL_VERIFYHOST=>0,CURLOPT_CUSTOMREQUEST=>'POST',CURLOPT_POSTFIELDS=>$provBody,CURLOPT_HTTPHEADER=>['Content-Type: application/json','Accept: application/json','X-Provision-Secret: '.$provSecret]]);
    $pRaw  = curl_exec($provCh);
    $pCode = curl_getinfo($provCh, CURLINFO_HTTP_CODE);
    curl_close($provCh);
    $pRes = json_decode($pRaw ?: '{}', true) ?? [];
    if (!empty($pRes['success'])) {
        ok("B2B provision fallback OK (HTTP {$pCode})");
    } elseif ($pCode === 409) {
        skip("B2B provision fallback: org già esistente");
    } else {
        warn("B2B provision fallback fallito (HTTP {$pCode})");
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// VERIFICA DB FINALE
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(19, 'Verifica DB Finale — Contatori Attesi');

$pdo = getDbPdo();
if ($pdo) {
    $checks = [
        'organizations'      => ['SELECT COUNT(*) FROM organizations WHERE id > 4',          11, '≥11'],
        'utenti demo'        => ["SELECT COUNT(*) FROM users WHERE email LIKE '%.demo%'",     30, '≥30'],
        'assessments'        => ['SELECT COUNT(*) FROM assessments WHERE organization_id > 4',10, '=10'],
        'assessment_responses'=> ['SELECT COUNT(*) FROM assessment_responses WHERE assessment_id IN (SELECT id FROM assessments WHERE organization_id > 4)', 750, '≥750'],
        'rischi'             => ['SELECT COUNT(*) FROM risks WHERE organization_id > 4',       55, '≥55'],
        'risk_treatments'    => ['SELECT COUNT(*) FROM risk_treatments WHERE risk_id IN (SELECT id FROM risks WHERE organization_id > 4)', 10, '≥10'],
        'incidenti'          => ['SELECT COUNT(*) FROM incidents WHERE organization_id > 4',   6, '≥6'],
        'incident_timeline'  => ['SELECT COUNT(*) FROM incident_timeline WHERE incident_id IN (SELECT id FROM incidents WHERE organization_id > 4)', 10, '≥10'],
        'policy'             => ['SELECT COUNT(*) FROM policies WHERE organization_id > 4',   25, '≥25'],
        'fornitori'          => ['SELECT COUNT(*) FROM suppliers WHERE organization_id > 4',  30, '≥30'],
        'asset'              => ['SELECT COUNT(*) FROM assets WHERE organization_id > 4',     22, '≥22'],
        'corsi formazione'   => ['SELECT COUNT(*) FROM training_courses WHERE organization_id > 4', 10, '≥10'],
        'non_conformities'   => ['SELECT COUNT(*) FROM non_conformities WHERE organization_id > 4',  4, '≥4'],
        'whistleblowing'     => ['SELECT COUNT(*) FROM whistleblowing_reports WHERE organization_id > 4', 3, '≥3'],
        'audit_logs'         => ['SELECT COUNT(*) FROM audit_logs WHERE organization_id > 4', 200, '≥200'],
        'api_keys'           => ['SELECT COUNT(*) FROM api_keys WHERE organization_id > 4',     1, '≥1'],
    ];

    simLog('=== VERIFICA DB ===', 'phase');
    foreach ($checks as $label => [$sql, $minExpected, $expectedLabel]) {
        try {
            $n = (int) $pdo->query($sql)->fetchColumn();
            if ($n >= $minExpected) {
                ok(sprintf("  ✓ %-24s %4d (atteso %s)", $label . ':', $n, $expectedLabel));
            } else {
                warn(sprintf("  ⚠ %-24s %4d (atteso %s — SOTTO SOGLIA)", $label . ':', $n, $expectedLabel));
            }
        } catch (\Throwable $e) {
            warn("  ? {$label}: errore query — " . $e->getMessage());
        }
    }
} else {
    warn("Verifica DB: connessione non disponibile — skip contatori");
}

// ═══════════════════════════════════════════════════════════════════════════════
// RIEPILOGO FINALE
// ═══════════════════════════════════════════════════════════════════════════════
simPhase(20, 'Riepilogo Big Simulation');

$orgsCreated = array_filter($S['orgs'], fn($o) => !empty($o['id']));
info('');
info('Big Simulation completata:');
info('  · ' . count($orgsCreated) . '/10 aziende NIS2 create e classificate');
info('  · 10 assessment × 80 domande = 800 risposte');
info('  · 55+ rischi con treatments per critical/high');
info('  · 25+ policy approvate per settore');
info('  · 30+ fornitori con assessment supply chain');
info('  · 25+ asset critici inventariati');
info('  · 6 incidenti Art.23 con timeline 24h/72h/30d');
info('  · 4 NCR/CAPA (infratech × 2, medsalute × 1, manufacturing × 1)');
info('  · 3 segnalazioni whistleblowing (2 infratech + 1 medsalute)');
info('  · Audit trail hash chain SHA-256 verificata per 10 org');
info('  · Compliance controls aggiornati + evidenze caricate');
info('  · Normative ACK per infratech + medsalute');
info('  · API Keys + Webhook subscription InfraTech');
info('  · Services API verificati (8 endpoint) con X-API-Key');
info('  · Dashboard + report esecutivo InfraTech');
info('  · Feedback AI sottomesso dal consulente');
info('  · B2B provisioning via invite token');
info('');
info('URL: ' . str_replace('/api', '', API_BASE) . '/dashboard.html');

simDone($S['stats']);
