<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\AI_Providers\Claude\Claude;
use Limb_Chatbot\Includes\AI_Providers\Claude\Seeders\AI_Models_Seeder as Claude_Models_Seeder;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Deep_Seek;
use Limb_Chatbot\Includes\AI_Providers\Deep_Seek\Seeders\AI_Models_Seeder as Deep_Seek_Models_Seeder;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Gemini;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Seeders\AI_Models_Seeder as Gemini_Models_Seeder;
use Limb_Chatbot\Includes\AI_Providers\Grok\Grok;
use Limb_Chatbot\Includes\AI_Providers\Grok\Seeders\AI_Models_Seeder as Grok_Models_Seeder;
use Limb_Chatbot\Includes\AI_Providers\Open_Ai\Open_Ai;
use Limb_Chatbot\Includes\AI_Providers\Open_Ai\Seeders\AI_Models_Seeder as Open_Ai_Models_Seeder;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Open_Router;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Seeders\AI_Models_Seeder as Open_Router_Models_Seeder;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Chatbot_Meta;
use Limb_Chatbot\Includes\Data_Objects\Setting;

/**
 * Re-seeds AI model catalogs, removes deprecated models, and repairs orphaned model IDs.
 *
 * Shared by versioned migrations that refresh the provider model list.
 *
 * @since 2.0.1
 */
class Ai_Model_Catalog_Refresher {

	/**
	 * @var callable
	 */
	private $logger;

	/**
	 * @var callable
	 */
	private $maybe_add_setting;

	/**
	 * @param callable $logger            function( string $message, string $level = 'info' ): void
	 * @param callable $maybe_add_setting function( string $key, mixed $value ): void
	 */
	public function __construct( callable $logger, callable $maybe_add_setting ) {
		$this->logger            = $logger;
		$this->maybe_add_setting = $maybe_add_setting;
	}

	/**
	 * Refresh AI model catalogs for all providers.
	 *
	 * @param array<string> $deprecated_models Model names to delete after re-seeding.
	 *
	 * @return bool True on success, false on failure.
	 */
	public function run( array $deprecated_models ): bool {
		if ( ! $this->seed_ai_provider_models() ) {
			return false;
		}

		if ( ! $this->delete_deprecated_models( $deprecated_models ) ) {
			return false;
		}

		if ( ! $this->seed_provider_settings() ) {
			return false;
		}

		$this->fix_orphaned_chatbot_model_settings();

		return true;
	}

	/**
	 * @param string $message Log message.
	 * @param string $level   Log level.
	 *
	 * @return void
	 */
	private function log( string $message, string $level = 'info' ): void {
		( $this->logger )( $message, $level );
	}

	/**
	 * @param string $key   Setting key.
	 * @param mixed  $value Setting value.
	 *
	 * @return void
	 */
	private function add_setting_if_missing( string $key, $value ): void {
		( $this->maybe_add_setting )( $key, $value );
	}

	/**
	 * Seed AI models for all providers.
	 *
	 * @return bool True on success, false on failure.
	 */
	private function seed_ai_provider_models(): bool {
		$seeders = [
			'Open AI'     => new Open_Ai_Models_Seeder(),
			'Gemini'      => new Gemini_Models_Seeder(),
			'Claude'      => new Claude_Models_Seeder(),
			'Deep Seek'   => new Deep_Seek_Models_Seeder(),
			'Grok'        => new Grok_Models_Seeder(),
			'Open Router' => new Open_Router_Models_Seeder(),
		];

		foreach ( $seeders as $provider_name => $seeder ) {
			try {
				$this->log( sprintf( 'Seeding %s AI models', $provider_name ) );
				$seeder->run();
				$this->log( sprintf( '%s AI models seeded successfully', $provider_name ) );
			} catch ( \Exception $e ) {
				$this->log( sprintf( 'Error seeding %s models: %s', $provider_name, $e->getMessage() ), 'error' );

				return false;
			}
		}

		return true;
	}

	/**
	 * Seed default settings for all AI providers and fix orphaned provider-level model IDs.
	 *
	 * @return bool True on success, false on failure.
	 */
	private function seed_provider_settings(): bool {
		try {
			$this->log( 'Seeding provider settings from data schemas' );

			$provider_rules = [];
			$providers      = [
				new Open_Ai(),
				new Gemini(),
				new Claude(),
				new Deep_Seek(),
				new Grok(),
				new Open_Router(),
			];

			foreach ( $providers as $ai_provider ) {
				$settings = "Limb_Chatbot\\Includes\\AI_Providers\\{$ai_provider::$name}\\Data_Schemas\\Setting";
				if ( class_exists( $settings ) ) {
					$provider_rules = array_merge( $provider_rules, $settings::rules() );
				}
			}

			$settings_created = 0;
			foreach ( $provider_rules as $key => $rule ) {
				if ( isset( $rule['default'] ) && ! Setting::exists( $key ) ) {
					$default_value = $rule['default'];

					if ( $default_value !== null ) {
						$this->add_setting_if_missing( $key, $default_value );
						++$settings_created;
					}
				}
			}

			if ( $settings_created > 0 ) {
				$this->log( sprintf( 'Created %d provider settings', $settings_created ) );
			} else {
				$this->log( 'All provider settings already exist' );
			}

			$this->fix_orphaned_ai_model_settings( $provider_rules );

			$this->log( 'Provider settings seeded successfully' );

			return true;
		} catch ( \Exception $e ) {
			$this->log( sprintf( 'Error seeding provider settings: %s', $e->getMessage() ), 'error' );

			return false;
		}
	}

	/**
	 * Fix ai_model_id settings that reference deleted or non-existent models.
	 *
	 * @param array $provider_rules Provider setting rules with defaults.
	 *
	 * @return void
	 */
	private function fix_orphaned_ai_model_settings( array $provider_rules ): void {
		foreach ( $provider_rules as $key => $rule ) {
			if ( ! str_ends_with( $key, '.ai_model_id' ) ) {
				continue;
			}

			$new_default = $rule['default'] ?? null;

			foreach ( [ $key, $key . Setting::PREVIEW_POSTFIX ] as $setting_key ) {
				if ( ! Setting::exists( $setting_key ) ) {
					continue;
				}

				$setting = Setting::find( $setting_key );

				if ( ! $setting instanceof Setting ) {
					continue;
				}

				$current_value = $setting->get_value();

				if ( ! $current_value || AI_Model::count( [ 'id' => (int) $current_value ] ) ) {
					continue;
				}

				if ( $new_default !== null ) {
					Setting::update( [ 'key' => $setting_key ], [ 'value' => $new_default ] );
					$this->log( sprintf( 'Updated orphaned setting %s from model ID %s to %s', $setting_key, $current_value, $new_default ) );
				}
			}
		}
	}

	/**
	 * Delete deprecated AI models.
	 *
	 * @param array<string> $deprecated_models Model names to delete.
	 *
	 * @return bool True on success, false on failure.
	 */
	private function delete_deprecated_models( array $deprecated_models ): bool {
		try {
			foreach ( $deprecated_models as $model_name ) {
				$model = AI_Model::find_by_name( $model_name );

				if ( $model instanceof AI_Model ) {
					AI_Model::delete( [ 'name' => $model_name ] );
					$this->log( sprintf( 'Deleted deprecated model: %s', $model_name ) );
				} else {
					$this->log( sprintf( 'Deprecated model not found, skipping: %s', $model_name ) );
				}
			}

			return true;
		} catch ( \Exception $e ) {
			$this->log( sprintf( 'Error deleting deprecated models: %s', $e->getMessage() ), 'error' );

			return false;
		}
	}

	/**
	 * Fix orphaned ai_model_id references for the global default chatbot setting and per-chatbot CPT post meta.
	 *
	 * @return void
	 */
	private function fix_orphaned_chatbot_model_settings(): void {
		$provider_default_model_map = $this->build_provider_default_chatbot_model_map();

		$model_key    = Setting::SETTING_PREFIX . Chatbot::SETTING_PREFIX . 'ai_model_id';
		$provider_key = Setting::SETTING_PREFIX . Chatbot::SETTING_PREFIX . 'ai_provider_id';

		foreach ( [ $model_key, $model_key . Setting::PREVIEW_POSTFIX ] as $setting_key ) {
			if ( ! Setting::exists( $setting_key ) ) {
				continue;
			}
			$setting = Setting::find( $setting_key );
			if ( ! $setting instanceof Setting ) {
				continue;
			}
			$current_model_id = $setting->get_value();
			if ( ! $current_model_id || AI_Model::count( [ 'id' => (int) $current_model_id ] ) ) {
				continue;
			}

			$provider_setting = Setting::find( $provider_key );
			$provider_id      = $provider_setting instanceof Setting ? $provider_setting->get_value() : null;
			$new_default      = $provider_id ? ( $provider_default_model_map[ $provider_id ] ?? null ) : null;

			if ( $new_default !== null ) {
				Setting::update( [ 'key' => $setting_key ], [ 'value' => $new_default ] );
				$this->log(
					sprintf(
						'Updated orphaned global chatbot setting %s from model ID %s to %s',
						$setting_key,
						$current_model_id,
						$new_default
					)
				);
			}
		}

		$chatbots = Chatbot::where( [] );
		if ( $chatbots->is_empty() ) {
			return;
		}

		foreach ( $chatbots->get() as $chatbot ) {
			if ( ! $chatbot instanceof Chatbot || empty( $chatbot->get_id() ) ) {
				continue;
			}

			$ai_model_id = $chatbot->get_ai_model_id();
			if ( ! $ai_model_id || AI_Model::count( [ 'id' => (int) $ai_model_id ] ) ) {
				continue;
			}

			$ai_provider_id = $chatbot->get_ai_provider_id();
			$new_default    = $ai_provider_id ? ( $provider_default_model_map[ $ai_provider_id ] ?? null ) : null;

			if ( $new_default !== null ) {
				Chatbot_Meta::update(
					[ 'meta_key' => '_ai_model_id', 'chatbot_id' => $chatbot->get_id() ],
					[ 'meta_value' => $new_default ]
				);
				$this->log(
					sprintf(
						'Updated orphaned ai_model_id for chatbot #%d from model ID %s to %s',
						$chatbot->get_id(),
						$ai_model_id,
						$new_default
					)
				);
			}
		}
	}

	/**
	 * Build a map of provider ID → default chatbot model ID from each provider's schema.
	 *
	 * @return array<string, int> Map of provider_id → model_id.
	 */
	private function build_provider_default_chatbot_model_map(): array {
		$map       = [];
		$providers = [
			new Open_Ai(),
			new Gemini(),
			new Claude(),
			new Deep_Seek(),
			new Grok(),
			new Open_Router(),
		];

		foreach ( $providers as $ai_provider ) {
			$provider_name  = $ai_provider::$name;
			$provider_id    = $ai_provider::$id;
			$settings_class = 'Limb_Chatbot\\Includes\\AI_Providers\\' . $provider_name . '\\Data_Schemas\\Setting';
			if ( ! class_exists( $settings_class ) ) {
				continue;
			}

			$chatbot_model_key = Setting::SETTING_PREFIX . 'ai_providers.' . $provider_id . '.utilities.chatbot.ai_model_id';
			$rules             = $settings_class::rules();

			if ( isset( $rules[ $chatbot_model_key ]['default'] ) ) {
				$map[ $provider_id ] = $rules[ $chatbot_model_key ]['default'];
			}
		}

		return $map;
	}
}
