<?php
/**
 * NIS2 Agile - Risk Controller
 *
 * Gestione rischi cyber, matrice rischi, trattamenti.
 */

require_once __DIR__ . '/BaseController.php';
require_once APP_PATH . '/services/AIService.php';
require_once APP_PATH . '/services/WebhookService.php';
require_once APP_PATH . '/services/FairService.php';

class RiskController extends BaseController
{
    /**
     * GET /api/risks/list
     */
    public function list(): void
    {
        $this->requireOrgAccess();
        $pagination = $this->getPagination();

        $where = 'organization_id = ?';
        $params = [$this->getCurrentOrgId()];

        // Filtri opzionali
        if ($this->hasParam('status')) {
            $where .= ' AND status = ?';
            $params[] = $this->getParam('status');
        }
        if ($this->hasParam('category')) {
            $where .= ' AND category = ?';
            $params[] = $this->getParam('category');
        }

        $total = Database::count('risks', $where, $params);

        $risks = Database::fetchAll(
            "SELECT r.*, u.full_name as owner_name
             FROM risks r
             LEFT JOIN users u ON u.id = r.owner_user_id
             WHERE r.{$where}
             ORDER BY r.inherent_risk_score DESC
             LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}",
            $params
        );

        $this->jsonPaginated($risks, $total, $pagination['page'], $pagination['per_page']);
    }

    /**
     * POST /api/risks/create
     */
    public function create(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $this->validateRequired(['title', 'category']);

        $likelihood = (int) $this->getParam('likelihood', 0);
        $impact = (int) $this->getParam('impact', 0);

        $riskId = Database::insert('risks', [
            'organization_id'     => $this->getCurrentOrgId(),
            'risk_code'           => $this->generateCode('RSK'),
            'title'               => trim($this->getParam('title')),
            'description'         => $this->getParam('description'),
            'category'            => $this->getParam('category'),
            'threat_source'       => $this->getParam('threat_source'),
            'vulnerability'       => $this->getParam('vulnerability'),
            'affected_assets'     => $this->getParam('affected_assets') ? json_encode($this->getParam('affected_assets')) : null,
            'likelihood'          => $likelihood,
            'impact'              => $impact,
            'inherent_risk_score' => $likelihood * $impact,
            'treatment'           => $this->getParam('treatment', 'mitigate'),
            'owner_user_id'       => $this->getParam('owner_user_id'),
            'review_date'         => $this->getParam('review_date'),
            'nis2_article'        => $this->getParam('nis2_article'),
        ]);

        $this->logAudit('risk_created', 'risk', $riskId);

        // Dispatch webhook per rischi HIGH/CRITICAL
        $riskScore = $likelihood * $impact;
        if ($riskScore >= 12) { // HIGH: 12-16, CRITICAL: >16 (su scala 5x5)
            try {
                $riskData = Database::fetchOne('SELECT * FROM risks WHERE id = ?', [$riskId]);
                $riskLevel = $riskScore >= 20 ? 'critical' : 'high';
                $riskData['risk_level'] = $riskLevel;
                (new WebhookService())->dispatch(
                    $this->getCurrentOrgId(),
                    'risk.high_created',
                    WebhookService::riskPayload($riskData, 'created')
                );
            } catch (Throwable $e) {
                error_log('[WEBHOOK] dispatch error: ' . $e->getMessage());
            }
        }

        $this->jsonSuccess(['id' => $riskId], 'Rischio registrato', 201);
    }

    /**
     * GET /api/risks/{id}
     */
    public function get(int $id): void
    {
        $this->requireOrgAccess();

        $risk = Database::fetchOne(
            'SELECT r.*, u.full_name as owner_name
             FROM risks r
             LEFT JOIN users u ON u.id = r.owner_user_id
             WHERE r.id = ? AND r.organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$risk) {
            $this->jsonError('Rischio non trovato', 404, 'RISK_NOT_FOUND');
        }

        // Carica trattamenti
        $risk['treatments'] = Database::fetchAll(
            'SELECT rt.*, u.full_name as responsible_name
             FROM risk_treatments rt
             LEFT JOIN users u ON u.id = rt.responsible_user_id
             WHERE rt.risk_id = ?
             ORDER BY rt.due_date',
            [$id]
        );

        $this->jsonSuccess($risk);
    }

    /**
     * PUT /api/risks/{id}
     */
    public function update(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $risk = Database::fetchOne(
            'SELECT * FROM risks WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$risk) {
            $this->jsonError('Rischio non trovato', 404, 'RISK_NOT_FOUND');
        }

        $updates = [];
        $allowedFields = [
            'title', 'description', 'category', 'threat_source', 'vulnerability',
            'likelihood', 'impact', 'treatment', 'residual_likelihood', 'residual_impact',
            'status', 'owner_user_id', 'review_date', 'nis2_article',
        ];

        foreach ($allowedFields as $field) {
            if ($this->hasParam($field)) {
                $updates[$field] = $this->getParam($field);
            }
        }

        // Ricalcola score se likelihood o impact cambiano
        $likelihood = (int) ($updates['likelihood'] ?? $risk['likelihood']);
        $impact = (int) ($updates['impact'] ?? $risk['impact']);
        $updates['inherent_risk_score'] = $likelihood * $impact;

        if (isset($updates['residual_likelihood']) || isset($updates['residual_impact'])) {
            $resLikelihood = (int) ($updates['residual_likelihood'] ?? $risk['residual_likelihood']);
            $resImpact = (int) ($updates['residual_impact'] ?? $risk['residual_impact']);
            $updates['residual_risk_score'] = $resLikelihood * $resImpact;
        }

        if (!empty($updates)) {
            Database::update('risks', $updates, 'id = ?', [$id]);
            $this->logAudit('risk_updated', 'risk', $id, $updates);
        }

        $this->jsonSuccess($updates, 'Rischio aggiornato');
    }

    /**
     * DELETE /api/risks/{id}
     */
    public function delete(int $id): void
    {
        $this->requireOrgRole(['org_admin']);

        $deleted = Database::delete('risks', 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);

        if ($deleted === 0) {
            $this->jsonError('Rischio non trovato', 404, 'RISK_NOT_FOUND');
        }

        $this->logAudit('risk_deleted', 'risk', $id);
        $this->jsonSuccess(null, 'Rischio eliminato');
    }

    /**
     * POST /api/risks/{id}/treatments
     */
    public function addTreatment(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $this->validateRequired(['action_description']);

        // Verifica che il rischio esista per l'organizzazione
        $risk = Database::fetchOne(
            'SELECT id FROM risks WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$risk) {
            $this->jsonError('Rischio non trovato', 404, 'RISK_NOT_FOUND');
        }

        $treatmentId = Database::insert('risk_treatments', [
            'risk_id'             => $id,
            'action_description'  => $this->getParam('action_description'),
            'responsible_user_id' => $this->getParam('responsible_user_id'),
            'due_date'            => $this->getParam('due_date'),
            'status'              => 'planned',
            'notes'               => $this->getParam('notes'),
        ]);

        $this->logAudit('treatment_added', 'risk', $id, ['treatment_id' => $treatmentId]);

        $this->jsonSuccess(['id' => $treatmentId], 'Trattamento aggiunto', 201);
    }

    /**
     * PUT /api/risks/treatments/{id}
     */
    public function updateTreatment(int $treatmentId): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $treatment = Database::fetchOne(
            'SELECT rt.* FROM risk_treatments rt
             JOIN risks r ON r.id = rt.risk_id
             WHERE rt.id = ? AND r.organization_id = ?',
            [$treatmentId, $this->getCurrentOrgId()]
        );

        if (!$treatment) {
            $this->jsonError('Trattamento non trovato', 404, 'TREATMENT_NOT_FOUND');
        }

        $updates = [];
        foreach (['action_description', 'responsible_user_id', 'due_date', 'status', 'completion_date', 'notes'] as $field) {
            if ($this->hasParam($field)) {
                $updates[$field] = $this->getParam($field);
            }
        }

        if (!empty($updates)) {
            Database::update('risk_treatments', $updates, 'id = ?', [$treatmentId]);
            $this->logAudit('treatment_updated', 'risk_treatment', $treatmentId, $updates);
        }

        $this->jsonSuccess($updates, 'Trattamento aggiornato');
    }

    /**
     * GET /api/risks/matrix
     */
    public function getRiskMatrix(): void
    {
        $this->requireOrgAccess();

        $risks = Database::fetchAll(
            'SELECT id, title, category, likelihood, impact, inherent_risk_score,
                    residual_likelihood, residual_impact, residual_risk_score, status
             FROM risks
             WHERE organization_id = ? AND status != "closed"',
            [$this->getCurrentOrgId()]
        );

        $this->jsonSuccess([
            'risks' => $risks,
            'summary' => [
                'total'    => count($risks),
                'critical' => count(array_filter($risks, fn($r) => ($r['inherent_risk_score'] ?? 0) >= 20)),
                'high'     => count(array_filter($risks, fn($r) => ($r['inherent_risk_score'] ?? 0) >= 12 && ($r['inherent_risk_score'] ?? 0) < 20)),
                'medium'   => count(array_filter($risks, fn($r) => ($r['inherent_risk_score'] ?? 0) >= 6 && ($r['inherent_risk_score'] ?? 0) < 12)),
                'low'      => count(array_filter($risks, fn($r) => ($r['inherent_risk_score'] ?? 0) < 6)),
            ],
        ]);
    }

    /**
     * POST /api/risks/ai-suggest
     */
    public function aiSuggestRisks(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        $org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);
        $assets = Database::fetchAll(
            'SELECT name, asset_type, criticality FROM assets WHERE organization_id = ? AND status = "active"',
            [$this->getCurrentOrgId()]
        );

        try {
            $aiService = new AIService();
            $suggestions = $aiService->suggestRisks($org, $assets);

            $aiService->logInteraction(
                $this->getCurrentOrgId(),
                $this->getCurrentUserId(),
                'risk_suggestion',
                'Risk suggestions for ' . $org['sector'],
                substr(json_encode($suggestions), 0, 500)
            );

            $this->jsonSuccess($suggestions, 'Suggerimenti rischi generati');
        } catch (Throwable $e) {
            $this->jsonError('Errore AI: ' . $e->getMessage(), 500, 'AI_ERROR');
        }
    }

    // ══════════════════════════════════════════════════════════════════════
    // RISK QUANTITATIVO FAIR (P2) — analisi economica del rischio
    // ══════════════════════════════════════════════════════════════════════

    /**
     * POST /api/risks/{id}/fair
     * Calcola l'ALE (Annualized Loss Expectancy) via Monte Carlo FAIR e
     * persiste parametri + risultati sul rischio.
     * Body: { tef_min, tef_ml, tef_max, vuln (0-1), lm_min, lm_ml, lm_max, iterations? }
     */
    public function computeFair(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager', 'board_member']);
        $risk = Database::fetchOne(
            'SELECT id FROM risks WHERE id = ? AND organization_id = ? AND deleted_at IS NULL',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$risk) {
            $this->jsonError('Rischio non trovato', 404, 'NOT_FOUND');
        }

        // Limiti coerenti con le colonne DB per evitare overflow:
        //   fair_tef_* DECIMAL(10,2) -> max ~99.999.999  | fair_lm_* DECIMAL(14,2) -> max ~999 mld
        //   ale_* DECIMAL(16,2). Cappiamo TEF e Loss Magnitude a valori realistici così che
        //   ALE = TEF * vuln * LM resti entro DECIMAL(16,2).
        $TEF_MAX = 1000000.0;        // 1M eventi/anno (limite prudenziale)
        $LM_MAX  = 1000000000000.0;  // 1.000 mld EUR per evento
        $params = [
            'tef_min' => (float) $this->getParam('tef_min', 0),
            'tef_ml'  => (float) $this->getParam('tef_ml', 0),
            'tef_max' => (float) $this->getParam('tef_max', 0),
            'vuln'    => (float) $this->getParam('vuln', 1),
            'lm_min'  => (float) $this->getParam('lm_min', 0),
            'lm_ml'   => (float) $this->getParam('lm_ml', 0),
            'lm_max'  => (float) $this->getParam('lm_max', 0),
        ];
        // Validazione: niente valori negativi o NaN/inf
        foreach ($params as $k => $v) {
            if (!is_finite($v) || $v < 0) {
                $this->jsonError("Parametro FAIR non valido: {$k}", 422, 'INVALID_FAIR_PARAM');
            }
        }
        foreach (['tef_min', 'tef_ml', 'tef_max'] as $k) {
            if ($params[$k] > $TEF_MAX) {
                $this->jsonError("Frequenza eventi ({$k}) oltre il limite ammesso ({$TEF_MAX})", 422, 'FAIR_OUT_OF_RANGE');
            }
        }
        foreach (['lm_min', 'lm_ml', 'lm_max'] as $k) {
            if ($params[$k] > $LM_MAX) {
                $this->jsonError("Magnitudo di perdita ({$k}) oltre il limite ammesso ({$LM_MAX})", 422, 'FAIR_OUT_OF_RANGE');
            }
        }
        if ($params['vuln'] > 1) {
            $this->jsonError('La vulnerabilità deve essere compresa tra 0 e 1', 422, 'FAIR_OUT_OF_RANGE');
        }
        $iterations = (int) $this->getParam('iterations', FairService::DEFAULT_ITERATIONS);

        $sim = FairService::simulate($params, $iterations);

        Database::query(
            'UPDATE risks SET
                fair_tef_min=?, fair_tef_ml=?, fair_tef_max=?, fair_vuln=?,
                fair_lm_min=?, fair_lm_ml=?, fair_lm_max=?,
                ale_min=?, ale_ml=?, ale_max=?, ale_mean=?, fair_computed_at=NOW()
             WHERE id=? AND organization_id=?',
            [
                $params['tef_min'], $params['tef_ml'], $params['tef_max'], $params['vuln'],
                $params['lm_min'], $params['lm_ml'], $params['lm_max'],
                $sim['ale_min'], $sim['ale_ml'], $sim['ale_max'], $sim['ale_mean'],
                $id, $this->getCurrentOrgId(),
            ]
        );

        $this->logAudit('risk_fair_computed', 'risk', $id, ['ale_mean' => $sim['ale_mean']]);
        $this->jsonSuccess(['risk_id' => $id, 'parameters' => $params, 'result' => $sim], 'Analisi FAIR calcolata');
    }

    /**
     * GET /api/risks/fairRegister
     * Registro quantitativo: rischi con ALE calcolato, ordinati per esposizione
     * economica, + ALE totale di portafoglio.
     */
    public function fairRegister(): void
    {
        $this->requireOrgAccess();
        $rows = Database::fetchAll(
            'SELECT id, risk_code, title, category, status, inherent_risk_score,
                    ale_min, ale_ml, ale_max, ale_mean, fair_computed_at
             FROM risks
             WHERE organization_id = ? AND deleted_at IS NULL AND ale_mean IS NOT NULL
             ORDER BY ale_mean DESC',
            [$this->getCurrentOrgId()]
        );
        $totalAle = 0.0;
        foreach ($rows as $r) { $totalAle += (float) $r['ale_mean']; }

        $this->jsonSuccess([
            'total_risks_quantified' => count($rows),
            'portfolio_ale_mean'     => round($totalAle, 2),
            'currency'               => 'EUR',
            'risks'                  => $rows,
        ]);
    }

    // ══════════════════════════════════════════════════════════════════════
    // KEY RISK INDICATORS (KRI) — dashboard
    // ══════════════════════════════════════════════════════════════════════

    /** GET /api/risks/kri — lista KRI con stato semaforo ricalcolato. */
    public function listKri(): void
    {
        $this->requireOrgAccess();
        $rows = Database::fetchAll(
            'SELECT * FROM kri WHERE organization_id = ? ORDER BY FIELD(status,"red","amber","green","unknown"), name',
            [$this->getCurrentOrgId()]
        );
        foreach ($rows as &$r) {
            $r['status'] = self::evalKriStatus($r);
        }
        unset($r);

        $summary = ['green' => 0, 'amber' => 0, 'red' => 0, 'unknown' => 0];
        foreach ($rows as $r) { $summary[$r['status']]++; }

        $this->jsonSuccess(['summary' => $summary, 'total' => count($rows), 'kris' => $rows]);
    }

    /** POST /api/risks/kri — crea un KRI. */
    public function createKri(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager', 'board_member']);
        $this->validateRequired(['name']);

        $dir = $this->getParam('direction', 'higher_worse');
        if (!in_array($dir, ['higher_worse', 'lower_worse'], true)) $dir = 'higher_worse';
        $cat = $this->getParam('category', 'cyber');
        if (!in_array($cat, ['cyber','operational','compliance','supply_chain','physical','human'], true)) $cat = 'cyber';

        $data = [
            'organization_id'    => $this->getCurrentOrgId(),
            'name'               => trim($this->getParam('name')),
            'description'        => $this->getParam('description'),
            'category'           => $cat,
            'unit'               => $this->getParam('unit'),
            'current_value'      => $this->numOrNull($this->getParam('current_value')),
            'target_value'       => $this->numOrNull($this->getParam('target_value')),
            'threshold_warning'  => $this->numOrNull($this->getParam('threshold_warning')),
            'threshold_critical' => $this->numOrNull($this->getParam('threshold_critical')),
            'direction'          => $dir,
            'linked_risk_id'     => $this->numOrNull($this->getParam('linked_risk_id')),
            'measured_at'        => $this->getParam('current_value') !== null ? date('Y-m-d H:i:s') : null,
        ];
        $data['status'] = self::evalKriStatus($data);

        $kriId = Database::insert('kri', $data);
        $this->logAudit('kri_created', 'kri', $kriId);
        $this->jsonSuccess(['id' => $kriId, 'status' => $data['status']], 'KRI creato', 201);
    }

    /** PUT /api/risks/kri/{id} — aggiorna valore/soglie KRI (ricalcola stato). */
    public function updateKri(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager', 'board_member']);
        $kri = Database::fetchOne('SELECT * FROM kri WHERE id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
        if (!$kri) {
            $this->jsonError('KRI non trovato', 404, 'NOT_FOUND');
        }

        $fields = ['name', 'description', 'category', 'unit', 'current_value', 'target_value', 'threshold_warning', 'threshold_critical', 'direction', 'linked_risk_id'];
        $merged = $kri;
        foreach ($fields as $f) {
            $v = $this->getParam($f);
            if ($v !== null) {
                $merged[$f] = in_array($f, ['current_value','target_value','threshold_warning','threshold_critical','linked_risk_id'], true)
                    ? $this->numOrNull($v) : $v;
            }
        }
        $merged['status'] = self::evalKriStatus($merged);
        $measuredAt = $this->getParam('current_value') !== null ? date('Y-m-d H:i:s') : $kri['measured_at'];

        Database::query(
            'UPDATE kri SET name=?, description=?, category=?, unit=?, current_value=?, target_value=?,
                threshold_warning=?, threshold_critical=?, direction=?, linked_risk_id=?, status=?, measured_at=?
             WHERE id=? AND organization_id=?',
            [
                $merged['name'], $merged['description'], $merged['category'], $merged['unit'],
                $merged['current_value'], $merged['target_value'], $merged['threshold_warning'],
                $merged['threshold_critical'], $merged['direction'], $merged['linked_risk_id'],
                $merged['status'], $measuredAt, $id, $this->getCurrentOrgId(),
            ]
        );
        $this->logAudit('kri_updated', 'kri', $id);
        $this->jsonSuccess(['id' => $id, 'status' => $merged['status']], 'KRI aggiornato');
    }

    /** Stato semaforo KRI in base a valore corrente, soglie e direzione. */
    private static function evalKriStatus(array $k): string
    {
        $cur = $k['current_value'] ?? null;
        if ($cur === null || $cur === '') return 'unknown';
        $cur  = (float) $cur;
        $warn = isset($k['threshold_warning'])  && $k['threshold_warning']  !== null ? (float) $k['threshold_warning']  : null;
        $crit = isset($k['threshold_critical']) && $k['threshold_critical'] !== null ? (float) $k['threshold_critical'] : null;
        $dir  = $k['direction'] ?? 'higher_worse';

        if ($dir === 'higher_worse') {
            if ($crit !== null && $cur >= $crit) return 'red';
            if ($warn !== null && $cur >= $warn) return 'amber';
            return 'green';
        }
        if ($crit !== null && $cur <= $crit) return 'red';
        if ($warn !== null && $cur <= $warn) return 'amber';
        return 'green';
    }

    private function numOrNull($v)
    {
        return ($v === null || $v === '') ? null : (float) $v;
    }
}
