<?php

namespace Drupal\api_monitor_ai;

use Drupal\ai\AiProviderPluginManager;
use Drupal\ai\Dto\StructuredOutputSchema;
use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\api_monitor_client\ApiSnifferService;
use Drupal\Core\Config\ConfigFactoryInterface;

class AiAssistantService {

  /**
   * The AI provider plugin manager.
   *
   * @var \Drupal\ai\AiProviderPluginManager
   */
  protected $aiProviderManager;

  /**
   * The API sniffer service.
   *
   * @var \Drupal\api_monitor_client\ApiSnifferService
   */
  protected $apiRunner;

  /**
   * The configuration object.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected $config;

  /**
   * Constructs a new AiAssistantService object.
   */
  public function __construct(AiProviderPluginManager $ai_provider_manager, ApiSnifferService $api_runner, ConfigFactoryInterface $config_factory) {
    $this->aiProviderManager = $ai_provider_manager;
    $this->apiRunner = $api_runner;
    $this->config = $config_factory->get('api_monitor_ai.settings');
  }

  /**
   * Esegue una domanda all'IA utilizzando il provider di default.
   */
  public function askAI(string $question, $systemPrompt = NULL, ?string $api_id = NULL, ?string $provider_id = NULL) {
    try {
      $ai_config = $this->aiProviderManager->getSetProvider('chat');
      /** @var \Drupal\ai\AiProviderInterface $provider */
      $provider = $ai_config['provider_id'];
      $model_id = $ai_config['model_id'];
    }
    catch (\Exception $e) {
      return ['error' => 'Nessun provider AI configurato o attivo per la chat.'];
    }

    if (!$provider) {
      return ['error' => 'Nessun provider AI configurato per la chat.'];
    }

    if (is_null($systemPrompt)) {
      $systemPrompt = $this->setSystemPrompt($api_id, $provider_id);
    }

    $input = new ChatInput([
      ['role' => 'system', 'content' => $systemPrompt],
      ['role' => 'user', 'content' => $question],
    ]);

    try {
      /** @var \Drupal\ai\OperationType\Chat\ChatOutput $response */
      $response = $provider->chat($input, $model_id);
      $message = $response->getNormalized();

      return [
        'choices' => [
          [
            'message' => [
              'content' => $message->getText(),
            ],
          ],
        ],
      ];
    }
    catch (\Exception $e) {
      return ['error' => "Errore durante la chiamata all'IA: " . $e->getMessage()];
    }
  }

  /**
   * Recupera i modelli disponibili dal provider di default.
   */
  public function getAvailableModels(): array {
    try {
      $ai_config = $this->aiProviderManager->getSetProvider('chat');
      /** @var \Drupal\ai\AiProviderInterface $provider */
      $provider = $ai_config['provider_id'];
    }
    catch (\Exception $e) {
      return [];
    }

    if (!$provider) {
      return [];
    }

    $models = [];
    foreach ($provider->getModels('chat') as $model_id => $model_definition) {
      $models[$model_id] = $model_id;
    }
    asort($models);
    return $models;
  }

  public function setSystemPrompt(?string $api_id = NULL, ?string $provider_id = NULL) : string {
    $discovery = $this->getCleanDiscovery($api_id, $provider_id);
    $systemPrompt = "Sei un esperto tecnico di API Drupal.
Hai a disposizione i seguenti endpoint dal Discovery (limitati al contesto corrente se applicabile): " . json_encode($discovery) . "

REGOLE:
1. Se l'utente vuole testare, rispondi SOLO con il JSON: {\"action\": \"test_api\", \"api_id\": \"ID\", \"provider_id\": \"PROVIDER_ID\"}.
2. Se l'utente fa una domanda generale, rispondi normalmente in linguaggio naturale.
3. Non inventare endpoint che non sono nel Discovery.";
    return $systemPrompt;
  }

  /**
   * Pulisce il discovery per ottimizzare l'invio all'IA.
   */
  public function getCleanDiscovery(?string $api_id = NULL, ?string $provider_id = NULL): array {
    $raw_discovery = $this->apiRunner->getAllDiscoveries();
    $clean_discovery = [];

    foreach ($raw_discovery as $api) {
      if ($api_id && ($api['id'] ?? '') !== $api_id) {
        continue;
      }

      if ($provider_id && ($api['provider_id'] ?? '') !== $provider_id) {
          continue;
      }

      // Estraiamo solo i metadati utili al ragionamento dell'LLM
      $clean_discovery[] = [
        'id'     => $api['id'] ?? '',
        'label'  => $api['label'] ?? '',
        'path'   => $api['path'] ?? '',
        'method' => $api['methods'] ?? ['GET'],
        'auth_required' => $api['auth_required'] ?? FALSE,
        'provider_id' => $api['provider_id'] ?? '',
      ];
    }

    return $clean_discovery;
  }

  /**
   * Esegue un'analisi euristica sulla risposta di un'API.
   */
  public function analyzeResponseHealth(string $api_id, string $responseBody, int $statusCode, bool $isPiiAuthorized = FALSE): array {
    try {
      $ai_config = $this->aiProviderManager->getSetProvider('chat');
      /** @var \Drupal\ai\AiProviderInterface $provider */
      $provider = $ai_config['provider_id'];
      $model_id = $ai_config['model_id'];
    }
    catch (\Exception $e) {
      return ['error' => 'Nessun provider AI configurato o attivo per la chat.'];
    }

    if (!$provider) {
      return ['error' => 'Nessun provider AI configurato per la chat.'];
    }

    // Limitiamo il body della risposta per evitare di superare i limiti di token
    $truncatedBody = mb_strimwidth($responseBody, 0, 3000, '...');

    $piiContext = $isPiiAuthorized
      ? "NOTA: Questo endpoint è AUTORIZZATO a restituire dati personali (PII) perché fa parte della sua funzione di business (es. anagrafica, profili). Non dare un verdetto CRITICAL per la semplice presenza di email o nomi, ma segnala comunque se trovi dati eccessivamente critici come PASSWORD in chiaro o segreti di sistema."
      : "NOTA: Questo endpoint NON dovrebbe contenere dati personali (PII). La presenza di email, nomi o altri dati sensibili deve essere segnalata con alta priorità.";

    $systemPrompt = "Sei un analista esperto in sicurezza informatica e integrità dei dati API.
Il tuo compito è analizzare la risposta di un endpoint API e identificare potenziali problemi.

$piiContext

REGOLE DI ANALISI:
1. PII DETECTION: Identifica se sono presenti dati sensibili non protetti.
2. MASKED ERRORS: Verifica se l'endpoint restituisce uno status code 200 OK ma il body contains messaggi di errore.
3. LOGICAL CONSISTENCY: Valuta se il contenuto della risposta è coerente con quello che ci si aspetta da un'API.

RISPONDI ESCLUSIVAMENTE IN FORMATO JSON CON QUESTA STRUTTURA:
{
  \"verdict\": \"SAFE\" | \"WARNING\" | \"CRITICAL\",
  \"issues\": [\"Lista delle problematiche riscontrate\"],
  \"pii_found\": true | false,
  \"is_masked_error\": true | false,
  \"analysis_summary\": \"Breve riassunto dell'analisi\"
}";

    $userMessage = "Analizza la seguente risposta per l'endpoint API: $api_id\nStatus Code: $statusCode\nResponse Body:\n$truncatedBody";

    $input = new ChatInput([
      new ChatMessage('user', $userMessage)
    ]);
    // Setting system prompt.
    $input->setSystemPrompt($systemPrompt);
    // Impostiamo il formato strutturato se supportato dal provider.
    $schema_definition = [
      'type' => 'object',
      'json_schema' => [
        'properties' => [
          'verdict' => ['type' => 'string', 'enum' => ['SAFE', 'WARNING', 'CRITICAL']],
          'issues' => ['type' => 'array', 'items' => ['type' => 'string']],
          'pii_found' => ['type' => 'boolean'],
          'is_masked_error' => ['type' => 'boolean'],
          'analysis_summary' => ['type' => 'string'],
        ],
      ],
      'required' => ['verdict', 'issues', 'pii_found', 'is_masked_error', 'analysis_summary'],
    ];

    $schema = StructuredOutputSchema::fromArray($schema_definition);
    $input->setChatStructuredJsonSchema($schema);

    try {
      $response = $provider->chat($input, $model_id);
      $message = $response->getNormalized();
      $content = $message->getText();

      // Pulizia del contenuto se non è JSON puro (alcuni modelli aggiungono markdown)
      if (str_starts_with($content, '```json')) {
        $content = trim(str_replace(['```json', '```'], '', $content));
      }

      $data = json_decode($content, TRUE);
      if (json_last_error() === JSON_ERROR_NONE) {
        return $data;
      }
      return ['error' => 'Risposta non valida dall\'IA: non è un JSON valido.'];
    }
    catch (\Exception $e) {
      return ['error' => "Errore durante l'analisi euristica: " . $e->getMessage()];
    }
  }

}
