<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Embeddable_Message;
use Limb_Chatbot\Includes\Data_Objects\Limit;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\AI_Model_Meta;
use Limb_Chatbot\Includes\Data_Objects\Token_Usage;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Factories\Token_Calculator_Factory;
use Limb_Chatbot\Includes\Interfaces\Usage_Service_Interface;
use Limb_Chatbot\Includes\Repositories\Vector_Repository;

/**
 * Handles usage checking and cost validation for input messages based on limits.
 *
 * @since 1.0.0
 */
class Input_Usage_Service implements Usage_Service_Interface {

	/**
	 * Check if a message exceeds the given input limit (token or cost).
	 *
	 * @param  Limit  $limit  The limit to check against.
	 * @param  Message|null  $message  Optional message object.
	 *
	 * @return Limit|null   Returns the limit if exceeded, or null if within range.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function check( Limit $limit, Message $message = null ) {
		if ( empty( $message ) || empty( $message->get_content() ) ) {
			return null;
		}
		if ( ! ( $tokens = ( new Token_Calculator_Factory() )->make( $message, $limit )->calculate() ) ) {
			return null;
		}
		if ( $limit->get_unit() == Limit::UNIT_COST ) {
			return $this->check_cost( $message, $limit, $tokens );
		}

		return $tokens > $limit->get_value() ? $limit : null;
	}

	/**
	 * Check if a message's token usage exceeds the defined cost limit.
	 *
	 * @param  Message  $message  The message to check.
	 * @param  Limit  $limit  The cost limit.
	 * @param  int  $tokens  The number of tokens used.
	 *
	 * @return Limit|null      Returns the limit if exceeded, or null otherwise.
	 * @throws Exception
	 * @since 1.0.0
	 *
	 */
	public function check_cost( Message $message, Limit $limit, int $tokens ) {
		if ( $message instanceof Embeddable_Message ) {
			$repository = new Vector_Repository();
			foreach ( $message->get_vector_index_ids() ?? [] as $vector_index_id ) {
				$vector = $repository->get_index_vector( $vector_index_id );
				if ( $vector && ! $this->is_cost_limit_within_range( $limit, $tokens, $vector->get_ai_model_id() ) ) {
					$passed = true;
					break;
				}
			}

			return ! empty( $passed ) ? $limit : null;
		} else {
			return $this->is_cost_limit_within_range( $limit, $tokens, $limit->get_chatbot()->get_ai_model_id() ) ? null : $limit;
		}
	}

	/**
	 * Determines if the cost of input tokens is within the limit for the given model.
	 *
	 * @since 1.0.0
	 *
	 * @param Limit     $limit       The cost limit.
	 * @param int       $tokens      The number of tokens used.
	 * @param int|null  $ai_model_id The AI model ID used for pricing.
	 * @return bool     True if within cost range, false otherwise.
	 *
	 * @throws Exception If model metadata is missing or invalid.
	 */
	private function is_cost_limit_within_range( Limit $limit, int $tokens, ?int $ai_model_id ) {
		if ( empty( $ai_model_id ) ) {
			throw new Exception( Error_Codes::NOT_FOUND, __( 'AI_Model not found', 'limb-chatbot' ) );
		}
		$input_token_cost = AI_Model_Meta::where( [ 'ai_model_id' => $ai_model_id, 'meta_key' => 'input_token_cost' ] )->first();
		if ( $input_token_cost === null ) {
			throw new Exception( Error_Codes::MISSING_VALUE, __( 'The input token cost is missing', 'limb-chatbot' ) );
		}
		$input_token_cost = $input_token_cost->get_meta_value() * $tokens;

		return $input_token_cost <= $limit->get_value();
	}

	/**
	 * Updates usage statistics — for this service, no usage update is performed.
	 *
	 * @since 1.0.0
	 *
	 * @param Limit         $limit The current limit object.
	 * @param Token_Usage   $usage Token usage data.
	 * @param AI_Model      $model AI model used.
	 * @param Chat|null     $chat  Optional chat reference.
	 * @return void
	 */
	public function update_usage( Limit $limit, Token_Usage $usage, AI_Model $model, ?Chat $chat = null ) {
		// There is nothing to update for message
	}
}