<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\AI_Model_Meta;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Repositories\AI_Model_Repository;
use Limb_Chatbot\Includes\Utilities\AI_Model_Utility;
use Limb_Chatbot\Includes\Utilities\Fine_Tuning_Utility;


/**
 * Service class to handle AI model operations including fine tuning.
 *
 * @since 1.0.0
 */
class AI_Model_Service {

	/**
	 * Repository instance for AI_Model data operations.
	 *
	 * @var AI_Model_Repository
	 * @since 1.0.0
	 */
	protected AI_Model_Repository $repository;

	/**
	 * AI_Model_Service constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->repository = new AI_Model_Repository();
	}

//	/**
//	 * Starts the fine tuning process for an AI model.
//	 *
//	 * @param  array  $data  Data required to start tuning process including dataset and model identifiers.
//	 *
//	 * @return AI_Model The newly created fine-tuned AI model.
//	 * @throws Exception If there is an active formatting process or any error during creation.
//	 * @since 1.0.0
//	 */
//	public function tuning( array $data ): AI_Model {
//		try {
//			$process_handler = Fine_Tuning_File_Formatting\Handler::instance();
//			if ( $process_handler->actual_process->is_active() || $process_handler->chunk_process->is_active() ) {
//				throw new Exception( Error_Codes::TECHNICAL_ERROR, __( 'You already have active formatting process running. Please wait till its finished!', 'limb-chatbot' ) );
//			}
//			$dataset = Dataset::find( $data['dataset_id'] );
//			$parent  = AI_Model::find( $data['ai_model_id'] );
//			$model   = $this->create_for_tuning( $data, $parent );
//			$process_handler->set_model( $model );
//			$process_handler->set_dataset( $dataset );
//			$process_handler->start_process( Dataset_Entry::count( [ 'dataset_id' => $dataset->get_id() ] ) );
//
//			return AI_Model::where( [ 'id' => $model->id ] )->with( 'metas', [ 'meta_key' => [ 'status' ] ] )->first();
//		} catch ( \Exception $e ) {
//			Helper::log( $e );
//			if ( isset( $model ) && $model instanceof AI_Model ) {
//				$this->delete( $model->get_id() );
//			}
//			throw $e;
//		}
//	}

	/**
	 * Creates a new AI_Model instance prepared for fine tuning.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data Data to create the model, including suffix and config_id.
	 * @param AI_Model|null $parent Optional parent AI model to inherit properties from.
	 *
	 * @return AI_Model|null The newly created AI model or null on failure.
	 *
	 * @throws Exception If file or directory creation fails.
	 */
	protected function create_for_tuning( array $data, AI_Model $parent = null ): ?AI_Model {
		try {
			$model = $this->repository->create( [
				'ai_provider_id'     => $parent->get_ai_provider_id(),
				'fine_tuned'         => true,
				'status'             => AI_Model::STATUS_INACTIVE,
				'features'           => $parent->get_features(),
				'endpoints'          => $parent->get_endpoints(),
				'modalities'         => $parent->get_modalities(),
				'input_token_limit'  => $parent->get_input_token_limit(),
				'output_token_limit' => $parent->get_output_token_limit(),
				'name'               => $data['suffix'],
				'label'              => $parent->get_label() . ' ' . $data['suffix']
			] );
			$model->add_meta( 'dataset_id', $data['dataset_id'] );
			$model->add_meta( 'parent_model_id', $parent->get_id() );
			$model->add_meta( 'created_at', date( 'Y-m-d H:i:s' ) );
			$model->add_meta( 'suffix', $data['suffix'] );
			$model->add_meta( 'config_id', $data['config_id'] );
			$model->add_meta( 'input_token_cost', $parent->get_meta( 'input_token_cost' ) );
			$model->add_meta( 'output_token_cost', $parent->get_meta( 'output_token_cost' ) );
			if ( ! empty( $data['variables'] ) ) {
				$model->add_meta( 'variables', $data['variables'] );
			}
			$target_dir = Helper::get_wp_uploaded_file_dir( Limb_Chatbot()->get_files_dir() . AI_Model::FINE_TUNING_FILES_SUB_DIR );
			if ( ! wp_mkdir_p( $target_dir ) ) {
				throw new Exception( Error_Codes::FILE_UNABLE_TO_CREATE_UPLOAD_DIRECTORY, __( 'Failed to create tuning file directory.', 'limb-chatbot' ) );
			}
			if ( file_put_contents( $model->get_fine_tuning_file(), '' ) === false ) {
				throw new Exception( Error_Codes::FILE_FAILED_TO_CREATE, __( 'Failed to create training file for tuning', 'limb-chatbot' ) );
			}

			return $model;
		} catch ( \Exception $e ) {
			Helper::log( $e );
			if ( isset( $model ) && $model instanceof AI_Model ) {
				$this->delete( $model->get_id() );
			}
			throw $e;
		}
	}

	/**
	 * Deletes an AI model and its related resources.
	 *
	 * @since 1.0.0
	 *
	 * @param int $id AI model ID to delete.
	 *
	 * @return bool True on success, false otherwise.
	 *
	 * @throws Exception If deletion fails due to other errors.
	 */
	public function delete( int $id ): bool {
		$ai_model = AI_Model::find( $id );
		if ( ! is_null( $ai_model ) ) {
			try {
				if ( ( new AI_Model_Utility( $ai_model ) )->delete() ) {
					if ( $ai_model->get_fine_tuned() ) {
						@unlink( $ai_model->get_fine_tuning_file() );
					}

					return $this->repository->delete( $ai_model );
				}
			} catch ( Exception $e ) {
				if ( $e->get_http_status() === Response_Handler::STATUS_CODE_NOT_FOUND ) {
					if ( $ai_model->get_fine_tuned() ) {
						@unlink( $ai_model->get_fine_tuning_file() );
					}

					return $this->repository->delete( $ai_model );
				}
				throw $e;
			}
		}

		return false;
	}

	/**
	 * Retrieves AI models matching the specified parameters.
	 *
	 * @since 1.0.0
	 *
	 * @param array $params Query parameters including search, filters, pagination, and includes.
	 *
	 * @return Collection Collection of AI_Model objects.
	 *
	 * @throws Exception If pending fine tunings check fails.
	 */
	public function get_items( $params ) {
		if ( ! empty( $params['fine_tuned'] ) && ! empty( $params['meta_keys'] ) && in_array( 'status', $params['meta_keys'] ) ) {
			$this->check_pending_fine_tunings();
		}
		if ( ! empty( $params['search'] ) && ! empty( $params['search_fields'] ) ) {
			foreach ( $params['search_fields'] as $field ) {
				$params["{$field}LIKE"] = "%{$params['search']}%";
			}
		}
		$models = AI_Model::where( $params, $params['per_page'] ?? 10, $params['page'] ?? 1, $params['orderby'] ?? 'ai_provider_id', $params['order'] ?? 'DESC' );
		if ( $models->is_empty() ) {
			return $models;
		}
		if ( ! empty( $params['include'] ) && $include = $params['include'] ) {
			foreach ( $include as $relation ) {
				$args = $include == 'metas' && ! empty( $params['meta_keys'] ) ? [ 'meta_key' => $params['meta_keys'] ] : [];
				$models->with( $relation, $args );
			}
		}

		return $models;
	}

	/**
	 * Checks and updates the status of all pending fine tuning AI models.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 *
	 * @throws Exception If the response format from fine tuning utility is unexpected.
	 */
	public function check_pending_fine_tunings(): void {
		$models = AI_Model::where( [ 'fine_tuned' => true ] );
		if ( $models->is_empty() ) {
			return;
		}
		foreach ( $models->get() as $model ) {
			if ( $model->get_meta( 'status' ) == AI_Model_Meta::FINE_TUNING_STARTED ) {
				$response = ( new Fine_Tuning_Utility( $model ) )->retrieve();
				if ( ! is_array( $response ) ) {
					throw new Exception( Error_Codes::UNEXPECTED_RESPONSE_FORMAT, __( 'Unexpected response format', 'limb-chatbot' ) );
				}
				foreach ( $response as $model_meta ) {
					if ( ! $model_meta instanceof AI_Model_Meta ) {
						continue;
					}
					$model_meta->set_model_id( $model->get_id() );
					$model_meta->save();
				}
			}
			if ( $model->get_meta( 'status' ) == AI_Model_Meta::FINE_TUNING_COMPLETED ) {
				$model->status = AI_Model::STATUS_ACTIVE;
				$model->name   = $model->get_meta( $model->get_ai_provider_id() . '_fine_tuned_model' );
				$model->save();
			}
		}
	}

	/**
	 * Calculates the cost of input tokens for the given AI model.
	 *
	 * @since 1.0.0
	 *
	 * @param AI_Model $model The AI model instance.
	 * @param int $tokens Number of input tokens.
	 *
	 * @return float|null The calculated cost or null if cost is not set.
	 */
	public function get_input_cost( AI_Model $model, $tokens ) {
		if ( ! $token_cost = (float) $model->get_meta( 'input_token_cost' ) ) {
			return null;
		}

		return $token_cost * $tokens;
	}

	/**
	 * Calculates the cost of output tokens for the given AI model.
	 *
	 * @since 1.0.0
	 *
	 * @param AI_Model $model The AI model instance.
	 * @param int $tokens Number of output tokens.
	 *
	 * @return float|null The calculated cost or null if cost is not set.
	 */
	public function get_output_cost( AI_Model $model, $tokens ) {
		if ( ! $token_cost = (float) $model->get_meta( 'output_token_cost' ) ) {
			return null;
		}

		return $token_cost * $tokens;
	}
}