<?php
/**
 * Chatbot request preparation.
 *
 * @package KimaAI\ChatBot
 */

declare(strict_types=1);

namespace KimaAI\ChatBot;

use KimaAI\Admin\Settings;
use KimaAI\Ai\AiModels;
use KimaAI\Ai\Security;
use KimaAI\ChatBot\Tools\ChatbotToolRegistry;
use KimaAI\Helper;
use KimaAI\Logger;
use KimaAI\Models\ChatModel;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

defined('ABSPATH') || exit;

class ChatbotRequestPreparer {

  /**
   * Create the request preparer.
   *
   * @param Settings $settings Plugin settings.
   * @param ChatModel $chat_model Chat data model.
   * @param Security $security Security helper.
   * @param ChatbotToolRegistry $tool_registry Chatbot tool registry.
   */
  public function __construct(
    private Settings $settings,
    private ChatModel $chat_model,
    private Security $security,
    private ChatbotToolRegistry $tool_registry
  ) {
  }

  /**
   * Prepare a chatbot request or return an early error response.
   *
   * @return array{request?: ChatbotPreparedRequest, response?: WP_REST_Response}
   */
  public function prepare(WP_REST_Request $request): array {
    $data = $request->get_json_params();
    $data = is_array($data) ? $data : [];

    $command = $request->get_param('command');
    $command = is_array($command) ? $command : null;

    $payload = [
      'prompt' => $request->get_param('message'),
      'postId' => $request->get_param('postId'),
      'lang'   => $request->get_param('lang'),
    ];

    $chatbot_settings = $this->settings->getOption(null, 'chatbot', 'settings');

    if (!is_array($chatbot_settings) || empty($chatbot_settings)) {
      $error_message = __('Chatbot settings are not configured properly.', 'kimaai');
      Logger::error($error_message);
      return ['response' => new WP_REST_Response(['error' => $error_message], 500)];
    }
    $payload['chatbot_settings'] = $chatbot_settings;

    $payload['prompt'] = apply_filters('kimaai_chatbot_prompt', $payload['prompt'], $data, $request);

    $allowed = apply_filters('kimaai_chatbot_gate', true, $payload['prompt'], $payload, $data, $request);
    $allowed_deprecated = apply_filters_deprecated(
      'kimaai_chatbot_allowed',
      [true, $payload['prompt'], $data],
      '1.3.0',
      'kimaai_chatbot_gate'
    );

    if ($allowed === true && $allowed_deprecated !== true) {
      $allowed = $allowed_deprecated;
    }

    if ($allowed !== true) {
      if ($allowed instanceof WP_Error) {
        $error_message = $allowed->get_error_message();
      } elseif (is_string($allowed)) {
        $error_message = $allowed;
      } else {
        $error_message = __('Chatbot is not allowed to process this request.', 'kimaai');
      }

      do_action('kimaai_chatbot_blocked', $error_message, $payload, $data, $request);

      Logger::warn($error_message);
      return ['response' => new WP_REST_Response(['error' => $error_message], 403)];
    }

    $client_session_id = (string) $request->get_param('sessionId');
    $payload['sessionId'] = $client_session_id !== '' ? $client_session_id : Helper::ensureSessionId();

    $rate_limit_response = $this->checkRateLimit($payload, $chatbot_settings);
    if ($rate_limit_response instanceof WP_REST_Response) {
      return ['response' => $rate_limit_response];
    }

    $input_length_response = $this->checkInputLength($payload, $chatbot_settings);
    if ($input_length_response instanceof WP_REST_Response) {
      return ['response' => $input_length_response];
    }

    $tool_context = $this->tool_registry->resolve($payload, $chatbot_settings);
    $payload = $tool_context['payload'];
    $tools = $tool_context['tools'];

    $ai_payload = $this->buildAiPayload($payload, $chatbot_settings, $tools);

    $ai_payload = apply_filters('kimaai_chatbot_ai_payload', $ai_payload, $payload, $chatbot_settings);

    do_action('kimaai_chatbot_before_request', $ai_payload, $payload, $chatbot_settings);

    return [
      'request' => new ChatbotPreparedRequest(
        $payload,
        $ai_payload,
        $chatbot_settings,
        $tools,
        $command,
        $data
      ),
    ];
  }

  /**
   * Enforce the per-IP daily message rate limit.
   *
   * @param array $payload Chatbot request payload.
   * @param array $chatbot_settings Chatbot settings.
   * @return WP_REST_Response|null Error response on limit exceeded, null otherwise.
   */
  private function checkRateLimit(array $payload, array $chatbot_settings): ?WP_REST_Response {
    $user_ip = $this->security->getIpAddress();
    $max_messages = (int) $chatbot_settings['maxMessages'];
    $today_message_count = $this->chat_model->countChatbotMessagesByIpToday($user_ip);

    if ($today_message_count < $max_messages) {
      return null;
    }

    $max_messages_error = sprintf(
    /* translators: 1: maximum number of messages allowed in a session */
      __('Maximum number of messages (%1$d) reached for this session. Please come back tomorrow!', 'kimaai'),
      $max_messages
    );

    do_action('kimaai_chatbot_rate_limited', $user_ip, $today_message_count, $max_messages, $payload, $chatbot_settings);

    Logger::warn($max_messages_error);
    return new WP_REST_Response(['error' => $max_messages_error], 400);
  }

  /**
   * Enforce the maximum allowed input length.
   *
   * @param array $payload Chatbot request payload.
   * @param array $chatbot_settings Chatbot settings.
   * @return WP_REST_Response|null Error response on limit exceeded, null otherwise.
   */
  private function checkInputLength(array $payload, array $chatbot_settings): ?WP_REST_Response {
    $max_input_length = (int) $chatbot_settings['maxInputLength'];
    $current_input_length = Helper::safeStrlen($payload['prompt']);

    if ($current_input_length <= $max_input_length) {
      return null;
    }

    $max_input_length_error = sprintf(
    /* translators: 1: maximum allowed characters, 2: characters provided */
      __('Input exceeds maximum length of %1$d characters (%2$d characters provided). Please shorten your input.', 'kimaai'),
      $max_input_length,
      $current_input_length
    );

    Logger::warn($max_input_length_error);
    return new WP_REST_Response(['error' => $max_input_length_error], 400);
  }

  /**
   * Build the AI request payload from the chatbot payload and settings.
   *
   * @param array $payload Chatbot request payload.
   * @param array $chatbot_settings Chatbot settings.
   * @param array $tools Resolved tool definitions.
   * @return array
   */
  private function buildAiPayload(array $payload, array $chatbot_settings, array $tools): array {
    $provider = preg_match('/^[a-z]+/', $chatbot_settings['chatAiProvider'], $matches) ? $matches[0] : 'openai';
    $model_def = AiModels::getModel($provider, $chatbot_settings['chatAiModel']);
    $params = (is_array($model_def) && isset($model_def['params']) && is_array($model_def['params'])) ? $model_def['params'] : [];

    $supports_reasoning = array_key_exists('reasoning', $params) && is_array($params['reasoning']) && !empty($params['reasoning']);
    $supports_verbosity = array_key_exists('verbosity', $params) && is_array($params['verbosity']) && !empty($params['verbosity']);
    $use_reasoning_family_params = ($supports_reasoning || $supports_verbosity);

    $ai_payload = [
      'prompt'          => $payload['prompt'],
      'postId'          => $payload['postId'],
      'sessionId'       => $payload['sessionId'],
      'aiProvider'      => $chatbot_settings['chatAiProvider'],
      'aiModel'         => $chatbot_settings['chatAiModel'],
      'maxOutputTokens' => $chatbot_settings['maxOutputTokens'],
    ];

    if ($use_reasoning_family_params) {
      if ($supports_reasoning && isset($chatbot_settings['reasoning'])) {
        $ai_payload['reasoning']['effort'] = (string) $chatbot_settings['reasoning'];
      }
      if ($supports_verbosity && isset($chatbot_settings['verbosity'])) {
        $ai_payload['text']['verbosity'] = (string) $chatbot_settings['verbosity'];
      }
    } elseif (isset($chatbot_settings['temperature'])) {
      $ai_payload['temperature'] = (float) $chatbot_settings['temperature'];
    }

    if (!empty($tools)) {
      $ai_payload['tools'] = $tools;
      $ai_payload['toolChoice'] = 'auto';
      $ai_payload['parallelToolCalls'] = true;
      $ai_payload['toolConfig'] = $payload['toolConfig'] ?? [];
    }

    return $ai_payload;
  }
}
