<?php

declare(strict_types=1);

namespace Drupal\api_monitor_ai\Plugin\AiAgent;

use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\ai\Service\FunctionCalling\FunctionCallPluginManager;
use Drupal\api_monitor_ai\AiAssistantService;
use Drupal\api_monitor_client\ApiSnifferService;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ai_agents\Attribute\AiAgent;
use Drupal\ai_agents\PluginBase\AiAgentBase;
use Drupal\ai_agents\PluginInterfaces\AiAgentInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Agente per il monitoraggio delle API.
 */
#[AiAgent(
  id: 'api_monitor_agent',
  label: new TranslatableMarkup('API Monitor Agent'),
  module_dependencies: ['api_monitor_ai'],
)]
class ApiMonitorAgent extends AiAgentBase implements ContainerFactoryPluginInterface {

  protected ApiSnifferService $apiRunner;

  protected FunctionCallPluginManager $functionCallManager;

  protected LoggerInterface $logger;
  protected AiAssistantService $aiAssistantService;

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $parent_instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $parent_instance->apiRunner = $container->get('api_monitor_client.sniffer');
    $parent_instance->functionCallManager = $container->get('plugin.manager.ai.function_calls');
    $parent_instance->logger = $container->get('logger.channel.api_monitor_ai');
    $parent_instance->aiAssistantService = $container->get('api_monitor_ai.assistance_service');
    return $parent_instance;
  }

  /**
   * {@inheritdoc}
   */
  public function agentsNames() {
    return ['API Monitor Agent'];
  }

  /**
   * {@inheritdoc}
   */
  public function agentsCapabilities() {
    return [
      'api_monitor_agent' => [
        'name' => 'API Monitor Agent',
        'description' => 'Questo agente può analizzare lo stato delle API e eseguire test su di esse.',
        'usage_instructions' => 'Usa questo agente quando l\'utente chiede informazioni sulle API o vuole testare un endpoint.',
        'inputs' => [
          'question' => [
            'name' => 'Domanda',
            'type' => 'string',
            'description' => 'La domanda dell\'utente o l\'azione richiesta.',
          ],
        ],
        'outputs' => [
          'answer' => [
            'description' => 'La risposta dell\'agente o il risultato del test.',
            'type' => 'string',
          ],
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function answerQuestion() {
    $data = $this->agentHelper->runSubAgent('api_monitor_task', [
      'Summary' => $this->getDescription(),
      'Help Text' => $this->getHelp(),
      'Inputs' => $this->getInputsAsString(),
      'Outputs' => $this->getOutputsAsString(),
      'Discovery' => json_encode($this->apiRunner->getAllDiscoveries()),
    ]);

    // L'output del decoder JSON restituisce un array di risultati (formati).
    $result = $data[0] ?? $data;

    if (isset($result['answer'])) {
      return $result['answer'];
    }

    return $this->t("Spiacevolmente non ho una risposta per te.");
  }

  /**
   * {@inheritdoc}
   */
  public function getHelp() {
    return $this->t("Questo agente ti aiuta a monitorare e testare le tue API Drupal.");
  }

  /**
   * {@inheritdoc}
   */
  public function solve() {
    $question = $this->task->getDescription();
    $api_id_context = $this->getContextApiId();
    $provider_id_context = $this->getContextProviderId();

    // Prepariamo il contesto per l'agente.
    $context = [
      'user_question' => $question,
      'api_context' => [
        'api_id' => $api_id_context,
        'provider_id' => $provider_id_context,
      ],
      'discovery' => $this->aiAssistantService->getCleanDiscovery($api_id_context, $provider_id_context),
    ];

    try {
      // 1. IDENTIFICAZIONE INTENTO (Sub-Agente)
      // Utilizziamo lo YAML per capire cosa vuole fare l'utente.
      $response = $this->agentHelper->runSubAgent(
        'api_monitor_task',
        $context,
        "Analizza la richiesta dell'utente e rispondi ESCLUSIVAMENTE con uno dei formati JSON previsti."
      );

      // Estrazione robusta del JSON dell'intento.
      $intent = [];
      if ($response instanceof ChatMessage) {
        $intent = $this->extractJson($response->getText());
      }
      elseif (is_array($response)) {
        $intent = (isset($response[0]) && is_array($response[0])) ? $response[0] : $response;
      }

      // Se non abbiamo un intento valido, fallback a risposta informativa.
      if (empty($intent['action'])) {
        return is_string($response) ? $response : "Non sono riuscito a interpretare la richiesta. Prova a essere più specifico.";
      }

      // 2. ORCHESTRAZIONE DELLE AZIONI
      switch ($intent['action']) {
        case 'test_api':
          $api_id = $api_id_context;
          $provider_id = $provider_id_context;

          if ($this->functionCallManager->hasDefinition('api_monitor_ai:api_tester_tool')) {
            $tool = $this->functionCallManager->createInstance('api_monitor_ai:api_tester_tool');
            $tool->setContextValue('api_id', $api_id);
            $tool->setContextValue('provider_id', $provider_id);
            $tool->execute();
            $testResult = $tool->getReadableOutput();

            // Chiediamo all'IA di commentare il risultato tecnico per l'utente.
            $commentInstructions = "Sei un esperto tecnico. Spiega all'utente il risultato di questo test API in modo semplice: {$testResult}";
            $comment = $this->agentHelper->runSubAgent('api_monitor_task', ['test_result' => $testResult], $commentInstructions);

            return $this->getPlainTextResponse($comment) ?: "Test eseguito:\n" . $testResult;
          }
          break;

        case 'generate_client':
          $api_id = $api_id_context;
          $provider_id = $provider_id_context;
          $lang = $intent['language'] ?? 'React';

          if ($this->functionCallManager->hasDefinition('api_monitor_ai:generate_api_client')) {
            $tool = $this->functionCallManager->createInstance('api_monitor_ai:generate_api_client');
            $tool->setContextValue('api_id', $api_id);
            $tool->setContextValue('provider_id', $provider_id);
            $tool->setContextValue('language', $lang);
            $tool->execute();
            $apiDetails = $tool->getReadableOutput();

            // Chiediamo all'IA di generare il codice basandosi sui dettagli tecnici.
            // NOTA: Qui non usiamo JSON, così evitiamo ogni problema di caratteri speciali.
            $genInstructions = "Sei un Senior Software Engineer. Genera uno snippet di codice in '{$lang}' basandoti su queste specifiche tecniche: {$apiDetails}.
            Constraint: Rispondi SOLO con il codice racchiuso in blocchi markdown ```. Non aggiungere chiacchiere.";

            $codeResponse = $this->agentHelper->runSubAgent('api_monitor_task', ['api_spec' => $apiDetails], $genInstructions);
            return $this->getPlainTextResponse($codeResponse) ?: "Ecco i dettagli tecnici:\n" . $apiDetails;
          }
          break;

        case 'information':
          return $intent['answer'] ?? "Non ho informazioni specifiche al riguardo.";
      }

      return "L'azione richiesta non è stata riconosciuta.";
    }
    catch (\Exception $e) {
      $this->logger->error("Errore nell'agente Orchestratore: @message", ['@message' => $e->getMessage()]);
      return "Spiacente, si è verificato un errore tecnico nell'elaborazione: " . $e->getMessage();
    }
  }

  /**
   * Estrae il testo pulito da una risposta che potrebbe essere un ChatMessage o un array.
   */
  protected function getPlainTextResponse($response): string {
    if ($response instanceof ChatMessage) {
      return $response->getText();
    }
    if (is_array($response)) {
      return $response['answer'] ?? $response['snippet'] ?? (is_string(reset($response)) ? reset($response) : "");
    }
    return is_string($response) ? $response : "";
  }

  /**
   * Estrae e decodifica il JSON da una stringa in modo robusto.
   */
  protected function extractJson(string $text): array {
    $text = trim($text);
    if (empty($text)) {
      return [];
    }

    $text = preg_replace_callback('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"/s', function($matches) {
      return '"' . str_replace(["\r", "\n"], ['\r', '\n'], $matches[1]) . '"';
    }, $text);

    $first_brace = strpos($text, '{');
    $first_bracket = strpos($text, '[');
    $start = FALSE;
    $end_char = '';

    if ($first_brace !== FALSE && ($first_bracket === FALSE || $first_brace < $first_bracket)) {
      $start = $first_brace;
      $end_char = '}';
    }
    elseif ($first_bracket !== FALSE) {
      $start = $first_bracket;
      $end_char = ']';
    }

    if ($start !== FALSE) {
      $end = strrpos($text, $end_char);
      if ($end !== FALSE && $end > $start) {
        $text = substr($text, $start, $end - $start + 1);
        $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
      }
    }

    $decoded = json_decode($text, TRUE, 512, JSON_INVALID_UTF8_SUBSTITUTE);
    if (json_last_error() !== JSON_ERROR_NONE) {
      return [];
    }
    if (is_array($decoded) && isset($decoded[0]) && count($decoded) === 1 && is_array($decoded[0])) {
      return $decoded[0];
    }
    return is_array($decoded) ? $decoded : [];
  }

  /**
   * {@inheritdoc}
   */
  public function determineSolvability() {
    parent::determineSolvability();
    return AiAgentInterface::JOB_SOLVABLE;
  }

  /**
   * Recupera l'api_id dal contesto dei dati passati all'agente.
   *
   * @return string|null
   *   L'ID dell'API se presente nel contesto, altrimenti NULL.
   */
  protected function getContextApiId() {
    $data = $this->getData();
    if (is_array($data)) {
      foreach ($data as $item) {
        if (is_array($item) && isset($item['api_id'])) {
          return $item['api_id'];
        }
      }
    }
    return NULL;
  }

  /**
   * Recupera il provider_id dal contesto dei dati passati all'agente.
   *
   * @return string|null
   *   L'ID del provider se presente nel contesto, altrimenti NULL.
   */
  protected function getContextProviderId() {
    $data = $this->getData();
    if (is_array($data)) {
      foreach ($data as $item) {
        if (is_array($item) && isset($item['provider_id'])) {
          return $item['provider_id'];
        }
      }
    }
    return NULL;
  }

}
