<?php

namespace Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Utilities;

use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Data_Objects\Vector;
use Limb_Chatbot\Includes\Data_Objects\Vector_Index;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Utilities\Vector_Db_Utility as Global_Vector_Db_Utility;
use Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Endpoints\Vectors_Endpoint;
use Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Endpoints\Query_Endpoint;
use Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Endpoints\Index_Endpoint;
use Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Pinecone;
use ReflectionClass;


/**
 * Class Vector_Db_Utility
 *
 * Pinecone-specific vector database utility wrapper.
 *
 * @since 1.0.0
 */
class Vector_Db_Utility {

	/**
	 * Serverless spec type constant.
	 *
	 * @since 1.0.0
	 */
	const SPEC_SERVERLESS = 'serverless';

	/**
	 * Pod spec type constant.
	 *
	 * @since 1.0.0
	 */
	const SPEC_POD = 'pod';

	/**
	 * Global vector db utility instance.
	 *
	 * @var Global_Vector_Db_Utility
	 * @since 1.0.0
	 */
	public Global_Vector_Db_Utility $global_utility;

	/**
	 * Configuration object.
	 *
	 * @var Config|null
	 * @since 1.0.0
	 */
	public ?Config $config = null;

	/**
	 * Configuration ID.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $config_id = null;

	/**
	 * Distance metric used for vectors.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $metric = 'cosine';

	/**
	 * Vector dimensionality.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	public int $dimension = 1536;

	/**
	 * Deployment specification type.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $spec = 'serverless';

	/**
	 * Cloud provider name.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $cloud = 'aws';

	/**
	 * Cloud region.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $region = 'us-east-1';

	/**
	 * Environment name.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $environment = null;

	/**
	 * Pod type name.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $pod_type = null;

	/**
	 * Number of pods.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $pods = null;

	/**
	 * Deletion protection setting.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $deletion_protection = 'disabled';

	/**
	 * Request timeout in seconds.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	public int $timeout = 60;

	/**
	 * Constructor.
	 *
	 * @param Global_Vector_Db_Utility $global_utility Global vector DB utility instance.
	 *
	 * @since 1.0.0
	 */
	public function __construct( Global_Vector_Db_Utility $global_utility ) {
		$this->global_utility = $global_utility;
		$vector_index         = $this->global_utility->get_vector_index();
		$vector_index_metas   = $this->global_utility->get_vector_index_metas();
		$this->populate_properties( 'vector_dbs.' . Pinecone::$id . '.', $vector_index, $vector_index_metas );
		if ( $config_id = $vector_index->get_config_id() ) {
			$this->config_id = $config_id;
		} else {
			$vector_index->set_config_id( $this->config_id );
		}
		$this->config = Config::find( $this->config_id );
	}

	/**
	 * Populate properties from settings and vector index metas.
	 *
	 * @param string       $setting_prefix      Prefix to prepend to setting keys.
	 * @param Vector_Index $vector_index        Vector index instance.
	 * @param array        $vector_index_metas  Array of metas related to the vector index.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function populate_properties( $setting_prefix, Vector_Index $vector_index, array $vector_index_metas ): void {
		$reflection = new ReflectionClass( $this );
		$properties = $reflection->getProperties();
		foreach ( $properties as $property ) {
			if ( $property->getDeclaringClass()->getName() !== $reflection->getName() ) {
				// If not own property, pass it.
				continue;
			}
			if ( ! $property->getType() || ! $property->getType()->isBuiltin() ) {
				// If not built in property type, pass it.
				continue;
			}

			// From vector metas
			if ( ! empty( $vector_index_metas ) ) {
				foreach ( $vector_index_metas as $index_meta ) {
					if( $index_meta->get_meta_key() == $property->getName() ) {
						$value = $index_meta->get_meta_value();
						
						break;
					}
				}
			}

			// From settings
			if( ! isset( $value ) ) {
				$value = Setting::find( $setting_prefix . $property->getName() )->get_value();
			}
			
			if( isset( $value ) ) {
				$property->setValue( $this, $value );
			}
			unset($value);
		}
	}

	/**
	 * Create vector index.
	 *
	 * @return Collection Collection of created index metas.
	 *
	 * @throws \Exception
	 * @since 1.0.0
	 */
	public function create(): Collection {
		return ( new Index_Endpoint( $this ) )->create();
	}

	/**
	 * Delete vector index.
	 *
	 * @return bool Result of delete operation.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function delete() {
		return ( new Index_Endpoint( $this ) )->delete();
	}

	/**
	 * Upsert a single vector.
	 *
	 * @param  Vector  $vector  Vector instance to upsert.
	 *
	 * @return bool True on success.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function upsert( Vector $vector ): bool {
		return ( new Vectors_Endpoint( $this ) )->upsert( $vector );
	}

	/**
	 * Upsert multiple vectors in batch.
	 *
	 * @param  Vector[]  $vectors  Array of Vector instances.
	 *
	 * @return bool True on success.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function upsert_batch( array $vectors ): bool {
		return ( new Vectors_Endpoint( $this ) )->upsert_batch( $vectors );
	}

	/**
	 * Delete a single vector.
	 *
	 * @param  Vector  $vector  Vector instance to delete.
	 *
	 * @return bool True on success.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function delete_vector( Vector $vector ): bool {
		return ( new Vectors_Endpoint( $this ) )->delete( $vector );
	}

	/**
	 * Delete multiple vectors in batch.
	 *
	 * @param  Vector[]  $vectors  Array of Vector instances.
	 *
	 * @return bool True on success.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function delete_vector_batch( array $vectors ): bool {
		return ( new Vectors_Endpoint( $this ) )->delete( $vectors );
	}

	/**
	 * Query vectors from the vector DB.
	 *
	 * @param  Vector  $vector  Vector instance to query.
	 * @param  int  $count  Number of results to return.
	 *
	 * @return Collection Collection of matching vectors.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function query( Vector $vector, int $count = 5 ): Collection {
		$res = ( new Query_Endpoint( $this ) )->query( $vector, $count );

		return $res ?? new Collection();
	}

	/**
	 * Get the global vector DB utility.
	 *
	 * @return Global_Vector_Db_Utility
	 *
	 * @since 1.0.0
	 */
	public function get_global_utility(): Global_Vector_Db_Utility {
		return $this->global_utility;
	}

	/**
	 * Get config object.
	 *
	 * @return Config|null
	 *
	 * @since 1.0.0
	 */
	public function get_config(): ?Config {
		return $this->config;
	}

	/**
	 * Set config object.
	 *
	 * @param Config|null $config Config instance or null.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_config( ?Config $config ): void {
		$this->config = $config;
	}

	/**
	 * Get config ID.
	 *
	 * @return int|null
	 *
	 * @since 1.0.0
	 */
	public function get_config_id(): ?int {
		return $this->config_id;
	}

	/**
	 * Set config ID.
	 *
	 * @param int|null $config_id Config ID or null.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_config_id( ?int $config_id ): void {
		$this->config_id = $config_id;
	}

	/**
	 * Get request timeout.
	 *
	 * @return int Timeout in seconds.
	 *
	 * @since 1.0.0
	 */
	public function get_timeout(): int {
		return $this->timeout;
	}

	/**
	 * Get metric.
	 *
	 * @return string Metric name.
	 *
	 * @since 1.0.0
	 */
	public function get_metric(): string {
		return $this->metric;
	}

	/**
	 * Set metric.
	 *
	 * @param string $metric Metric name.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_metric( string $metric ): void {
		$this->metric = $metric;
	}

	/**
	 * Get dimension.
	 *
	 * @return int Vector dimension.
	 *
	 * @since 1.0.0
	 */
	public function get_dimension(): int {
		return $this->dimension;
	}

	/**
	 * Set dimension.
	 *
	 * @param int $dimension Vector dimension.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_dimension( int $dimension ): void {
		$this->dimension = $dimension;
	}

	/**
	 * Get spec.
	 *
	 * @return string Spec type.
	 *
	 * @since 1.0.0
	 */
	public function get_spec(): string {
		return $this->spec;
	}

	/**
	 * Set spec.
	 *
	 * @param string $spec Spec type.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_spec( string $spec ): void {
		$this->spec = $spec;
	}

	/**
	 * Get cloud provider.
	 *
	 * @return string Cloud provider name.
	 *
	 * @since 1.0.0
	 */
	public function get_cloud(): string {
		return $this->cloud;
	}

	/**
	 * Set cloud provider.
	 *
	 * @param string $cloud Cloud provider name.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_cloud( string $cloud ): void {
		$this->cloud = $cloud;
	}

	/**
	 * Get cloud region.
	 *
	 * @return string Cloud region.
	 *
	 * @since 1.0.0
	 */
	public function get_region(): string {
		return $this->region;
	}

	/**
	 * Set cloud region.
	 *
	 * @param string $region Cloud region.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_region( string $region ): void {
		$this->region = $region;
	}

	/**
	 * Get environment.
	 *
	 * @return string|null Environment name.
	 *
	 * @since 1.0.0
	 */
	public function get_environment(): ?string {
		return $this->environment;
	}

	/**
	 * Set environment.
	 *
	 * @param string|null $environment Environment name.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_environment( ?string $environment ): void {
		$this->environment = $environment;
	}

	/**
	 * Get pod type.
	 *
	 * @return string|null Pod type name.
	 *
	 * @since 1.0.0
	 */
	public function get_pod_type(): ?string {
		return $this->pod_type;
	}

	/**
	 * Set pod type.
	 *
	 * @param string|null $pod_type Pod type name.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_pod_type( ?string $pod_type ): void {
		$this->pod_type = $pod_type;
	}

	/**
	 * Get number of pods.
	 *
	 * @return int|null Number of pods.
	 *
	 * @since 1.0.0
	 */
	public function get_pods(): ?int {
		return $this->pods;
	}

	/**
	 * Set number of pods.
	 *
	 * @param int|null $pods Number of pods.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_pods( ?int $pods ): void {
		$this->pods = $pods;
	}

	/**
	 * Get deletion protection status.
	 *
	 * @return string Deletion protection status.
	 *
	 * @since 1.0.0
	 */
	public function get_deletion_protection(): string {
		return $this->deletion_protection;
	}

	/**
	 * Set deletion protection status.
	 *
	 * @param string $deletion_protection Deletion protection status.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_deletion_protection( string $deletion_protection ): void {
		$this->deletion_protection = $deletion_protection;
	}

	/**
	 * Check if spec is serverless.
	 *
	 * @return bool True if spec is serverless, false otherwise.
	 *
	 * @since 1.0.0
	 */
	public function is_serverless_spec(): bool {
		return $this->spec === self::SPEC_SERVERLESS;
	}

	/**
	 * Check if spec is pod.
	 *
	 * @return bool True if spec is pod, false otherwise.
	 *
	 * @since 1.0.0
	 */
	public function is_pod_spec(): bool {
		return $this->spec === self::SPEC_POD;
	}
}