<?php
/**
 * ChatBot
 *
 * @class ChatBot
 * @package KimaAI\ChatBot
 * @since 1.0.0
 */

declare(strict_types=1);

namespace KimaAI\ChatBot;

use KimaAI\Admin\Settings;
use KimaAI\Helper;
use KimaAI\KimaAI;
use KimaAI\Models\ChatCsatModel;
use KimaAI\Models\ChatFeedbackModel;
use KimaAI\Models\ChatModel;
use WP_REST_Request;
use WP_REST_Response;

defined('ABSPATH') || exit;

class ChatBot {

  /**
   * Create the chatbot handler.
   *
   * @param Settings $settings Plugin settings.
   * @param ChatModel $chat_model Chat data model.
   * @param ChatCsatModel $csat_model CSAT feedback model.
   * @param ChatFeedbackModel $feedback_model Message feedback model.
   * @param ChatbotRequestPreparer $request_preparer Request preparer.
   * @param ChatbotNormalRequestRunner $normal_runner Normal request runner.
   * @param ChatbotStreamingRequestRunner $streaming_runner Streaming request runner.
   */
  public function __construct(
    private Settings $settings,
    private ChatModel $chat_model,
    private ChatCsatModel $csat_model,
    private ChatFeedbackModel $feedback_model,
    private ChatbotRequestPreparer $request_preparer,
    private ChatbotNormalRequestRunner $normal_runner,
    private ChatbotStreamingRequestRunner $streaming_runner
  ) {
  }

  /**
   * Register chatbot REST API routes.
   *
   * @return void
   */
  public function registerRoutes(): void {
    register_rest_route(
      KimaAI::PREFIX . '/' . KimaAI::API_VERSION,
      '/chatbot',
      [
        'methods'             => 'POST',
        'callback'            => [$this, 'chatbot'],
        'permission_callback' => '__return_true',
        'args'                => [
          'message' => [
            'required'          => true,
            'type'              => 'string',
            'sanitize_callback' => [$this, 'sanitizeChatMessage'],
            'validate_callback' => [$this, 'validateChatMessage'],
          ],
          'postId'  => [
            'required' => false,
            'type'     => 'integer',
            'default'  => 0,
          ],
          'command' => [
            'required' => false,
            'type'     => 'object',
            'default'  => null,
          ],
          'stream'     => [
            'required' => false,
            'type'     => 'boolean',
            'default'  => false,
          ],
          'lang'       => [
            'required' => false,
            'type'     => 'string',
            'default'  => '',
          ],
          'sessionId'  => [
            'required'          => false,
            'type'              => 'string',
            'default'           => '',
            'sanitize_callback' => 'sanitize_text_field',
          ],
        ],
      ]
    );
    register_rest_route(
      KimaAI::PREFIX . '/' . KimaAI::API_VERSION,
      '/chatbot/history',
      [
        'methods'             => 'GET',
        'callback'            => [$this, 'getChatHistory'],
        'permission_callback' => '__return_true',
        'args'                => [
          'session_id' => [
            'required' => false,
            'type'     => 'string',
            'default'  => null,
          ],
        ],
      ]
    );
    register_rest_route(
      KimaAI::PREFIX . '/' . KimaAI::API_VERSION,
      '/chatbot/history/clear',
      [
        'methods'             => 'POST',
        'callback'            => [$this, 'clearChatHistory'],
        'permission_callback' => '__return_true',
      ]
    );
  }

  /**
   * Sanitize a chat message: strip HTML, slashes, and truncate to 5000 characters.
   *
   * @param mixed $value Raw input value.
   * @return string
   */
  public function sanitizeChatMessage(mixed $value): string {
    $value = is_scalar($value) ? (string) $value : '';
    $value = wp_unslash($value);

    // Treat chatbot input as plain text, not HTML
    $value = sanitize_textarea_field($value);

    // Prevent abuse / oversized payloads
    return mb_substr($value, 0, 5000);
  }

  /**
   * Validate that a chat message is a non-empty string within the 5000-character limit.
   *
   * @param mixed $value Raw input value.
   * @return bool
   */
  public function validateChatMessage(mixed $value): bool {
    if (!is_scalar($value)) {
      return false;
    }

    $value = trim((string) $value);

    return $value !== '' && mb_strlen($value) <= 5000;
  }

  /**
   * Handles incoming chat messages.
   */
  public function chatbot(WP_REST_Request $request): WP_REST_Response {
    $prepared = $this->request_preparer->prepare($request);

    if (($prepared['response'] ?? null) instanceof WP_REST_Response) {
      return $prepared['response'];
    }

    if ($this->streaming_runner->isEligible($prepared['request'], $request)) {
      $command_response = $this->normal_runner->handleCommand($prepared['request'], $request);

      if ($command_response instanceof WP_REST_Response) {
        return $command_response;
      }

      return $this->streaming_runner->handle($prepared['request'], $request);
    }

    return $this->normal_runner->handle($prepared['request'], $request);
  }

  /**
   * Retrieves chat history for the current session.
   *
   * @param WP_REST_Request $request The REST request object.
   * @return WP_REST_Response The REST response object.
   */
  public function getChatHistory(WP_REST_Request $request): WP_REST_Response {
    $session_id = $request->get_param('session_id') ?? Helper::ensureSessionId();
    $history = $this->chat_model->getSessionHistory($session_id);

    $messages = [];
    $assistant_message_ids = [];

    if ($history) {
      foreach ($history as $item) {
        $row_id = (int) ($item['id'] ?? 0);
        if (!$row_id) {
          continue;
        }

        $messages[] = [
          'id'      => 'u-' . $row_id,
          'role'    => 'user',
          'content' => $item['user_message'],
        ];

        $assistant_id = $row_id;

        $messages[] = [
          'id'          => $assistant_id,
          'role'        => 'bot',
          'content'     => $item['ai_response'],
          'renderables' => is_array($item['renderables'] ?? null) ? $item['renderables'] : [],
        ];

        $assistant_message_ids[] = $assistant_id;
      }
    }

    // Feedback status (backend source of truth)
    try {
      $rated_message_ids = $this->feedback_model->getExistingMessageIds($session_id, $assistant_message_ids);
    } catch (\Throwable $exception) {
      // keep response functional even if feedback table is missing
      $rated_message_ids = [];
    }

    try {
      $csat_given = $this->csat_model->existsForSession($session_id);
    } catch (\Throwable $exception) {
      $csat_given = false;
    }

    /**
     * Filter: build quick actions payload for the UI.
     *
     * @param array $quick_actions The quick actions payload.
     * @param array $messages Normalized chat messages (user/bot).
     * @param string $session_id
     * @param int $post_id
     * @param array $chatbot_settings The "chatbot.quickActions" repeatable settings rows array.
     * @return array{start: array, byMessageId: array<string, array>}
     */
    $quick_actions = apply_filters(
      'kimaai_chatbot_quick_actions_payload',
      [
        'start'       => [],
        'byMessageId' => [],
      ],
      $messages,
      $session_id,
      (int) get_the_ID(),
      $this->settings->getOption(null, 'chatbot', 'quickActions') ?? [],
    );

    return new WP_REST_Response(
      [
        'sessionId'    => $session_id,
        'messages'     => $messages,
        'feedback'     => [
          'csatGiven'       => $csat_given,
          'ratedMessageIds' => $rated_message_ids,
        ],
        'quickActions' => $quick_actions,
      ],
      200
    );
  }

  // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.Found -- $request is required by the WP REST callback interface.
  /**
   * Force a new session ID, leaving the existing DB history intact.
   *
   * @param WP_REST_Request $request REST request (unused; required by WP REST callback interface).
   * @return WP_REST_Response
   */
  public function clearChatHistory(WP_REST_Request $request): WP_REST_Response {
    // phpcs:enable Generic.CodeAnalysis.UnusedFunctionParameter.Found
    // Force a brand-new cookie/session id; leave DB history intact.
    $new_session_id = Helper::ensureSessionId(false, true);

    return new WP_REST_Response(
      [
        'ok'        => true,
        'sessionId' => $new_session_id,
      ],
      200
    );
  }
}
