<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Exception;
use Limb_Chatbot\Includes\AI_Providers\AI_Provider;
use Limb_Chatbot\Includes\AI_Providers\AI_Providers;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Factories\Chatbot_Parameter_Parser_Factory;
use Limb_Chatbot\Includes\Repositories\Vector_Index_Repository;
use Limb_Chatbot\Includes\Services\Vector_Index_Service;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Vector_Dbs\Local\Local;

/**
 * Class Chatbot
 *
 * Represents a chatbot custom post type in the system.
 *
 * @package Limb_Chatbot\Includes\Data_Objects
 * @since 1.0.0
 */
class Chatbot extends WP_Post_Data_Object {

	/**
	 * Chatbot type constant for assistant.
	 */
	const TYPE_ASSISTANT = 'assistant';

	/**
	 * Chatbot type constant for simple.
	 */
	const TYPE_SIMPLE = 'simple';

	/**
	 * Setting prefix for chatbot related settings.
	 */
	const SETTING_PREFIX = 'utilities.chatbot.';

	/**
	 * Custom post type identifier.
	 */
	const POST_TYPE = 'lbaic_chatbot';

	/**
	 * Post ID.
	 *
	 * @var int|null
	 * @json_excluded
	 * @since 1.0.0
	 */
	public $id;

	/**
	 * UUID of the chatbot.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $uuid = null;

	/**
	 * Meta properties to be loaded.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	protected array $meta_properties = [ 'utility' ];

	/**
	 * Flag indicating preview mode.
	 *
	 * @var bool
	 * @json_excluded
	 * @since 1.0.0
	 */
	protected bool $preview = false;

	/**
	 * Included relations or properties.
	 *
	 * @var array
	 * @json_excluded
	 * @since 1.0.0
	 */
	public array $included = [];

	/**
	 * Chatbot constructor.
	 *
	 * @param mixed|null $instance Optional instance data.
	 *
	 * @since 1.0.0
	 */
	public function __construct( $instance = null ) {
		parent::__construct( $instance );
		// Override input in case wp object_id and object_type sent
		if ( empty( $this->get_id() ) ) {
			// We are dealing with default chatbot
			$from_setting = Setting::find( Chatbot::SETTING_PREFIX . 'status' )->get_value();
			if ( isset( $from_setting ) ) {
				$this->status = (int) $from_setting;
			}
		}
	}

	/**
	 * Find chatbot by UUID.
	 *
	 * @param string $uuid UUID to search for.
	 * @return static|null Chatbot instance or null if not found.
	 * @since 1.0.0
	 */
	public static function find_by_uuid( $uuid ) {
		$chatbots = self::get_db_strategy()->find_by_meta( '_uuid', $uuid, self::POST_TYPE );
		if ( empty( $chatbots[0] ) ) {
			$chatbots = self::get_db_strategy()->find_by_meta( '_uuid', $uuid, self::POST_TYPE, WP_Post_Data_Object::STATUS_AUTO_DRAFT );
		}
		if ( ! empty( $chatbots[0] ) ) {
			$chatbot       = self::make( $chatbots[0] );
			$chatbot->uuid = $uuid;
		}

		return $chatbot ?? null;
	}

	/**
	 * Check if a chatbot exists by UUID.
	 *
	 * @param string $uuid UUID to check.
	 * @return bool True if exists, false otherwise.
	 * @since 1.0.0
	 */
	public static function exists_by_uuid( $uuid ) {
		$chatbots = self::get_db_strategy()->find_by_meta( '_uuid', $uuid, self::POST_TYPE );
		$chatbots = ! empty( $chatbots[0] ) ? $chatbots : self::get_db_strategy()->find_by_meta( '_uuid', $uuid, self::POST_TYPE, WP_Post_Data_Object::STATUS_AUTO_DRAFT );

		return ! empty( $chatbots[0] );
	}

	/**
	 * Check if the default chatbot is published.
	 *
	 * @return bool True if published, false otherwise.
	 * @since 1.0.0
	 */
	public static function is_default_published(): bool {
		return Setting::find( self::SETTING_PREFIX . 'status' )->get_value() == self::STATUS_PUBLISHED;
	}

	/**
	 * Get associated chatbot configuration.
	 *
	 * @return Config|null Config object or null if not found.
	 * @since 1.0.0
	 */
	public function get_config() {
		if ( $this->get_config_id() ) {
			return Config::find( $this->get_config_id() );
		}

		return null;
	}

	/**
	 * Get config ID parameter.
	 *
	 * @return mixed|null
	 * @since 1.0.0
	 */
	public function get_config_id() {
		return $this->get_parameter( 'config_id' );
	}

	/**
	 * Get AI provider instance.
	 *
	 * @return AI_Provider|null AI provider instance or null if not found.
	 * @since 1.0.0
	 */
	public function get_ai_provider(): ?AI_Provider {
		return AI_Providers::instance()->get_ai_provider( $this->get_ai_provider_id() );
	}

	/**
	 * Get AI provider ID parameter.
	 *
	 * @return mixed|null
	 * @since 1.0.0
	 */
	public function get_ai_provider_id() {
		return $this->get_parameter( 'ai_provider_id' );
	}

	/**
	 * Get chatbot type.
	 *
	 * @return mixed|null
	 * @since 1.0.0
	 */
	public function get_type() {
		return $this->get_parameter( 'type' );
	}

	/**
	 * Get AI model ID parameter.
	 *
	 * @return mixed|null
	 * @since 1.0.0
	 */
	public function get_ai_model_id() {
		return $this->get_parameter( 'ai_model_id' );
	}

	/**
	 * Get AI model instance.
	 *
	 * @return AI_Model|null AI model or null if not found.
	 * @since 1.0.0
	 */
	public function get_ai_model() {
		return AI_Model::find( $this->get_ai_model_id() );
	}

	/**
	 * Check if chatbot is in preview mode.
	 *
	 * @return bool True if preview, false otherwise.
	 * @since 1.0.0
	 */
	public function is_preview(): bool {
		return $this->preview;
	}

	/**
	 * Set preview mode flag.
	 *
	 * @param bool $preview
	 * @return void
	 * @since 1.0.0
	 */
	public function set_preview( bool $preview ): void {
		$this->preview = $preview;
	}

	/**
	 * Get chatbot utility helper instance.
	 *
	 * @return Chatbot_Utility Utility instance.
	 * @since 1.0.0
	 */
	public function utility(): Chatbot_Utility {
		return new Chatbot_Utility( $this );
	}

	/**
	 * Get a parameter value, with preview fallback.
	 *
	 * @param string $key Parameter key.
	 * @return mixed|null Parameter value or null if not set.
	 * @since 1.0.0
	 */
	public function get_parameter( string $key ) {
		if ( $this->is_preview() ) {
			$value = $this->get_parameter_value( $key . Setting::PREVIEW_POSTFIX );
		}
		if ( ! isset( $value ) ) {
			$value = $this->get_parameter_value( $key );
		}
		if ( isset( $value ) ) {
			if ( $parser = ( new Chatbot_Parameter_Parser_Factory() )->make( $key ) ) {
				return $parser->parse( $value, $this );
			}
		}

		return $value ?? null;
	}

	/**
	 * Helper to get raw parameter value from meta or settings.
	 *
	 * @param string $key Parameter key.
	 * @return mixed|null
	 * @since 1.0.0
	 */
	private function get_parameter_value( $key ) {
		$parameter = $this->id ? self::get_meta( ( ! str_starts_with( $key, '_' ) ? "_$key" : $key ), true ) : Setting::find( self::SETTING_PREFIX . $key );

		return $parameter ? ( $parameter instanceof Setting ? $parameter->get_value() : $parameter->get_meta_value() ) : null;
	}

	/**
	 * Update a parameter value.
	 *
	 * @param  string  $key  Parameter key.
	 * @param  mixed  $value  Parameter value.
	 *
	 * @return Chatbot_Meta|Setting Result of update operation.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function update_parameter( $key, $value ) {
		if ( empty( $this->id ) ) {
			return Setting::update( [ 'key' => Setting::SETTING_PREFIX . self::SETTING_PREFIX . $key ], [ 'value' => $value ] );
		} else {
			// In this method, we're always attaching '_' for meta fetching. Otherwise, use ::update method
			return Chatbot_Meta::update( [ 'meta_key' => ( ! str_starts_with( $key, '_' ) ? "_$key" : $key ), 'post_id' => $this->get_id() ], [ 'meta_value' => $value ] );
		}
	}

	/**
	 * Check if the chatbot has a specific meta key.
	 *
	 * @param string $meta_key Meta key to check.
	 * @return bool True if meta exists, false otherwise.
	 * @since 1.0.0
	 */
	public function has_meta( string $meta_key ): bool {
		return ! Chatbot_Meta::where( [ 'chatbot_id' => $this->id, 'meta_key' => $meta_key ] )->is_empty();
	}

	public function get_vector_index_for_chatbot( $vector_index_config_id, $vector_index_type, $vector_index_dimension ) {
		$vector_index_meta = Vector_Index_Meta::where( [
			'chatbot_uuid' => $this->uuid
		] );
		if ( $vector_index_meta->is_empty() ) {
			return null;
		}
		// Get the one which has the same config and vector index type
		foreach ( $vector_index_meta->get() as $meta ) {
			if ( ! $meta instanceof Vector_Index_Meta ) {
				continue;
			}
			$index = Vector_Index::find( $meta->get_vector_index_id() );
			$vector_index_config_id = $vector_index_type == Local::$id ? null : $vector_index_config_id;
			$vector_index_type = $vector_index_type == Local::$id ? null : $vector_index_type;
			if ( $index && $index->get_config_id() == $vector_index_config_id && $index->get_vector_db_id() == $vector_index_type ) {
				$dimension = $index->find_meta( 'dimension' );
				if ( $dimension instanceof Vector_Index_Meta ) {
					$dimension = $dimension->get_meta_value();
					if ( $dimension == $vector_index_dimension ) {
						$vector_indexes[] = $index;
					}
				}
			}
		}

		if ( ! empty( $vector_indexes ) ) {
			$connected_vector_indexes = $this->get_parameter( 'kb_vector_index_ids' );
			foreach ( $vector_indexes as $index ) {
				if ( in_array( $index->get_id(), $connected_vector_indexes ?? [] ) ) {
					return $index;
				}
			}
		}

		return null;
	}

	public function create_vector_index_for_chatbot( $vector_index_config_id, $vector_index_type, $dimension ) {
		$is_local             = $vector_index_type == Local::$id;
		$data = [
			'name'         => 'lbaicchatbot' . ( $this->uuid ? $this->uuid : 'default' ) . 'index',
			'vector_db_id' => $is_local ? null : $vector_index_type,
			'config_id'    => $is_local ? null : $vector_index_config_id,
			'metas'        => [
				[
					'meta_key'   => 'dimension',
					'meta_value' => $dimension,
				],
				[
					'meta_key'   => 'chatbot_uuid',
					'meta_value' => $this->uuid,
				]
			]
		];
		$vector_index_service = new Vector_Index_Service( new Vector_Index_Repository() );
		if ( $vector_index = $vector_index_service->create( $data ) ) {
			$vector_index_ids   = $this->get_parameter( 'kb_vector_index_ids' ) ?? [];
			$vector_index_ids[] = $vector_index->get_id();
			$this->update_parameter( 'kb_vector_index_ids', array_unique( $vector_index_ids ) );

			return $vector_index;
		}

		return null;
	}
}