<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Interfaces\Migration_Interface;

use function Limb_Chatbot;

/**
 * Service for managing database migrations.
 *
 * Handles version tracking and executes necessary migrations when the plugin
 * is updated. Always runs Install class on version changes to ensure database
 * schema is up-to-date, then runs any version-specific migrations.
 *
 * @since 1.0.0
 */
class Migration_Service {

	/**
	 * Option name for storing database version.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	const DB_VERSION_OPTION = 'lbaic.plugin.current_version';
	/**
	 * Current plugin version from code.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $current_version;
	/**
	 * Database version stored in WordPress options.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $db_version;
	/**
	 * Directory where migration classes are stored.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $migrations_dir;

	/**
	 * Collection of migration results.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	protected $migration_results = array();

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->current_version = Limb_Chatbot()->get_version();
		$this->db_version      = get_option( self::DB_VERSION_OPTION, '1.0.3' );
		$this->migrations_dir  = Limb_Chatbot()->get_plugin_dir_path() . 'includes/migrations';
	}

	/**
	 * Execute migrations if needed.
	 *
	 * Compares current version with database version and runs Install class
	 * followed by any pending version-specific migrations.
	 *
	 * @return array Migration results including success status and details.
	 * @throws Exception If migration fails.
	 * @since 1.0.0
	 */
	public function migrate(): array {
		$this->migration_results = array(
			'success'         => false,
			'version_changed' => false,
			'from_version'    => $this->db_version,
			'to_version'      => $this->current_version,
			'migrations_run'  => array(),
			'errors'          => array(),
		);

		try {
			// Check if migration is needed
			if ( ! $this->needs_migration() ) {
				$this->migration_results['success'] = true;
				$this->migration_results['message'] = 'Database is up to date. No migration needed.';

				return $this->migration_results;
			}

			$this->migration_results['version_changed'] = true;

			// Run version-specific migrations
			$this->run_pending_migrations();

			// Update database version
			$this->update_db_version();

			$this->migration_results['success'] = true;
			$this->migration_results['message'] = sprintf(
				'Successfully migrated to %s.',
				$this->current_version
			);

			return $this->migration_results;
		} catch ( \Exception $e ) {
			$this->migration_results['errors'][] = $e->getMessage();
			$this->log_error( $e );

			throw new Exception(
				Error_Codes::TECHNICAL_ERROR,
				sprintf( 'Migration failed: %s', $e->getMessage() ),
				$this->migration_results,
				500
			);
		}
	}

	/**
	 * Check if migration is needed.
	 *
	 * @return bool True if migration is needed, false otherwise.
	 * @since 1.0.0
	 */
	protected function needs_migration(): bool {
		return version_compare( $this->db_version, $this->current_version, '<' );
	}

	/**
	 * Log migration activity.
	 *
	 * @param  string  $message  Log message.
	 * @param  string  $level  Log level (info, warning, error).
	 *
	 * @return void
	 * @since 1.0.0
	 */
	protected function log( string $message, string $level = 'info' ): void {
		Helper::log( sprintf( '[LBAIC Migration Service] %s: %s', strtoupper( $level ), $message ) );
	}

	/**
	 * Run pending version-specific migrations.
	 *
	 * Loads and executes migration classes for versions between the database
	 * version and current version.
	 *
	 * @return void
	 * @throws Exception If a migration fails.
	 * @since 1.0.0
	 */
	protected function run_pending_migrations(): void {
		$migrations = $this->discover_migrations();

		if ( empty( $migrations ) ) {
			$this->log( 'No version-specific migrations found' );

			return;
		}

		foreach ( $migrations as $migration_class ) {
			$this->execute_migration( $migration_class );
		}
	}

	/**
	 * Discover available migration classes.
	 *
	 * Scans the migrations directory and returns migration classes that
	 * need to be executed based on version comparison.
	 *
	 * @return array Array of migration class names.
	 * @since 1.0.0
	 */
	protected function discover_migrations(): array {
		$migrations = array();

		if ( ! is_dir( $this->migrations_dir ) ) {
			$this->log( 'Migrations directory does not exist: ' . $this->migrations_dir, 'warning' );

			return $migrations;
		}

		$files = glob( $this->migrations_dir . '/class-lbaic-migration-*.php' );

		if ( empty( $files ) ) {
			return $migrations;
		}

		foreach ( $files as $file ) {
			require_once $file;

			$class_name = $this->get_class_name_from_file( $file );

			if ( ! class_exists( $class_name ) ) {
				$this->log( "Migration class not found: {$class_name}", 'warning' );
				continue;
			}

			$migration = new $class_name();

			if ( ! $migration instanceof Migration_Interface ) {
				$this->log( "Invalid migration class (must implement Migration_Interface): {$class_name}", 'warning' );
				continue;
			}

			$target_version = $migration->get_version();

			// Only include migrations for versions between db_version and current_version
			if (
				version_compare( $target_version, $this->db_version, '>' ) &&
				version_compare( $target_version, $this->current_version, '<=' )
			) {
				$migrations[ $target_version ] = $class_name;
			}
		}

		// Sort migrations by version
		uksort( $migrations, 'version_compare' );

		return $migrations;
	}

	/**
	 * Get fully qualified class name from migration file.
	 *
	 * @param  string  $file  File path.
	 *
	 * @return string Fully qualified class name.
	 * @since 1.0.0
	 */
	protected function get_class_name_from_file( string $file ): string {
		$filename = basename( $file, '.php' );
		// Convert class-lbaic-migration-1-0-4.php to Migration_1_0_4
		$class_name = str_replace( 'class-lbaic-', '', $filename );
		$class_name = str_replace( '-', '_', $class_name );
		$class_name = implode( '_', array_map( 'ucfirst', explode( '_', $class_name ) ) );

		return "Limb_Chatbot\\Includes\\Migrations\\{$class_name}";
	}

	/**
	 * Execute a single migration.
	 *
	 * @param  string  $class_name  Fully qualified migration class name.
	 *
	 * @return void
	 * @throws Exception|\Exception If migration execution fails.
	 * @since 1.0.0
	 */
	protected function execute_migration( string $class_name ): void {
		try {
			$migration = new $class_name();

			$this->log( sprintf(
				'Executing migration: %s (v%s) - %s',
				$class_name,
				$migration->get_version(),
				$migration->get_description()
			) );

			$success = $migration->up();

			if ( ! $success ) {
				throw new \Exception( "Migration returned false: {$class_name}" );
			}

			$this->migration_results['migrations_run'][] = array(
				'class'       => $class_name,
				'version'     => $migration->get_version(),
				'description' => $migration->get_description(),
				'success'     => true,
			);

			$this->log( sprintf(
				'Migration completed successfully: %s (v%s)',
				$class_name,
				$migration->get_version()
			) );
		} catch ( \Exception $e ) {
			$error_msg = sprintf(
				'Migration failed: %s (v%s) - %s',
				$class_name,
				$migration->get_version() ?? 'unknown',
				$e->getMessage()
			);

			$this->migration_results['errors'][] = $error_msg;
			$this->log( $error_msg, 'error' );

			throw new \Exception( $error_msg, 0, $e );
		}
	}

	/**
	 * Update the database version in WordPress options.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	protected function update_db_version(): void {
		update_option( self::DB_VERSION_OPTION, $this->current_version );
		$this->log( "Database version updated to {$this->current_version}" );
	}

	/**
	 * Log error with full exception details.
	 *
	 * @param  \Exception  $exception  Exception to log.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	protected function log_error( \Exception $exception ): void {
		$this->log(
			sprintf(
				'Exception: %s in %s:%d',
				$exception->getMessage(),
				$exception->getFile(),
				$exception->getLine()
			),
			'error'
		);

		if ( method_exists( $exception, 'getTraceAsString' ) ) {
			$this->log( $exception->getTraceAsString(), 'error' );
		}
	}

	/**
	 * Get current database version.
	 *
	 * @return string Database version.
	 * @since 1.0.0
	 */
	public function get_db_version(): string {
		return $this->db_version;
	}

	/**
	 * Get current plugin version.
	 *
	 * @return string Current plugin version.
	 * @since 1.0.0
	 */
	public function get_current_version(): string {
		return $this->current_version;
	}

	/**
	 * Check if database is up to date.
	 *
	 * @return bool True if database is current, false otherwise.
	 * @since 1.0.0
	 */
	public function is_up_to_date(): bool {
		return empty($this->discover_migrations());
	}
}
