<?php
/**
 * NIS2 Agile - Asset Scoring Service
 *
 * Metodologia di scoring rilevanza NIS2 (requisito GV.OC-04).
 * Adattata dai mockup docs/nis2/assets.html + doc-relevant-systems.html.
 *
 * 6 criteri pesati, punteggio 0-100:
 *   C1 Criticita Operativa   0-25
 *   C2 Impatto Interruzione  0-25
 *   C3 Dati Trattati         0-20
 *   C4 Dipendenze            0-15
 *   C5 Esposizione           0-10
 *   C6 Obblighi Normativi    0-5
 *
 * Soglia rilevanza NIS2: score >= 40.
 * Classi: >=80 critico | 60-79 alto | 40-59 medio | 20-39 basso | <20 trascurabile.
 *
 * La logica e' PURA (nessun side effect / DB): si presta a unit test e riuso.
 */
class AssetScoringService
{
    public const RELEVANCE_THRESHOLD = 40;

    /**
     * Griglia ufficiale: per ogni criterio, la lista di opzioni selezionabili
     * (value => punti) con label per la UI. value e' una chiave stabile usata
     * dal frontend e salvata in relevance_criteria JSON.
     */
    public const GRID = [
        'c1_operational_criticality' => [
            'label'   => 'Criticita Operativa',
            'max'     => 25,
            'help'    => 'Quanto il sistema e essenziale per l\'erogazione dei servizi core business.',
            'options' => [
                'critical'   => ['label' => 'Critico',      'points' => 25],
                'very_high'  => ['label' => 'Molto Alto',   'points' => 20],
                'high'       => ['label' => 'Alto',         'points' => 15],
                'medium'     => ['label' => 'Medio',        'points' => 10],
                'low'        => ['label' => 'Basso',        'points' => 5],
                'negligible' => ['label' => 'Trascurabile', 'points' => 0],
            ],
        ],
        'c2_disruption_impact' => [
            'label'   => 'Impatto Interruzione',
            'max'     => 25,
            'help'    => 'Conseguenze di un\'interruzione in termini di durata e utenti impattati.',
            'options' => [
                'gt24h_gt70'  => ['label' => '>24h + >70% utenti',    'points' => 25],
                'h8_24_50_70' => ['label' => '8-24h + 50-70% utenti', 'points' => 20],
                'h4_8_30_50'  => ['label' => '4-8h + 30-50% utenti',  'points' => 15],
                'h1_4_10_30'  => ['label' => '1-4h + 10-30% utenti',  'points' => 10],
                'lt1h_lt10'   => ['label' => '<1h + <10% utenti',     'points' => 5],
                'none'        => ['label' => 'Nessun impatto',        'points' => 0],
            ],
        ],
        'c3_data_processed' => [
            'label'   => 'Dati Trattati',
            'max'     => 20,
            'help'    => 'Sensibilita e criticita dei dati gestiti dal sistema.',
            'options' => [
                'gdpr_art9'      => ['label' => 'Dati Sensibili Art.9 GDPR',     'points' => 20],
                'personal_large' => ['label' => 'Dati Personali larga scala',    'points' => 15],
                'personal_fin'   => ['label' => 'Dati Personali + Finanziari',   'points' => 10],
                'confidential'   => ['label' => 'Dati Aziendali Riservati',      'points' => 5],
                'public'         => ['label' => 'Dati Pubblici',                 'points' => 0],
            ],
        ],
        'c4_dependencies' => [
            'label'   => 'Dipendenze',
            'max'     => 15,
            'help'    => 'Quanti altri sistemi critici dipendono da questo sistema.',
            'options' => [
                'ge5_critical'   => ['label' => '>=5 sistemi critici',       'points' => 15],
                'n3_4_critical'  => ['label' => '3-4 sistemi critici',       'points' => 12],
                'n2_critical'    => ['label' => '2 sistemi critici',         'points' => 9],
                'n1_critical'    => ['label' => '1 sistema critico',         'points' => 6],
                'noncritical'    => ['label' => '1-2 sistemi non critici',   'points' => 3],
                'none'           => ['label' => 'Nessuna dipendenza',        'points' => 0],
            ],
        ],
        'c5_exposure' => [
            'label'   => 'Esposizione',
            'max'     => 10,
            'help'    => 'Superficie di attacco ed esposizione del sistema.',
            'options' => [
                'internet_no_mfa' => ['label' => 'Internet pubblico senza MFA', 'points' => 10],
                'internet_mfa'    => ['label' => 'Internet con MFA',            'points' => 8],
                'partner_net'     => ['label' => 'Reti partner/fornitori',      'points' => 6],
                'intranet'        => ['label' => 'Rete aziendale intranet',     'points' => 4],
                'mgmt_isolated'   => ['label' => 'Rete gestione isolata',       'points' => 2],
                'air_gapped'      => ['label' => 'Completamente isolato',       'points' => 0],
            ],
        ],
        'c6_regulatory' => [
            'label'   => 'Obblighi Normativi',
            'max'     => 5,
            'help'    => 'Se il sistema e soggetto a obblighi normativi o contrattuali specifici.',
            'options' => [
                'nis2_required'   => ['label' => 'Richiesto da NIS2',          'points' => 5],
                'mandatory_cert'  => ['label' => 'Certificazioni obbligatorie', 'points' => 4],
                'strict_sla'      => ['label' => 'Obblighi SLA stringenti',     'points' => 3],
                'external_audit'  => ['label' => 'Audit esterni regolari',      'points' => 2],
                'none'            => ['label' => 'Nessun obbligo',              'points' => 0],
            ],
        ],
    ];

    /**
     * Inferenza euristica dei 6 criteri GV.OC-04 dai campi di un asset
     * importato da CMDB/cloud. Permette lo scoring automatico in bulk senza
     * compilazione manuale della griglia. I valori restituiti sono chiavi
     * VALIDE della GRID, quindi calculate() li accetta senza eccezioni.
     *
     * Campi letti: criticality, asset_type, data_classification,
     *   internet_facing, dependencies_count, regulated.
     */
    public static function inferCriteria(array $a): array
    {
        $crit = strtolower((string) ($a['criticality'] ?? 'medium'));

        // C1 Criticita Operativa <- criticality
        $c1 = ['critical' => 'critical', 'high' => 'high', 'medium' => 'medium', 'low' => 'low'][$crit] ?? 'medium';

        // C2 Impatto Interruzione <- criticality (proxy)
        $c2 = ['critical' => 'gt24h_gt70', 'high' => 'h4_8_30_50', 'medium' => 'h1_4_10_30', 'low' => 'lt1h_lt10'][$crit] ?? 'h1_4_10_30';

        // C3 Dati Trattati <- data_classification (con proxy dal tipo asset)
        $dc = strtolower((string) ($a['data_classification'] ?? ''));
        $c3map = [
            'special' => 'gdpr_art9', 'art9' => 'gdpr_art9', 'sensitive' => 'gdpr_art9',
            'personal' => 'personal_large', 'pii' => 'personal_large',
            'financial' => 'personal_fin', 'confidential' => 'confidential',
            'internal' => 'confidential', 'public' => 'public',
        ];
        $c3 = $c3map[$dc] ?? ((($a['asset_type'] ?? '') === 'data') ? 'personal_large' : 'confidential');

        // C4 Dipendenze <- numero dipendenze critiche
        $deps = (int) ($a['dependencies_count'] ?? 0);
        $c4 = $deps >= 5 ? 'ge5_critical' : ($deps >= 3 ? 'n3_4_critical' : ($deps === 2 ? 'n2_critical' : ($deps === 1 ? 'n1_critical' : 'none')));

        // C5 Esposizione <- internet_facing
        $c5 = !empty($a['internet_facing']) ? 'internet_mfa' : 'intranet';

        // C6 Obblighi Normativi <- regulated
        $c6 = !empty($a['regulated']) ? 'nis2_required' : 'none';

        return [
            'c1_operational_criticality' => $c1,
            'c2_disruption_impact'       => $c2,
            'c3_data_processed'          => $c3,
            'c4_dependencies'            => $c4,
            'c5_exposure'                => $c5,
            'c6_regulatory'              => $c6,
        ];
    }

    /**
     * Calcola lo score a partire dalle selezioni dell'utente.
     *
     * @param array $criteria mappa criterioKey => optionValue
     *                        es. ['c1_operational_criticality' => 'critical', ...]
     * @return array{score:int, class:string, is_relevant:bool, breakdown:array, criticality:string}
     * @throws InvalidArgumentException se un criterio/opzione non e valido
     */
    public static function calculate(array $criteria): array
    {
        $score = 0;
        $breakdown = [];

        foreach (self::GRID as $key => $def) {
            if (!array_key_exists($key, $criteria)) {
                throw new InvalidArgumentException("Criterio mancante: {$key}");
            }
            $optVal = $criteria[$key];
            if (!isset($def['options'][$optVal])) {
                throw new InvalidArgumentException("Opzione non valida '{$optVal}' per criterio {$key}");
            }
            $pts = $def['options'][$optVal]['points'];
            $score += $pts;
            $breakdown[$key] = [
                'value'  => $optVal,
                'label'  => $def['options'][$optVal]['label'],
                'points' => $pts,
                'max'    => $def['max'],
            ];
        }

        $class = self::classify($score);

        return [
            'score'       => $score,
            'class'       => $class,
            'is_relevant' => $score >= self::RELEVANCE_THRESHOLD,
            'breakdown'   => $breakdown,
            // mapping verso l'enum legacy assets.criticality per coerenza UI esistente
            'criticality' => self::toLegacyCriticality($score),
        ];
    }

    /** Classe testuale secondo le soglie ufficiali. */
    public static function classify(int $score): string
    {
        if ($score >= 80) return 'critico';
        if ($score >= 60) return 'alto';
        if ($score >= 40) return 'medio';
        if ($score >= 20) return 'basso';
        return 'trascurabile';
    }

    /** Allinea lo score all'enum assets.criticality preesistente (low/medium/high/critical). */
    public static function toLegacyCriticality(int $score): string
    {
        if ($score >= 80) return 'critical';
        if ($score >= 60) return 'high';
        if ($score >= 40) return 'medium';
        return 'low';
    }

    /** Misure obbligatorie associate alla classe (per report GV.OC-04 / UI). */
    public static function requiredMeasures(string $class): array
    {
        return [
            'critico' => [
                'Monitoraggio continuo 24/7 (SIEM/SOC)',
                'Backup immutabile con test ripristino periodico',
                'MFA obbligatoria + accessi privilegiati controllati (PAM)',
                'Inclusione obbligatoria in BIA e piano di continuita',
                'Test di vulnerabilita/penetration test almeno annuali',
            ],
            'alto' => [
                'Monitoraggio in orario lavorativo + alerting',
                'Backup regolari con verifica integrita',
                'MFA per accessi remoti',
                'Inclusione in risk assessment ciclico',
            ],
            'medio' => [
                'Logging centralizzato',
                'Backup periodici',
                'Patch management documentato',
            ],
            'basso' => [
                'Inventario aggiornato',
                'Patch management standard',
            ],
            'trascurabile' => [
                'Censimento in inventario asset',
            ],
        ][$class] ?? [];
    }
}
