<?php
/**
 * NIS2 Agile - AI Service
 *
 * Integrazione con Anthropic Claude API per:
 * - Analisi gap analysis
 * - Suggerimenti rischi
 * - Generazione policy
 * - Classificazione incidenti
 * - Q&A NIS2
 */

class AIService
{
    private string $apiKey;
    private string $model;
    private int $maxTokens;
    private string $baseUrl = 'https://api.anthropic.com/v1/messages';

    public function __construct()
    {
        $this->apiKey = ANTHROPIC_API_KEY;
        $this->model = ANTHROPIC_MODEL;
        $this->maxTokens = ANTHROPIC_MAX_TOKENS;

        if (empty($this->apiKey) || $this->apiKey === 'sk-ant-xxxxx') {
            throw new RuntimeException('ANTHROPIC_API_KEY non configurata');
        }
    }

    /**
     * Blocco "fonti certe" da iniettare nei system prompt.
     * Elenca le fonti normative autoritative e impone di citarle, vietando
     * riferimenti inventati. (Richiesta utente 2026-05-29 - grounding su fonti certe.)
     */
    private function authoritativeSourcesBlock(): string
    {
        static $sources = null;
        if ($sources === null) {
            $sources = @include __DIR__ . '/../config/nis2_sources.php';
            if (!is_array($sources)) $sources = [];
        }
        if (empty($sources)) return '';

        $lines = [];
        foreach ($sources as $s) {
            $lines[] = '- ' . $s['citation'] . ' — ' . $s['authority'];
        }
        return "\n## FONTI NORMATIVE CERTE (cita SEMPRE quella pertinente)\n"
            . implode("\n", $lines)
            . "\n\nREGOLE SULLE FONTI (vincolanti):\n"
            . "1. Ogni affermazione normativa DEVE essere ancorata a una di queste fonti, citata esplicitamente (es. \"ai sensi dell'art. 23 della Direttiva (UE) 2022/2555\" o \"Determinazione ACN n. 164179/2025, Allegato 3\").\n"
            . "2. NON inventare numeri di articolo, determine, allegati o date: se non sei certo, dichiaralo e invita a verificare la fonte ufficiale.\n"
            . "3. Preferisci sempre il riferimento normativo italiano (D.Lgs. 138/2024 + Determine ACN) per gli obblighi operativi, e la Direttiva UE per i principi.\n";
    }

    /**
     * Analizza risultati gap analysis e genera raccomandazioni
     */
    public function analyzeGapAssessment(array $organization, array $responses, float $overallScore): array
    {
        $responseSummary = $this->summarizeResponses($responses);

        // Anonimizzazione: non inviare nome org né fatturato esatto ad API esterna
        $employeeRange = $this->employeeRange((int)($organization['employee_count'] ?? 0));

        $prompt = <<<PROMPT
Sei un esperto consulente di cybersecurity specializzato nella Direttiva NIS2 (EU 2022/2555) e nel D.Lgs. 138/2024 italiano.

Analizza i risultati della gap analysis per l'organizzazione seguente e fornisci raccomandazioni dettagliate.

## Organizzazione (dati anonimizzati)
- Settore: {$organization['sector']}
- Tipo entità NIS2: {$organization['entity_type']}
- Dimensione: {$employeeRange}

## Risultati Assessment (Score: {$overallScore}%)

{$responseSummary}

## Istruzioni
Fornisci la tua analisi in formato JSON con questa struttura:
{
  "executive_summary": "Riepilogo esecutivo (3-4 frasi)",
  "risk_level": "low|medium|high|critical",
  "top_priorities": [
    {
      "area": "Nome area",
      "nis2_article": "21.2.x",
      "current_status": "Breve descrizione stato attuale",
      "recommendation": "Azione raccomandata specifica",
      "effort": "low|medium|high",
      "timeline": "Tempistica suggerita"
    }
  ],
  "strengths": ["Punto di forza 1", "Punto di forza 2"],
  "quick_wins": ["Azione rapida 1", "Azione rapida 2"],
  "compliance_roadmap": [
    {"phase": 1, "title": "Titolo fase", "actions": ["Azione 1"], "duration": "X mesi"}
  ]
}

Rispondi SOLO con il JSON, senza testo aggiuntivo.
PROMPT;

        $response = $this->callAPI($prompt);
        return $this->parseJsonResponse($response);
    }

    /**
     * Suggerisce rischi basati su settore e asset
     */
    public function suggestRisks(array $organization, array $assets = []): array
    {
        $assetList = empty($assets) ? 'Non disponibile' : json_encode($assets, JSON_UNESCAPED_UNICODE);

        $prompt = <<<PROMPT
Sei un esperto di cybersecurity risk assessment. Genera una lista di rischi cyber per questa organizzazione.

## Organizzazione
- Settore: {$organization['sector']}
- Tipo entità NIS2: {$organization['entity_type']}
- Dipendenti: {$organization['employee_count']}

## Asset IT/OT
{$assetList}

Fornisci 10 rischi in formato JSON:
[
  {
    "title": "Titolo rischio",
    "description": "Descrizione dettagliata",
    "category": "cyber|operational|compliance|supply_chain|physical|human",
    "threat_source": "Fonte della minaccia",
    "vulnerability": "Vulnerabilità sfruttata",
    "likelihood": 1-5,
    "impact": 1-5,
    "nis2_article": "21.2.x",
    "suggested_treatment": "mitigate|accept|transfer|avoid",
    "mitigation_actions": ["Azione 1", "Azione 2"]
  }
]

Rispondi SOLO con il JSON array.
PROMPT;

        $response = $this->callAPI($prompt);
        return $this->parseJsonResponse($response);
    }

    /**
     * Genera bozza di policy
     */
    public function generatePolicy(string $category, array $organization, ?array $assessmentContext = null): array
    {
        $context = $assessmentContext ? json_encode($assessmentContext, JSON_UNESCAPED_UNICODE) : 'Non disponibile';

        $prompt = <<<PROMPT
Sei un esperto di information security policy writing. Genera una policy aziendale per la categoria "{$category}" conforme alla Direttiva NIS2.

## Organizzazione (dati anonimizzati)
- Settore: {$organization['sector']}
- Tipo entità NIS2: {$organization['entity_type']}

## Contesto Assessment
{$context}

Genera la policy in formato JSON:
{
  "title": "Titolo completo della policy",
  "version": "1.0",
  "category": "{$category}",
  "nis2_article": "21.2.x",
  "content": "Contenuto completo della policy in formato Markdown con sezioni: 1. Scopo, 2. Ambito di applicazione, 3. Responsabilità, 4. Definizioni, 5. Policy (regole specifiche), 6. Procedure operative, 7. Controlli, 8. Non conformità, 9. Revisione",
  "review_period_months": 12
}

La policy deve essere in italiano, professionale, specifica per il settore dell'organizzazione, e conforme ai requisiti NIS2.
Rispondi SOLO con il JSON.
PROMPT;

        $response = $this->callAPI($prompt);
        return $this->parseJsonResponse($response);
    }

    /**
     * Classifica un incidente e suggerisce severity
     */
    public function classifyIncident(string $title, string $description, array $organization): array
    {
        $entityType = $organization['entity_type'] ?? 'important';
        // Allegato 3 (soggetti essenziali) vs Allegato 4 (soggetti importanti):
        // gli importanti NON hanno l'obbligo sugli incidenti ricorrenti (IS-4).
        $isEssential = ($entityType === 'essential');
        $allowedIs = $isEssential ? 'IS-1|IS-2|IS-3|IS-4' : 'IS-1|IS-2|IS-3';
        $allegato = $isEssential ? 'Allegato 3 (soggetti essenziali)' : 'Allegato 4 (soggetti importanti)';
        $sourcesBlock = $this->authoritativeSourcesBlock();

        $prompt = <<<PROMPT
Sei un analista di incident response NIS2. Classifica il seguente incidente secondo il quadro normativo italiano.

## Quadro normativo di riferimento (cita la fonte pertinente in ogni campo motivazionale)
- Obbligo di notifica: art. 23 D.Lgs. 138/2024 e Direttiva (UE) 2022/2555.
- Classificazione incidenti significativi e tempistiche: Determinazione ACN n. 164179/2025, {$allegato}.
- Tempistiche: preallarme entro 24h, notifica completa entro 72h, relazione finale entro 1 mese dalla conoscenza dell'incidente significativo.
- Tipologie significative applicabili a questo soggetto ({$entityType}): {$allowedIs}.
{$sourcesBlock}

## Incidente
- Titolo: {$title}
- Descrizione: {$description}

## Organizzazione
- Settore: {$organization['sector']}
- Tipo entità: {$entityType}

Rispondi in formato JSON:
{
  "classification": "cyber_attack|data_breach|system_failure|human_error|natural_disaster|supply_chain|other",
  "nis2_incident_type": "{$allowedIs}|none",
  "severity": "low|medium|high|critical",
  "is_significant": true/false,
  "significance_reason": "Motivo con citazione esplicita della fonte (es. Determina ACN 164179/2025, {$allegato})",
  "requires_csirt_notification": true/false,
  "notification_basis": "Riferimento normativo dell'obbligo (es. art. 23 D.Lgs. 138/2024)",
  "suggested_actions": ["Azione immediata 1", "Azione immediata 2"],
  "potential_impact": "Descrizione impatto potenziale",
  "iocs_to_check": ["Indicatore 1", "Indicatore 2"]
}

Non inventare riferimenti: usa solo le fonti elencate sopra. Rispondi SOLO con il JSON.
PROMPT;

        $response = $this->callAPI($prompt);
        return $this->parseJsonResponse($response);
    }

    /**
     * Chiama Anthropic API
     */
    private function callAPI(string $prompt, ?string $systemPrompt = null): string
    {
        $system = $systemPrompt ?? <<<SYSTEM
Sei un esperto consulente di cybersecurity e compliance NIS2, integrato nella piattaforma SaaS "NIS2 Agile".

Moduli della piattaforma che conosci:
- Gap Analysis (80 domande, 10 categorie Art.21)
- Risk Management (registro rischi ISO 27005, matrice 5×5)
- Incident Management (flussi Art.23: early warning 24h, notifica 72h, report 30d)
- Policy Management (generazione e approvazione policy NIS2)
- Supply Chain (valutazione fornitori, risk scoring)
- Training (assegnazione corsi, compliance Art.20)
- Asset Inventory (asset critici, dipendenze)
- Audit & Report (controlli, evidenze, export CSV)
- NCR/CAPA (non conformità, azioni correttive)
- Whistleblowing (segnalazioni anomalie Art.32)
- Normative Updates (feed NIS2/ACN/DORA con ACK)
- Segnalazioni Feedback (bug reporting con risoluzione autonoma AI)
- Services API (endpoint pubblici per integrazioni B2B)

Rispondi sempre in italiano, in modo professionale, preciso e conciso.
Non includere dati identificativi dell'organizzazione nelle risposte.
SYSTEM;

        // Grounding su fonti certe: applicato solo al prompt di default
        // (i system prompt espliciti gestiscono le fonti per conto proprio).
        if ($systemPrompt === null) {
            $system .= "\n" . $this->authoritativeSourcesBlock();
        }

        $body = [
            'model'      => $this->model,
            'max_tokens' => $this->maxTokens,
            'system'     => $system,
            'messages'   => [
                ['role' => 'user', 'content' => $prompt],
            ],
        ];

        $ch = curl_init($this->baseUrl);
        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'x-api-key: ' . $this->apiKey,
                'anthropic-version: 2023-06-01',
            ],
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($body),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 120,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($curlError) {
            throw new RuntimeException('Errore connessione AI: ' . $curlError);
        }

        if ($httpCode !== 200) {
            $errorData = json_decode($response, true);
            $errorMessage = $errorData['error']['message'] ?? 'Errore API sconosciuto';
            throw new RuntimeException("Errore API AI ({$httpCode}): {$errorMessage}");
        }

        $data = json_decode($response, true);

        if (!isset($data['content'][0]['text'])) {
            throw new RuntimeException('Risposta AI non valida');
        }

        return $data['content'][0]['text'];
    }

    /**
     * Converte numero dipendenti in range anonimizzato
     */
    private function employeeRange(int $count): string
    {
        if ($count <= 0)   return 'Non specificato';
        if ($count <= 10)  return 'Micro impresa (1-10 dipendenti)';
        if ($count <= 50)  return 'Piccola impresa (11-50 dipendenti)';
        if ($count <= 250) return 'Media impresa (51-250 dipendenti)';
        if ($count <= 1000) return 'Grande impresa (251-1000 dipendenti)';
        return 'Grande organizzazione (>1000 dipendenti)';
    }

    /**
     * Riassume le risposte dell'assessment per il prompt AI
     */
    private function summarizeResponses(array $responses): string
    {
        $byCategory = [];
        foreach ($responses as $r) {
            $cat = $r['category'] ?? 'other';
            if (!isset($byCategory[$cat])) {
                $byCategory[$cat] = ['implemented' => 0, 'partial' => 0, 'not_implemented' => 0, 'na' => 0, 'total' => 0];
            }
            $byCategory[$cat]['total']++;
            match ($r['response_value']) {
                'implemented'     => $byCategory[$cat]['implemented']++,
                'partial'         => $byCategory[$cat]['partial']++,
                'not_implemented' => $byCategory[$cat]['not_implemented']++,
                'not_applicable'  => $byCategory[$cat]['na']++,
                default => null,
            };
        }

        $summary = '';
        foreach ($byCategory as $cat => $counts) {
            $pct = $counts['total'] > 0
                ? round(($counts['implemented'] * 100 + $counts['partial'] * 50) / (($counts['total'] - $counts['na']) * 100) * 100)
                : 0;
            $summary .= "- {$cat}: {$pct}% (implementati: {$counts['implemented']}, parziali: {$counts['partial']}, non implementati: {$counts['not_implemented']})\n";
        }

        return $summary;
    }

    /**
     * Parsing robusto della risposta JSON dall'AI
     */
    private function parseJsonResponse(string $response): array
    {
        // Rimuovi eventuale markdown code blocks
        $response = preg_replace('/^```(?:json)?\s*/m', '', $response);
        $response = preg_replace('/\s*```\s*$/m', '', $response);
        $response = trim($response);

        $data = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            error_log('[AI] JSON parse error: ' . json_last_error_msg() . ' | Response: ' . substr($response, 0, 500));
            return ['error' => 'Impossibile analizzare la risposta AI', 'raw' => substr($response, 0, 1000)];
        }

        return $data;
    }

    /**
     * Classifica una segnalazione feedback/bug per il sistema di ticketing interno
     *
     * @param string $tipo        Tipo segnalazione (bug|ux|funzionalita|domanda|altro)
     * @param string $descrizione Testo della segnalazione (non contiene PII org)
     * @return array ['categoria', 'priorita', 'suggerimento', 'risposta_utente']
     */
    public function classifyFeedback(string $tipo, string $descrizione): array
    {
        $prompt = <<<PROMPT
Sei un assistente tecnico per una piattaforma SaaS di compliance NIS2.
Analizza la seguente segnalazione e rispondi SOLO con un oggetto JSON valido.

Tipo segnalazione: {$tipo}
Descrizione: {$descrizione}

Rispondi con questo JSON (senza markdown, solo JSON puro):
{
  "categoria": "una tra: autenticazione, dashboard, rischi, incidenti, policy, assessment, formazione, fornitori, asset, report, impostazioni, performance, interfaccia, documentazione, altro",
  "priorita": "una tra: alta, media, bassa",
  "suggerimento": "suggerimento tecnico breve per il team di sviluppo (max 150 caratteri)",
  "risposta_utente": "risposta leggibile e rassicurante per l'utente finale in italiano (max 200 caratteri)"
}

Criteri priorità:
- alta: blocca funzionalità critica (compliance, accesso, salvataggio dati)
- media: degrada l'esperienza ma esiste un workaround
- bassa: miglioria estetica o funzionalità secondaria
PROMPT;

        $ch = curl_init($this->baseUrl);
        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'x-api-key: ' . $this->apiKey,
                'anthropic-version: 2023-06-01',
            ],
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode([
                'model'      => $this->model,
                'max_tokens' => 500,
                'system'     => 'Sei l\'assistente tecnico di NIS2 Agile, piattaforma SaaS per compliance NIS2. Conosci tutti i moduli: gap analysis, rischi, incidenti, policy, supply chain, formazione, asset, audit, NCR/CAPA, whistleblowing, normative, feedback. Rispondi SEMPRE con JSON puro, senza markdown né testo aggiuntivo.',
                'messages'   => [['role' => 'user', 'content' => $prompt]],
            ]),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
        ]);

        $response  = curl_exec($ch);
        $httpCode  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($curlError || $httpCode !== 200) {
            throw new RuntimeException("classifyFeedback AI error [{$httpCode}]: {$curlError}");
        }

        $data = json_decode($response, true);
        $text = $data['content'][0]['text'] ?? '';

        $result = $this->parseJsonResponse($text);

        // Normalizza priorità
        $validP = ['alta', 'media', 'bassa'];
        if (!in_array($result['priorita'] ?? '', $validP)) {
            $result['priorita'] = 'media';
        }

        return $result;
    }

    /**
     * Analisi cross-organizzazione per consulenti (L4)
     * Riceve dati già aggregati e anonimizzati dal controller.
     * NON deve mai ricevere nomi org, P.IVA o dati identificativi.
     */
    public function crossOrgAnalysis(int $orgCount, array $aggregated, string $question): array
    {
        $context = $this->buildCrossOrgContext($aggregated, $orgCount);

        $system = <<<SYSTEM
Sei un analista di cybersecurity e compliance NIS2 specializzato nell'analisi comparativa multi-organizzazione.
Rispondi sempre in italiano, in modo professionale e sintetico.

REGOLA FONDAMENTALE DI PRIVACY (non derogabile):
- Non fare mai riferimento a organizzazioni specifiche o identificabili.
- Rispondi SOLO con statistiche aggregate e trend generali.
- Se una risposta richiederebbe identificare una singola organizzazione, rifiuta con: "Dato non disponibile per protezione della privacy".
- Non inventare dati non presenti nel contesto.
SYSTEM;

        $prompt = <<<PROMPT
## Portfolio analizzato: {$orgCount} organizzazioni (dati aggregati e anonimizzati)

{$context}

## Domanda del consulente
{$question}

Rispondi in formato JSON:
{
  "answer": "Risposta principale dettagliata (3-6 paragrafi)",
  "key_findings": ["Risultato chiave 1", "Risultato chiave 2"],
  "recommendations": ["Raccomandazione pratica 1", "Raccomandazione pratica 2"],
  "risk_areas": ["Area critica 1", "Area critica 2"],
  "benchmark_note": "Nota comparativa settoriale se rilevante, altrimenti null",
  "privacy_note": null
}

Rispondi SOLO con il JSON.
PROMPT;

        $response = $this->callAPI($prompt, $system);
        return $this->parseJsonResponse($response);
    }

    /**
     * Costruisce il contesto aggregato per il prompt cross-org
     */
    private function buildCrossOrgContext(array $d, int $orgCount): string
    {
        $lines = [];

        // Distribuzione settoriale
        if (!empty($d['by_sector'])) {
            $lines[] = '### Distribuzione Settoriale';
            foreach ($d['by_sector'] as $sector => $count) {
                $pct = round($count / $orgCount * 100);
                $lines[] = "- {$sector}: {$count} org ({$pct}%)";
            }
        }

        // Classificazione NIS2
        if (!empty($d['by_entity_type'])) {
            $lines[] = "\n### Classificazione NIS2";
            foreach ($d['by_entity_type'] as $type => $count) {
                $lines[] = "- {$type}: {$count} org";
            }
        }

        // Compliance score
        if (isset($d['avg_compliance_score'])) {
            $lines[] = "\n### Compliance Score Medio: {$d['avg_compliance_score']}%";
            if (isset($d['score_distribution'])) {
                foreach ($d['score_distribution'] as $range => $count) {
                    $lines[] = "- {$range}: {$count} org";
                }
            }
        }

        // Assessment
        if (isset($d['assessments'])) {
            $a = $d['assessments'];
            $lines[] = "\n### Gap Assessment";
            $lines[] = "- Org con assessment completato: {$a['with_completed']} / {$orgCount}";
            if (!empty($a['avg_by_category'])) {
                $lines[] = "- Score medio per categoria:";
                foreach ($a['avg_by_category'] as $cat => $score) {
                    $lines[] = "  - {$cat}: {$score}%";
                }
            }
        }

        // Rischi
        if (isset($d['risks'])) {
            $r = $d['risks'];
            $lines[] = "\n### Rischi (aggregato)";
            $lines[] = "- Totale rischi aperti: {$r['total_open']}";
            $lines[] = "- Media rischi per org: {$r['avg_per_org']}";
            if (!empty($r['by_severity'])) {
                foreach ($r['by_severity'] as $sev => $count) {
                    $lines[] = "- Gravità {$sev}: {$count} rischi";
                }
            }
            $lines[] = "- % con piano trattamento: {$r['pct_with_treatment']}%";
        }

        // Policy
        if (isset($d['policies'])) {
            $p = $d['policies'];
            $lines[] = "\n### Policy";
            $lines[] = "- Media policy approvate per org: {$p['avg_approved']}";
            $lines[] = "- Org senza policy approvate: {$p['without_approved']}";
        }

        // Formazione
        if (isset($d['training'])) {
            $t = $d['training'];
            $lines[] = "\n### Formazione";
            $lines[] = "- Tasso completamento medio: {$t['avg_completion_rate']}%";
            $lines[] = "- Org con completamento < 50%: {$t['below_50pct']}";
        }

        // Controlli
        if (isset($d['controls'])) {
            $c = $d['controls'];
            $lines[] = "\n### Controlli ISO 27001 / NIS2";
            $lines[] = "- % media implementazione: {$c['avg_implementation']}%";
            if (!empty($c['weakest_categories'])) {
                $lines[] = "- Categorie più deboli: " . implode(', ', $c['weakest_categories']);
            }
        }

        // Incidenti
        if (isset($d['incidents'])) {
            $i = $d['incidents'];
            $lines[] = "\n### Incidenti (ultimi 12 mesi)";
            $lines[] = "- Totale incidenti: {$i['total']}";
            $lines[] = "- Org con almeno 1 incidente: {$i['orgs_with_incidents']}";
            if (!empty($i['by_severity'])) {
                foreach ($i['by_severity'] as $sev => $count) {
                    $lines[] = "- Gravità {$sev}: {$count}";
                }
            }
        }

        return implode("\n", $lines);
    }

    /**
     * Registra interazione AI nel database
     */
    public function logInteraction(int $orgId, int $userId, string $type, string $promptSummary, string $responseSummary, int $tokensUsed = 0): void
    {
        Database::insert('ai_interactions', [
            'organization_id'  => $orgId,
            'user_id'          => $userId,
            'interaction_type' => $type,
            'prompt_summary'   => substr($promptSummary, 0, 500),
            'response_summary' => $responseSummary,
            'tokens_used'      => $tokensUsed,
            'model_used'       => $this->model,
        ]);
    }

    /**
     * Migration 012-014: Q&A grounded sulla KB multi-livello.
     *
     * Esegue una RAG search sui documenti visibili all'utente (SYSTEM/FIRM/ORG)
     * e inietta i top-K chunks nel system prompt prima di chiamare Claude.
     *
     * Se Voyage/Qdrant non sono disponibili, ricade su Claude diretto senza grounding.
     *
     * @param string $question     Domanda dell'utente
     * @param array  $userContext  ['user_id', 'organization_id', 'consulting_firm_id']
     * @return array ['answer'=>string, 'sources'=>array, 'rag_used'=>bool]
     */
    public function askWithRag(string $question, array $userContext): array
    {
        $sources = [];
        $contextBlock = '';
        $ragUsed = false;

        // Tenta RAG: se fallisce, prosegui senza grounding (degradazione graceful)
        try {
            require_once __DIR__ . '/RagService.php';
            $rag = new RagService();
            $hits = $rag->searchForUser($question, $userContext, 5, 0.28);
            if (!empty($hits)) {
                $contextBlock = $rag->formatContext($hits);
                $sources = array_map(fn($h) => [
                    'title' => $h['title'],
                    'scope' => $h['scope'],
                    'score' => $h['score'],
                ], $hits);
                $ragUsed = true;
            }
        } catch (Exception $e) {
            error_log('[AIService::askWithRag] RAG failed, fallback diretto: ' . $e->getMessage());
        }

        $systemPrompt = "Sei un esperto consulente di cybersecurity NIS2 (EU 2022/2555) e D.Lgs. 138/2024.\n"
            . "Rispondi in modo preciso e cita le fonti del contesto quando rilevanti.\n"
            . $this->authoritativeSourcesBlock();
        if (!empty($contextBlock)) {
            $systemPrompt .= "\n## Contesto documentale (knowledge base)\n" . $contextBlock
                . "\n\nQuando rispondi, cita esplicitamente i numeri tra parentesi quadre [1], [2], ... che corrispondono ai documenti del contesto.";
        } else {
            $systemPrompt .= "\nNon e' disponibile contesto documentale specifico per questa domanda. Rispondi con la tua conoscenza generale e indica esplicitamente che non hai trovato fonti nella knowledge base.";
        }

        $answer = $this->callAPI($question, $systemPrompt);

        return [
            'answer'   => $answer,
            'sources'  => $sources,
            'rag_used' => $ragUsed,
        ];
    }
}
