<?php

namespace Limb_Chatbot\Includes\AI_Providers\Gemini\Utilities;

use Limb_Chatbot\Includes\Chatbot_Tools\Chatbot_Tools;
use Limb_Chatbot\Includes\Data_Objects\Agent_Operation_Results_Message;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content\Tool_Calls_Message;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content\Tool_Result_Message;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content_Endpoint;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Traits\Json_Serializable_Trait;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility as Global_Utility;
use ReflectionClass;

/**
 * Class Chatbot_Utility
 *
 * Utility class to handle chatbot-related functionalities for Gemini AI Provider.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Gemini\Utilities
 * @since 1.0.0
 */
class Chatbot_Utility {
	use Json_Serializable_Trait;

	/**
	 * Global chatbot utility instance.
	 *
	 * @var Global_Utility
	 * @since 1.0.0
	 */
	public Global_Utility $global_utility;

	/**
	 * AI Model ID used for requests.
	 *
	 * @var mixed
	 * @since 1.0.0
	 */
	public $ai_model_id;

	/**
	 * API version string.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $version = 'v1beta';

	/**
	 * Default timeout for requests in seconds.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	public $default_timeout = 100;

	/**
	 * Constructor.
	 *
	 * @param Global_Utility $global_utility The global utility instance.
	 * @since 1.0.0
	 */
	public function __construct( Global_Utility $global_utility ) {
		$this->global_utility = $global_utility;
		$this->populate_properties( ! empty( $this->global_utility->get_chatbot()->get_id() ) );
	}

	/**
	 * Populate object properties either from the CPT meta or Settings.
	 *
	 * @param bool $is_cpt Whether the chatbot is a CPT (custom post type).
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function populate_properties( $is_cpt ) {
		$reflection = new ReflectionClass( $this );
		$properties = $reflection->getProperties();
		foreach ( $properties as $property ) {
			$property_name = $property->getName();
			// TODO follow open-ai Chatbot_Utility. Cause below there are couple inaccuracies
			if ( empty( $this->{$property_name} ) && ! $property->isStatic() ) {
				$key = self::class . '_' . $property_name;
				if ( $is_cpt ) {
					$value = $this->global_utility->get_chatbot()->get_meta( $property_name );
				} else {
					$value = Setting::find( $key )->get_value();
				}
				$this->{$property_name} = $value;
			}
		}
	}

	/**
	 * Generate content by delegating to the endpoint.
	 *
	 * @return Message|null Generated message result.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function generate() {
		return $this->get_endpoint_instance()->generate();
	}

	/**
	 * Get instance of the Generate Content Endpoint.
	 *
	 * @return Generate_Content_Endpoint
	 * @since 1.0.0
	 */
	public function get_endpoint_instance() {
		return new Generate_Content_Endpoint( $this );
	}

	/**
	 * Get the AI model instance based on the AI model ID.
	 *
	 * @return AI_Model
	 * @since 1.0.0
	 */
	public function get_ai_model() {
		return AI_Model::find( $this->ai_model_id );
	}

	/**
	 * Process a tool call message by invoking the respective function and returning a Tool_Result_Message.
	 *
	 * @param Tool_Calls_Message $tool_calls_message The tool calls message.
	 *
	 * @return Tool_Result_Message|Agent_Operation_Results_Message|null The result message or null if function does not exist.
	 *
	 * @throws Exception If the function call object is invalid.
	 * @since 1.0.0
	 */
	public function tool_call( Tool_Calls_Message $tool_calls_message ): array {
		if ( empty( $tool_calls_message->get_parts()[0]->functionCall->name ) || empty( $tool_calls_message->get_parts()[0]->functionCall->args ) ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Invalid function_call object', 'limb-chatbot' ) );
		}
		$parts        = $tool_calls_message->get_parts();
		$result_parts = [];
		foreach ( $parts as $part ) {
			if ( $part && ! empty( $part->functionCall ) ) {
				if ( ! empty( $part->functionCall->name ) ) {
					$tool_name = $part->functionCall->name;
					$tool      = Chatbot_Tools::instance()->get_tool( $tool_name );
					if ( ! $tool ) {
						continue;
					}
					$arguments = (array) $part->functionCall->args;
					$result    = $tool->execute( $arguments, $tool_calls_message, $this->global_utility );
					if ( $result instanceof Agent_Operation_Results_Message ) {
						return [ $result ];
					}
					$result_parts[] = [
						'functionResponse' => [
							'name'     => $tool_name,
							'response' => [
								'name'    => $tool_name,
								'content' => $result,
							],
						],
					];
				}
			}
		}

		return [ Tool_Result_Message::make( [ 'role' => 'user', 'parts' => $result_parts ] ) ];
	}

	/**
	 * Get system instructions from the first message if it is a system role.
	 *
	 * @return string|false The system instructions string or false if not available.
	 * @since 1.0.0
	 */
	public function get_system_instructions() {
		$messages = $this->global_utility->get_messages();
		if ( $messages[0] && $messages[0]->get_role() === 'system' ) {
			return $messages[0]->get_content()[0]['text']['value'];
		}

		return false;
	}

	/**
	 * Get the config instance if config_id is set.
	 *
	 * @return Config|null
	 * @since 1.0.0
	 */
	public function get_config() {
		return ! empty( $this->config_id ) ? Config::find( $this->config_id ) : null;
	}

	/**
	 * Get the API version string.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_version() {
		return ! empty( $this->version ) ? $this->version : 'v1beta';
	}

	/**
	 * Get timeout value in seconds.
	 *
	 * @return int
	 * @since 1.0.0
	 */
	public function get_timeout() {
		return ! empty( $this->timeout ) ? $this->timeout : $this->default_timeout;
	}

	/**
	 * Get the tools in the format required for the API.
	 *
	 * @return array|null
	 * @since 1.0.0
	 */
	public function get_tools() {
		$tools         = [];
		$chatbot_tools = $this->global_utility->get_tools();
		if ( empty( $chatbot_tools ) ) {
			return null;
		}
		foreach ( $chatbot_tools as $tool ) {
			$parameters                       = $tool->get_parameters_schema();
			$tools['function_declarations'][] = array(
				'name'        => $tool->get_name(),
				'description' => $tool->get_description(),
				'parameters'  => array(
					'type'       => $parameters['type'] ?? null,
					'properties' => $parameters['properties'] ?? null,
					'required'   => $parameters['required'] ?? null,
				),
			);
		}

		return $tools;
	}
}