<?php
/**
 * NIS2 Agile - Incident Controller
 *
 * Gestione incidenti con workflow NIS2 Art. 23 (24h/72h/30d).
 */

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

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

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

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

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

        $incidents = Database::fetchAll(
            "SELECT i.*, u1.full_name as reported_by_name, u2.full_name as assigned_to_name
             FROM incidents i
             LEFT JOIN users u1 ON u1.id = i.reported_by
             LEFT JOIN users u2 ON u2.id = i.assigned_to
             WHERE i.{$where}
             ORDER BY i.detected_at DESC
             LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}",
            $params
        );

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

    /**
     * POST /api/incidents/create
     */
    public function create(): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager', 'employee']);
        $this->validateRequired(['title', 'classification', 'severity', 'detected_at']);

        $detectedAt = $this->getParam('detected_at');
        $isSignificant = (bool) $this->getParam('is_significant', false);

        // Regime obblighi NIS2 (Determina ACN 164179/2025): Allegato 3 essenziali / Allegato 4 importanti.
        $org = Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);
        $entityObligation = ($org && ($org['entity_type'] ?? '') === 'essential') ? 'essential' : 'important';

        // IS-4 (incidenti ricorrenti) non si applica ai soggetti importanti.
        $isType = $this->getParam('nis2_incident_type');
        $validIs = $entityObligation === 'essential' ? ['IS-1','IS-2','IS-3','IS-4'] : ['IS-1','IS-2','IS-3'];
        if ($isType !== null && !in_array($isType, $validIs, true)) {
            $isType = null;
        }

        $data = [
            'organization_id' => $this->getCurrentOrgId(),
            'incident_code'   => $this->generateCode('INC'),
            'title'           => trim($this->getParam('title')),
            'description'     => $this->getParam('description'),
            'classification'  => $this->getParam('classification'),
            'severity'        => $this->getParam('severity'),
            'is_significant'  => $isSignificant ? 1 : 0,
            'nis2_incident_type' => $isType,
            'entity_obligation'  => $entityObligation,
            'detected_at'     => $detectedAt,
            'affected_services'    => $this->getParam('affected_services'),
            'affected_users_count' => $this->getParam('affected_users_count'),
            'cross_border_impact'  => $this->getParam('cross_border_impact', 0),
            'malicious_action'     => $this->getParam('malicious_action', 0),
            'reported_by'     => $this->getCurrentUserId(),
            'assigned_to'     => $this->getParam('assigned_to'),
        ];

        // Calcola scadenze NIS2 Art. 23 se significativo
        if ($isSignificant) {
            $detectedTime = strtotime($detectedAt);
            $data['early_warning_due'] = date('Y-m-d H:i:s', $detectedTime + 24 * 3600);   // +24h
            $data['notification_due']  = date('Y-m-d H:i:s', $detectedTime + 72 * 3600);   // +72h
            $data['final_report_due']  = date('Y-m-d H:i:s', $detectedTime + 30 * 86400);  // +30 giorni
        }

        $incidentId = Database::insert('incidents', $data);

        // Aggiungi evento timeline
        Database::insert('incident_timeline', [
            'incident_id' => $incidentId,
            'event_type'  => 'detection',
            'description' => "Incidente rilevato: {$data['title']}",
            'created_by'  => $this->getCurrentUserId(),
        ]);

        $this->logAudit('incident_created', 'incident', $incidentId, [
            'severity' => $data['severity'], 'is_significant' => $isSignificant
        ]);

        // Dispatch webhook events
        try {
            $incident = array_merge($data, ['id' => $incidentId]);
            $webhookSvc = new WebhookService();
            $webhookSvc->dispatch($this->getCurrentOrgId(), 'incident.created', WebhookService::incidentPayload($incident, 'created'));
            if ($isSignificant) {
                $webhookSvc->dispatch($this->getCurrentOrgId(), 'incident.significant', WebhookService::incidentPayload($incident, 'significant'));
            }
        } catch (Throwable $e) {
            error_log('[WEBHOOK] dispatch error: ' . $e->getMessage());
        }

        $this->jsonSuccess([
            'id'            => $incidentId,
            'incident_code' => $data['incident_code'],
            'is_significant' => $isSignificant,
            'deadlines'     => $isSignificant ? [
                'early_warning' => $data['early_warning_due'],
                'notification'  => $data['notification_due'],
                'final_report'  => $data['final_report_due'],
            ] : null,
        ], 'Incidente registrato', 201);
    }

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

        $incident = Database::fetchOne(
            'SELECT i.*, u1.full_name as reported_by_name, u2.full_name as assigned_to_name
             FROM incidents i
             LEFT JOIN users u1 ON u1.id = i.reported_by
             LEFT JOIN users u2 ON u2.id = i.assigned_to
             WHERE i.id = ? AND i.organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );

        if (!$incident) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }

        $incident['timeline'] = Database::fetchAll(
            'SELECT it.*, u.full_name as created_by_name
             FROM incident_timeline it
             LEFT JOIN users u ON u.id = it.created_by
             WHERE it.incident_id = ?
             ORDER BY it.created_at',
            [$id]
        );

        $this->jsonSuccess($incident);
    }

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

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

        if (!$incident) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }

        $updates = [];
        $allowedFields = [
            'title', 'description', 'classification', 'severity', 'is_significant',
            'nis2_incident_type', 'status', 'affected_services', 'affected_users_count',
            'cross_border_impact', 'malicious_action', 'root_cause', 'remediation_actions',
            'lessons_learned', 'assigned_to',
        ];

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

        // Timbra automaticamente i timestamp di fase al primo ingresso nello stato
        // (per il calcolo metriche TTD/TTC/TTR). Non sovrascrive valori gia' presenti.
        if (isset($updates['status'])) {
            $now = date('Y-m-d H:i:s');
            $stamp = [
                'analyzing'  => 'triaged_at',
                'containing' => 'contained_at',
                'eradicating'=> 'eradicated_at',
                'recovering' => 'recovered_at',
            ];
            $col = $stamp[$updates['status']] ?? null;
            if ($col !== null && empty($incident[$col])) {
                $updates[$col] = $now;
            }
            if ($updates['status'] === 'closed') {
                $updates['closed_at'] = $now;
                if (empty($incident['recovered_at'])) $updates['recovered_at'] = $now;
            }
        }

        // Se diventa significativo, calcola scadenze
        if (isset($updates['is_significant']) && $updates['is_significant'] && !$incident['is_significant']) {
            $detectedTime = strtotime($incident['detected_at']);
            $updates['early_warning_due'] = date('Y-m-d H:i:s', $detectedTime + 24 * 3600);
            $updates['notification_due']  = date('Y-m-d H:i:s', $detectedTime + 72 * 3600);
            $updates['final_report_due']  = date('Y-m-d H:i:s', $detectedTime + 30 * 86400);
        }

        if (!empty($updates)) {
            Database::update('incidents', $updates, 'id = ?', [$id]);
            $this->logAudit('incident_updated', 'incident', $id, $updates);

            // Dispatch webhook: incident.updated e incident.significant se appena flaggato
            try {
                $updatedIncident = Database::fetchOne('SELECT * FROM incidents WHERE id = ?', [$id]);
                $webhookSvc = new WebhookService();
                $webhookSvc->dispatch($this->getCurrentOrgId(), 'incident.updated', WebhookService::incidentPayload($updatedIncident, 'updated'));
                if (isset($updates['is_significant']) && $updates['is_significant'] && !$incident['is_significant']) {
                    $webhookSvc->dispatch($this->getCurrentOrgId(), 'incident.significant', WebhookService::incidentPayload($updatedIncident, 'significant'));
                }
            } catch (Throwable $e) {
                error_log('[WEBHOOK] dispatch error: ' . $e->getMessage());
            }
        }

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

    /**
     * POST /api/incidents/{id}/timeline
     */
    public function addTimelineEvent(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager', 'employee']);
        $this->validateRequired(['event_type', 'description']);

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

        if (!$incident) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }

        $eventId = Database::insert('incident_timeline', [
            'incident_id' => $id,
            'event_type'  => $this->getParam('event_type'),
            'description' => $this->getParam('description'),
            'created_by'  => $this->getCurrentUserId(),
        ]);

        $this->jsonSuccess(['id' => $eventId], 'Evento aggiunto alla timeline', 201);
    }

    /**
     * POST /api/incidents/{id}/early-warning
     * Registra invio early warning (24h) al CSIRT
     */
    public function sendEarlyWarning(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        Database::update('incidents', [
            'early_warning_sent_at' => date('Y-m-d H:i:s'),
        ], 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);

        Database::insert('incident_timeline', [
            'incident_id' => $id,
            'event_type'  => 'notification',
            'description' => 'Early warning (24h) inviato al CSIRT nazionale (ACN)',
            'created_by'  => $this->getCurrentUserId(),
        ]);

        // Invia notifica email ai responsabili
        $this->notifyIncidentStakeholders($id, 'early_warning');

        $this->logAudit('early_warning_sent', 'incident', $id);
        $this->jsonSuccess(null, 'Early warning registrato');
    }

    /**
     * POST /api/incidents/{id}/notification
     * Registra invio notifica (72h) al CSIRT
     */
    public function sendNotification(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        Database::update('incidents', [
            'notification_sent_at' => date('Y-m-d H:i:s'),
        ], 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);

        Database::insert('incident_timeline', [
            'incident_id' => $id,
            'event_type'  => 'notification',
            'description' => 'Notifica incidente (72h) inviata al CSIRT nazionale (ACN)',
            'created_by'  => $this->getCurrentUserId(),
        ]);

        // Invia notifica email ai responsabili
        $this->notifyIncidentStakeholders($id, 'notification');

        $this->logAudit('notification_sent', 'incident', $id);
        $this->jsonSuccess(null, 'Notifica CSIRT registrata');
    }

    /**
     * POST /api/incidents/{id}/final-report
     * Registra invio report finale (30 giorni)
     */
    public function sendFinalReport(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

        Database::update('incidents', [
            'final_report_sent_at' => date('Y-m-d H:i:s'),
        ], 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);

        Database::insert('incident_timeline', [
            'incident_id' => $id,
            'event_type'  => 'notification',
            'description' => 'Report finale (30 giorni) inviato al CSIRT nazionale (ACN)',
            'created_by'  => $this->getCurrentUserId(),
        ]);

        // Invia notifica email ai responsabili
        $this->notifyIncidentStakeholders($id, 'final_report');

        $this->logAudit('final_report_sent', 'incident', $id);
        $this->jsonSuccess(null, 'Report finale registrato');
    }

    /**
     * Notifica stakeholder via email per milestone incidente
     */
    private function notifyIncidentStakeholders(int $incidentId, string $type): void
    {
        try {
            $incident = Database::fetchOne('SELECT * FROM incidents WHERE id = ?', [$incidentId]);
            $org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);

            if (!$incident || !$org) return;

            // Trova org_admin e compliance_manager
            $recipients = Database::fetchAll(
                'SELECT u.email, u.full_name FROM users u
                 JOIN user_organizations uo ON uo.user_id = u.id
                 WHERE uo.organization_id = ? AND uo.role IN ("org_admin", "compliance_manager") AND u.is_active = 1',
                [$this->getCurrentOrgId()]
            );

            if (empty($recipients)) return;

            $emailService = new EmailService();
            $emails = array_column($recipients, 'email');

            match ($type) {
                'early_warning'  => $emailService->sendIncidentEarlyWarning($incident, $org, $emails),
                'notification'   => $emailService->sendIncidentNotification($incident, $org, $emails),
                'final_report'   => $emailService->sendIncidentFinalReport($incident, $org, $emails),
            };
        } catch (Throwable $e) {
            error_log('[EMAIL_ERROR] ' . $e->getMessage());
            // Non bloccare il flusso principale per errori email
        }
    }

    /**
     * POST /api/incidents/{id}/ai-classify
     */
    public function aiClassify(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);

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

        if (!$incident) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }

        $org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);

        try {
            $aiService = new AIService();
            $classification = $aiService->classifyIncident($incident['title'], $incident['description'] ?? '', $org);

            $aiService->logInteraction(
                $this->getCurrentOrgId(),
                $this->getCurrentUserId(),
                'incident_classification',
                "Classify incident #{$id}: {$incident['title']}",
                substr(json_encode($classification), 0, 500)
            );

            $this->jsonSuccess($classification, 'Classificazione AI completata');
        } catch (Throwable $e) {
            $this->jsonError('Errore AI: ' . $e->getMessage(), 500, 'AI_ERROR');
        }
    }

    /**
     * GET /api/incidents/{id}/metrics
     * Calcola TTD/TTC/TTR e downtime dai timestamp di fase (in minuti).
     */
    public function metrics(int $id): void
    {
        $this->requireOrgAccess();
        $inc = Database::fetchOne(
            'SELECT detected_at, triaged_at, contained_at, eradicated_at, recovered_at, closed_at, affected_users_count
             FROM incidents WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$inc) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }
        $this->jsonSuccess($this->computeMetrics($inc));
    }

    /** Differenza in minuti tra due datetime, null se mancante. */
    private function minutesBetween(?string $from, ?string $to): ?int
    {
        if (empty($from) || empty($to)) return null;
        $a = strtotime($from); $b = strtotime($to);
        if ($a === false || $b === false) return null;
        return (int) round(($b - $a) / 60);
    }

    private function computeMetrics(array $inc): array
    {
        $det = $inc['detected_at'] ?? null;
        return [
            'ttd_minutes'      => $this->minutesBetween($det, $inc['triaged_at'] ?? null),
            'ttc_minutes'      => $this->minutesBetween($det, $inc['contained_at'] ?? null),
            'ttr_minutes'      => $this->minutesBetween($det, $inc['recovered_at'] ?? null),
            'downtime_minutes' => $this->minutesBetween($det, $inc['recovered_at'] ?? $inc['closed_at'] ?? null),
            'affected_users'   => isset($inc['affected_users_count']) ? (int) $inc['affected_users_count'] : null,
        ];
    }

    /**
     * GET /api/incidents/{id}/pir
     * Ritorna la Post-Incident Review (RC.CO-03) con le metriche calcolate.
     */
    public function getPir(int $id): void
    {
        $this->requireOrgAccess();
        $inc = Database::fetchOne(
            'SELECT * FROM incidents WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$inc) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }
        $pir = Database::fetchOne('SELECT * FROM incident_pir WHERE incident_id = ?', [$id]);
        if ($pir && !empty($pir['improvement_actions'])) {
            $pir['improvement_actions'] = json_decode($pir['improvement_actions'], true);
        }
        $this->jsonSuccess([
            'pir'     => $pir,
            'metrics' => $this->computeMetrics($inc),
            'reference' => 'RC.CO-03 (NIST CSF) - PIR da completare entro 2 settimane dalla chiusura per incidenti critici',
        ]);
    }

    /**
     * POST /api/incidents/{id}/pir
     * Crea o aggiorna la Post-Incident Review (upsert).
     */
    public function savePir(int $id): void
    {
        $this->requireOrgRole(['org_admin', 'compliance_manager']);
        $inc = Database::fetchOne(
            'SELECT * FROM incidents WHERE id = ? AND organization_id = ?',
            [$id, $this->getCurrentOrgId()]
        );
        if (!$inc) {
            $this->jsonError('Incidente non trovato', 404, 'INCIDENT_NOT_FOUND');
        }

        $m = $this->computeMetrics($inc);
        $actions = $this->getParam('improvement_actions');

        $fields = [
            'organization_id'         => $this->getCurrentOrgId(),
            'problem_statement'       => $this->getParam('problem_statement'),
            'why_1' => $this->getParam('why_1'), 'why_2' => $this->getParam('why_2'),
            'why_3' => $this->getParam('why_3'), 'why_4' => $this->getParam('why_4'),
            'why_5' => $this->getParam('why_5'),
            'root_cause'              => $this->getParam('root_cause'),
            'ttd_minutes'             => $m['ttd_minutes'],
            'ttc_minutes'             => $m['ttc_minutes'],
            'ttr_minutes'             => $m['ttr_minutes'],
            'downtime_minutes'        => $m['downtime_minutes'],
            'affected_users'          => $m['affected_users'],
            'estimated_cost_eur'      => $this->getParam('estimated_cost_eur'),
            'notification_compliance' => $this->getParam('notification_compliance') !== null ? (int)(bool)$this->getParam('notification_compliance') : null,
            'what_went_well'          => $this->getParam('what_went_well'),
            'what_to_improve'         => $this->getParam('what_to_improve'),
            'improvement_actions'     => is_array($actions) ? json_encode($actions, JSON_UNESCAPED_UNICODE) : null,
            'participants'            => $this->getParam('participants'),
            'reviewed_by'             => $this->getCurrentUserId(),
            'reviewed_at'             => date('Y-m-d H:i:s'),
            'status'                  => $this->getParam('status', 'draft'),
        ];

        $existing = Database::fetchOne('SELECT id FROM incident_pir WHERE incident_id = ?', [$id]);
        if ($existing) {
            Database::update('incident_pir', $fields, 'incident_id = ?', [$id]);
            $pirId = (int) $existing['id'];
        } else {
            $fields['incident_id'] = $id;
            $pirId = Database::insert('incident_pir', $fields);
        }

        // Se la root cause e' definita, allineala anche all'incidente (campo legacy)
        if (!empty($fields['root_cause'])) {
            Database::update('incidents', ['root_cause' => $fields['root_cause']], 'id = ?', [$id]);
        }

        $this->logAudit('incident_pir_saved', 'incident', $id, ['pir_id' => $pirId, 'status' => $fields['status']]);
        $this->jsonSuccess(['pir_id' => $pirId, 'metrics' => $m], 'Post-Incident Review salvata');
    }
}
