<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Limb_Chatbot\Includes\Config_Dependent_Interface;
use Limb_Chatbot\Includes\AI_Providers\Claude\Claude;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Deep_Seek;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Gemini;
use Limb_Chatbot\Includes\AI_Providers\Grok\Grok;
use Limb_Chatbot\Includes\AI_Providers\Open_Ai\Open_Ai;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Open_Router;
use Limb_Chatbot\Includes\Integrations\Slack\Slack;
use Limb_Chatbot\Includes\Integrations\Telegram\Telegram;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Traits\Encryptable;
use Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Pinecone;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Data_Object_Collection;
use ReflectionClass;
use WP_Error;

/**
 * Class Config
 *
 * Represents a configuration object for an AI provider or vector DB.
 *
 * @package Limb_Chatbot\Includes\Data_Objects
 * @since 1.0.0
 * @since 1.0.11 Added Telegram support
 */
class Config extends WPDB_Data_Object {

	use Encryptable;

	/**
	 * The fields that can be mass-assigned.
	 *
	 * @since 1.0.0
	 */
	const FILLABLE = array( 'title', 'description', 'params', 'related_to', 'default', 'created_at', 'updated_at' );
	/**
	 * The database table name.
	 *
	 * @since 1.0.0
	 */
	const TABLE_NAME = 'lbaic_configs';

	/**
	 * Config ID.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $id;

	/**
	 * Config title.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $title = '';

	/**
	 * Config description.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $description = '';

	/**
	 * Configuration parameters.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	public array $params = [];

	/**
	 * Fully qualified class name this config is related to.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $related_to = '';

	/**
	 * Whether this is the default config.
	 *
	 * @var bool
	 * @since 1.0.0
	 */
	public bool $default = false;

	/**
	 * Creation timestamp.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $created_at;

	/**
	 * Update timestamp.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $updated_at;

	/**
	 * Config constructor.
	 *
	 * Decrypts sensitive values like `api_key` from params.
	 *
	 * @param mixed $instance Raw database row or associative array.
	 * @since 1.0.0
	 */
	public function __construct( $instance = null ) {
		if ( is_array( $instance ) && ! empty( $instance['params'] ) && $this->isJson( $instance['params'] ) ) {
			$instance['params'] = Helper::maybe_json_decode( $instance['params'] );
			$instance = $this->decrypt_params($instance);
		}
		if ( is_array( $instance ) && ! empty( $instance['related_to'] ) && ! str_contains( $instance['related_to'], 'Limb_Chatbot_PRO' ) ) {
			$instance['related_to'] = str_replace( 'Limb_Chatbot_PRO', 'Limb_Chatbot', $instance['related_to'] );
		}
		parent::__construct( $instance );
	}

	public function mask_params() {
		if ( $this->get_related_to_instance() instanceof Slack ) {
			$this->params['bot_token']      = self::mask( $this->params['bot_token'], 20 );
			$this->params['signing_secret'] = self::mask( $this->params['signing_secret'], 20 );
			$this->params['app_token']      = self::mask( $this->params['app_token'], 20 );
		} elseif ( $this->get_related_to_instance() instanceof Telegram ) {
			$this->params['bot_token'] = self::mask( $this->params['bot_token'], 20 );
		} else {
			$this->params['api_key'] = self::mask( $this->params['api_key'], 20 );
		}
		return $this;
	}

	public function encrypt_params() {
		if ( $this->get_related_to_instance() instanceof Slack ) {
			$this->params['bot_token']      = self::encrypt( $this->params['bot_token'] );
			$this->params['signing_secret'] = self::encrypt( $this->params['signing_secret'] );
			$this->params['app_token']      = self::encrypt( $this->params['app_token'] );
		} elseif ( $this->get_related_to_instance() instanceof Telegram ) {
			$this->params['bot_token']      = self::encrypt( $this->params['bot_token'] );
		} else {
			$this->params['api_key'] = self::encrypt( $this->params['api_key'] );
		}
		$this->data['params'] = $this->params;
		return $this;
	}

	public function decrypt_params( $instance) {
		if ( ! empty( $instance['params']['signing_secret'] ) ) {
			// Slack has signing_secret
			$instance['params']['bot_token']      = $this->decrypt( $instance['params']['bot_token'] );
			$instance['params']['signing_secret'] = $this->decrypt( $instance['params']['signing_secret'] );
			$instance['params']['app_token']      = $this->decrypt( $instance['params']['app_token'] );
		} elseif ( ! empty( $instance['params']['bot_token'] ) ) {
			$instance['params']['bot_token']      = $this->decrypt( $instance['params']['bot_token'] );
		} elseif ( ! empty( $instance['params']['api_key'] ) ) {
			$instance['params']['api_key'] = $this->decrypt( $instance['params']['api_key'] );
		}

		return $instance;
	}

	/**
	 * Get the instance of the related class.
	 *
	 * @return Config_Dependent_Interface|WP_Error
	 * @since 1.0.0
	 */
	public function get_related_to_instance() {
		$class_name = $this->get_related_to();

		return class_exists( $class_name ) ? new $class_name() : new WP_Error();
	}

	/**
	 * Get the related class name.
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	public function get_related_to() {
		return $this->related_to;
	}

	/**
	 * Set the related class name.
	 *
	 * @param mixed $related_to
	 * @since 1.0.0
	 */
	public function set_related_to( $related_to ): void {
		$this->related_to = $related_to;
	}

	/**
	 * Get the config title.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_title() {
		return $this->title;
	}

	/**
	 * Set the config title.
	 *
	 * @param string $title
	 * @since 1.0.0
	 */
	public function set_title( $title ): void {
		$this->title = $title;
	}

	/**
	 * Get the config description.
	 *
	 * @return string|null
	 * @since 1.0.0
	 */
	public function get_description() {
		return $this->description;
	}

	/**
	 * Set the config description.
	 *
	 * @param string|null $description
	 * @since 1.0.0
	 */
	public function set_description( $description ): void {
		$this->description = $description;
	}

	/**
	 * Get the configuration parameters.
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public function get_params(): array {
		return $this->params;
	}

	/**
	 * Check if this is the default config.
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function get_default() {
		return $this->default;
	}

	/**
	 * Set whether this is the default config.
	 *
	 * @param bool $default
	 * @since 1.0.0
	 */
	public function set_default( $default ): void {
		$this->default = $default;
	}

	/**
	 * Get creation timestamp.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_created_at() {
		return $this->created_at;
	}

	/**
	 * Set creation timestamp.
	 *
	 * @param string $created_at
	 * @since 1.0.0
	 */
	public function set_created_at( $created_at ): void {
		$this->created_at = $created_at;
	}

	/**
	 * Get update timestamp.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_updated_at() {
		return $this->updated_at;
	}

	/**
	 * Set update timestamp.
	 *
	 * @param string $updated_at
	 * @since 1.0.0
	 */
	public function set_updated_at( $updated_at ): void {
		$this->updated_at = $updated_at;
	}

	/**
	 * Get the static mapping of config-dependent class instances.
	 *
	 * @param int $id Config-dependent ID.
	 * @return string|null Fully qualified class name or null if not found.
	 * @since 1.0.0
	 * @since 1.0.9 Added Claude
	 * @since 1.0.12 Added Grok
	 * @since 1.0.15 Added OpenRouter
	 */
	public static function get_config_dependant_instances( $id ) {
		$classes = [ Open_Ai::class, Gemini::class, Claude::class, Deep_Seek::class, Grok::class, Open_Router::class, Pinecone::class, Slack::class, Telegram::class ];
		foreach ( $classes as $item ) {
			if ( $item::$id == $id ) {
				$found = $item;
				break;
			}
		}

		return $found ?? null;
	}

	public static function where( $where, ...$args ): Data_Object_Collection {
		if ( ! empty( $where['related_to'] ) ) {
			if ( str_contains( $where['related_to'], 'Limb_Chatbot_PRO' ) ) {
				$where['related_to'] = str_replace( 'Limb_Chatbot_PRO', 'Limb_Chatbot', $where['related_to'] );
			}
		}

		return parent::where( $where, ...$args );
	}

	public static function create( $data ) {
		if ( ! empty( $data['related_to'] ) ) {
			if ( str_contains( $data['related_to'], 'Limb_Chatbot_PRO' ) ) {
				$data['related_to'] = str_replace( 'Limb_Chatbot_PRO', 'Limb_Chatbot', $data['related_to'] );
			}
		}

		return parent::create( $data );
	}

	public static function update( $where, $data ) {
		if ( ! empty( $where['related_to'] ) ) {
			if ( str_contains( $where['related_to'], 'Limb_Chatbot_PRO' ) ) {
				$where['related_to'] = str_replace( 'Limb_Chatbot_PRO', 'Limb_Chatbot', $where['related_to'] );
			}
		}

		return parent::update( $where, $data );
	}

	public function save() {
		$for_db = $data = $this->to_internal_array();
		array_walk( $for_db, function ( &$value, $key ) {
			if ( in_array( $key, static::FILLABLE ) && ( is_array( $value ) || is_object( $value ) || $value instanceof Collection ) ) {
				$value = json_encode( $value, JSON_PRESERVE_ZERO_FRACTION | JSON_UNESCAPED_UNICODE );
			}
		} );
		if ( ! empty( $for_db['related_to'] ) && is_string( $for_db['related_to'] ) ) {
			if ( str_contains( $for_db['related_to'], 'Limb_Chatbot_PRO' ) ) {
				$for_db['related_to'] = str_replace( 'Limb_Chatbot_PRO', 'Limb_Chatbot', $for_db['related_to'] );
			}
		}
		if ( empty( $this->id ) ) {
			$db_data = static::get_db_strategy()->create( $for_db, static::TABLE_NAME, static::FILLABLE );
		} else {
			$db_data = static::get_db_strategy()->update( [ 'id' => $this->id ], $for_db, static::TABLE_NAME, static::FILLABLE );
		}
		unset( $for_db );
		foreach ( $db_data as $key => $value ) {
			if ( ! array_key_exists( $key, $data ) || $data[ $key ] === null ) {
				$data[ $key ] = $value;
			}
		}
		$this->set_data( $data );
		$this->hydrate();

		return $this;
	}
}