<?php

namespace Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Endpoints;

use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Endpoints\Chat_Completion\Handlers\Chat_Completion_Response_Handler;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Endpoints\Chat_Completion\Stream_Parser;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Endpoints\Chat_Completion\Tool_Calls_Message;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Endpoints\Chat_Completion\Tool_Result_Message;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Services\Message_Service;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Collection;

/**
 * Class Chat_Completion_Endpoint
 *
 * Handles the chat completion API endpoint communication for the Deep Seek AI provider.
 * Provides methods for chat generation, Q&A entries, conversation analysis, and action messages.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Endpoints
 * @since 1.0.0
 */
class Chat_Completion_Endpoint extends Deep_Seek_Endpoint {

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param Chatbot_Utility|object $utility The utility instance (Chatbot or Copilot).
	 */
	public function __construct( $utility ) {
		parent::__construct( $utility );
	}

	/**
	 * Generates the chat completion request and sends it to the Deep Seek API.
	 *
	 * Prepares the request body, manages streaming options, sends the request,
	 * and returns the generated message. This method is consistent with the OpenAI
	 * implementation - it returns a Message or Tool_Calls_Message object.
	 *
	 * @since 1.0.0
	 *
	 * @return Message|Tool_Calls_Message|null The generated message from DeepSeek.
	 * @throws Exception Throws exception if message preparation fails.
	 */
	public function generate() {
		$is_stream   = $this->utility->global_utility->get_stream();
		$model       = $this->utility->global_utility->get_ai_model();
		$http_client = $this->http_client_factory( $is_stream );
		$messages    = $this->prepare_messages();
		$body        = array(
			'model'    => $model->get_name(),
			'messages' => $messages,
		);
		if ( ! in_array( $model->get_name(), $this->get_temperature_unsupported_models() ) ) {
			$body['temperature'] = 0; // Make this dynamic
		}
		if ( $tools = $this->utility->get_tools() ) {
			$body['tools'] = $tools;
		}
		if ( $is_stream ) {
			$body['stream']         = $is_stream;
			$body['stream_options'] = array( 'include_usage' => true );
		}
		if ( $max_completion_tokens = $this->utility->global_utility->get_max_completion_tokens() ) {
			$body['max_tokens'] = $max_completion_tokens;
		}
		$args = [
			'body'    => json_encode( $body ),
			'timeout' => $this->utility->get_timeout() ?? 60,
			'headers' => $this->get_header(),
		];
		if ( $is_stream ) {
			$stream_parser         = new Stream_Parser();
			$args['stream_parser'] = array( $stream_parser, 'parser' );
		}
		$response = $http_client->post( self::API_BASE_URL . '/chat/completions', $args );

		return ( new Chat_Completion_Response_Handler( $response, $http_client, $this, $stream_parser ?? null ) )->get_message();
	}

	/**
	 * Prepare the list of messages for the chat completion request.
	 *
	 * Processes the messages from the chatbot utility and prepares them
	 * in the required format for the API call.
	 *
	 * @since 1.0.0
	 *
	 * @return array Prepared array of messages.
	 * @throws Exception Throws exception if no messages are found.
	 */
	private function prepare_messages() {
		foreach ( $this->utility->global_utility->get_messages() as $message ) {
			$messages[] = $this->prepare_individual_message( $message );
		}
		if ( empty( $messages ) ) {
			throw new Exception( Error_Codes::CHATBOT_NO_MESSAGES, __( 'Messages are empty', 'limb-chatbot' ) );
		}

		return $messages;
	}

	/**
	 * Prepare a single message for the chat completion request.
	 *
	 * Handles different message types and formats them appropriately
	 * for the API request payload.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed        $message The message object to prepare.
	 * @param mixed $model   Optional AI model for compatibility checks.
	 *
	 * @return array The prepared message array.
	 * @throws Exception Throws exception if the message type is not supported.
	 */
	protected function prepare_individual_message( $message, $model = null ) {
		if ( $message instanceof Tool_Calls_Message ) {
			$individual_message = array(
				'role'       => $message->get_role(),
				'tool_calls' => $message->get_tool_calls(),
			);
		} elseif ( $message instanceof Tool_Result_Message ) {
			$individual_message = array(
				'role'         => $message->get_role(),
				'content'      => is_string( $message->get_content() ) ? $message->get_content() : json_encode( $message->get_content() ),
				'tool_call_id' => $message->get_tool_call_id(),
			);
		} elseif ( $message instanceof Message ) {
			// Use Message_Service for proper message preparation.
			$individual_message = ( new Message_Service() )->prepare_message( $message );
		} else {
			throw new Exception( Error_Codes::NOT_SUPPORTED, __( 'Not supported message !', 'limb-chatbot' ) );
		}

		return $individual_message;
	}

	/**
	 * Send a generic request to the DeepSeek API endpoint.
	 *
	 * This method is used by other methods to make API calls with custom parameters.
	 * Supports temperature, response_format, and other generation options.
	 *
	 * @since 1.0.9
	 *
	 * @param Collection|array $messages Messages to send to the API.
	 * @param array            $params   Optional generation parameters (temperature, response_format, etc.).
	 *
	 * @return Chat_Completion_Response_Handler Response handler for processing the result.
	 * @throws Exception If the request fails.
	 */
	public function make_request( $messages, $stream = false, array $params = array() ): Chat_Completion_Response_Handler {
		$http_client = $this->http_client_factory($stream);

		// Get AI model from utility - support both Chatbot and Copilot utilities.
		$model = null;
		if ( property_exists( $this->utility, 'ai_model' ) && ! empty( $this->utility->ai_model ) ) {
			$model = $this->utility->ai_model;
		}
		if ( empty( $model ) || ( ! $model instanceof AI_Model ) ) {
			$model = $this->utility->global_utility->get_ai_model();
		}

		// Prepare messages for API.
		$prepared_messages = $messages->map( function ( $message ) use ( $model ) {
			return $this->prepare_individual_message( $message, $model );
		} )->items;

		// Build request body with model and messages.
		$body = array_merge(
			array(
				'model'    => $model->get_name(),
				'messages' => $prepared_messages,
			),
			$params
		);
		if ( $stream ) {
			$body['stream']         = $stream;
			$body['stream_options'] = array( 'include_usage' => true );
		}

		$args = array(
			'body'    => wp_json_encode( $body ),
			'headers' => $this->get_header(),
			'timeout' => $this->utility->get_timeout() ?? 60,
		);
		if ( $stream ) {
			$stream_parser         = new Stream_Parser();
			$args['stream_parser'] = array( $stream_parser, 'parser' );
		}

		$response = $http_client->post( self::API_BASE_URL . '/chat/completions', $args );

		return new Chat_Completion_Response_Handler( $response, $http_client, $this, $stream_parser ?? null );
	}

	/**
	 * Determine if vector search is applicable for the given messages.
	 *
	 * Sends a collection of messages to the AI model to determine if
	 * RAG context retrieval would be beneficial for the response.
	 *
	 * @param Collection $messages Collection of messages to process.
	 *
	 * @return Message The AI-generated response message.
	 * @throws Exception If the request fails.
	 *@since 1.0.9
	 *
	 */
	public function is_vector_search_applicable( Collection $messages ): Message {
		return $this->make_request(
			$messages,
			false,
			array( 'temperature' => 0.3 )
		)->get_response_message();
	}

	/**
	 * Analyze a single turn of conversation.
	 *
	 * Used for conversation state analysis to determine user intent,
	 * sentiment, and next steps in the conversation flow.
	 *
	 * @since 1.0.9
	 *
	 * @param Collection|array $messages Messages for analysis.
	 *
	 * @return array JSON-decoded response from the AI endpoint.
	 * @throws Exception If the request fails.
	 */
	public function analyze_turn( $messages ): array {
		return $this->make_request(
			$messages,
			false,
			array(
				'temperature'     => 0.1,
				'response_format' => array( 'type' => 'json_object' ),
			)
		)->get_json();
	}

	/**
	 * Generate a question for data collection during action parameter gathering.
	 *
	 * This method generates a conversational question to collect
	 * required parameters for action execution.
	 *
	 * @param Collection $messages Collection of Message objects for the AI.
	 *
	 * @return Message The generated text response.
	 * @throws Exception If the request fails.
	 *@since 1.0.9
	 *
	 */
	public function generate_data_collect_question( Collection $messages, $stream = false ): ?Message {
		$res =  $this->make_request(
			$messages,
			$stream,
			array( 'temperature' => 0.7 )
		)->get_message();

		return $res;
	}

	/**
	 * Generate a success message for action completion.
	 *
	 * Creates a warm, celebratory message for users after they complete an action.
	 * Uses slightly higher temperature (0.7) for more creative, engaging responses.
	 *
	 * @param Collection $messages Messages for success message generation (system + user prompt).
	 *
	 * @return Message The generated success message.
	 * @throws Exception If the request fails.
	 *@since 1.0.9
	 *
	 */
	public function generate_action_success_message( Collection $messages, $stream = false ): Message {
		return $this->make_request(
			$messages,
			$stream,
			array( 'temperature' => 0.7 )
		)->get_message();
	}

	/**
	 * Rerank messages based on relevance.
	 *
	 * @param  mixed  $messages  Messages to rerank.
	 *
	 * @return Message The AI-generated reranked message.
	 * @throws Exception
	 * @since 1.0.9
	 */
	public function rerank( $messages ): Message {
		return $this->make_request( $messages, false, array( 'temperature' => 0 ) )->get_message();
	}

	/**
	 * Generate a JSON message response.
	 *
	 * @param  mixed  $messages  Messages to send.
	 *
	 * @return array JSON-decoded response.
	 * @throws Exception
	 * @since 1.0.9
	 */
	public function generate_json_message( $messages ): array {
		return $this->make_request( $messages, false, array( 'temperature' => 0.6 ) )->get_json();
	}

	/**
	 * Generate a JSON response for reranking.
	 *
	 * @param  mixed  $messages  Messages to send.
	 *
	 * @return array JSON-decoded response.
	 * @throws Exception
	 * @since 1.0.9
	 */
	public function generate_rerank_json_message( $messages ): array {
		return $this->make_request( $messages, false, array( 'temperature' => 0.2 ) )->get_json();
	}

	/**	 Get models that do not support temperature parameter.
	 *
	 * @since 1.0.9
	 *
	 * @return array List of model names that do not support temperature.
	 */
	private function get_temperature_unsupported_models() {
		return array();
	}
}