<?php

namespace Limb_Chatbot\Includes\Services;

use Exception;
use Limb_Chatbot\Includes\Config_Dependent_Interface;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Dataset;
use Limb_Chatbot\Includes\Data_Objects\Dataset_Meta;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Repositories\Config_Repository;
use Limb_Chatbot\Includes\Repositories\Vector_Index_Repository;


/**
 * Service class for managing chatbot configuration records.
 *
 * Handles creation, updating, and fetching of Config objects.
 *
 * @since 1.0.0
 */
class Config_Service {

	/**
	 * Repository used to persist and retrieve configuration data.
	 *
	 * @var Config_Repository
	 * @since 1.0.0
	 */
	public Config_Repository $repository;

	/**
	 * Config_Service constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param Config_Repository $repository The config repository instance.
	 */
	public function __construct( Config_Repository $repository ) {
		$this->repository = $repository;
	}

	/**
	 * Creates a new configuration.
	 *
	 * Automatically marks the first config for a `related_to` value as default.
	 * If a new default is being created, existing defaults for the same `related_to` are unset.
	 *
	 * @param  array  $data  Configuration data.
	 *
	 * @return Config The created Config object.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function create( array $data ): Config {
		$data['related_to'] = Config::get_config_dependant_instances( $data['related_to'] );
		if ( ! Config::count( [ 'related_to' => $data['related_to'] ] ) ) {
			$data['default'] = true;
		} else {
			if ( ! empty( $data['default'] ) ) {
				Config::update( [ 'related_to' => $data['related_to'] ], [ 'default' => false ] );
			}
		}
		$config           = Config::make( $data );
		$config_dependant = new $data['related_to'];
		if ( $config_dependant instanceof Config_Dependent_Interface ) {
			if ( $config_dependant->is_valid_config( $config ) ) {
				return $this->repository->create( $data );
			} else {
				throw new \Limb_Chatbot\Includes\Exceptions\Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Invalid config', 'limb-chatbot' ) );
			}
		}

		return $this->repository->create( $data );
	}

	/**
	 * Updates a configuration entry by ID.
	 *
	 * If the updated config is marked as default, all other configs for the same `related_to` will have their default removed.
	 * `params` will be merged with existing ones.
	 *
	 * @since 1.0.0
	 *
	 * @param int $config_id Configuration ID.
	 * @param array $data Data to update.
	 * @return Config The updated Config object.
	 */
	public function update( int $config_id, array $data ): Config {
		$config = Config::find( $config_id );
		if ( ! empty( $data['default'] ) ) {
			Config::update( [ 'related_to' => $config->get_related_to() ], [ 'default' => false ] );
		}
		unset( $data['related_to'] );
		if ( isset( $data['params'] ) ) {
			$data['params'] = array_merge( $config->get_params(), $data['params'] );
		}
		$this->repository->update( $config_id, $data );

		// TODO not the best implementation to fetch again. 
		return Config::find( $config_id );
	}

	/**
	 * Retrieves a collection of configuration items based on filters.
	 *
	 * Converts `related_to` to instance names if provided.
	 *
	 * @since 1.0.0
	 *
	 * @param array|null $params Optional query parameters.
	 * @return Collection|null Resulting configuration collection.
	 */
	public function get_items( ?array $params ): ?Collection {
		if ( isset( $params['related_to'] ) ) {
			$params['related_to'] = Config::get_config_dependant_instances( $params['related_to'] );
		}

		return $this->repository->get_items( $params );
	}

	/**
	 * Retrieves a single configuration item by ID.
	 *
	 * @since 1.0.0
	 *
	 * @param int $id Configuration ID.
	 * @param array|null $params Optional unused parameters.
	 * @return Config|null The Config object, or null if not found.
	 */
	public function get_item( int $id, ?array $params ) {
		return Config::find( $id );
	}

	public function delete( Config $config ) {
		// 1. First delete the datasets connected with config
		$dataset_service      = new Dataset_Service();
		$vector_index_service = new Vector_Index_Service( new Vector_Index_Repository );
		$dataset_metas        = Dataset_Meta::where( [
			'meta_key'   => 'index_config_id',
			'meta_value' => $config->get_id()
		] );
		if ( ! $dataset_metas->is_empty() ) {
			$dataset_metas->each( function ( Dataset_Meta $dataset_meta ) use (
				$dataset_service,
				$vector_index_service
			) {
				$dataset = $dataset_meta->dataset();
				if ( $dataset instanceof Dataset ) {
					// Check if dataset is last in storage and remove vector index if so
					$vector_index_id = $dataset->is_last_in_storage();
					if ( $vector_index_id ) {
						$vector_index_service->remove( $vector_index_id );
					}

					// Clear dataset entries (deletes all entries and their vectors)
					$dataset_service->clear( $dataset );

					// Delete dataset metadata
					Dataset_Meta::delete( [ 'dataset_id' => $dataset->get_id() ] );

					// Delete the dataset itself
					Dataset::delete( [ 'id' => $dataset->get_id() ] );
				}
			} );
		}

		// 2. Delete the config value from all possible chatbot parameters
		Config::delete(['id' => $config->get_id()]);

		return true;
	}
}