<?php
/**
 * Database upgrade manager.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Database;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Awesomemotive\Rewardswp\Abstracts\AbstractDatabaseUpgrade;
use Awesomemotive\Rewardswp\Abstracts\AbstractDatabaseTable;
use DI\Container;
use Exception;

/**
 * UpgradeManager class for managing database upgrades.
 *
 * @since 1.0.0
 */
class UpgradeManager {
	/**
	 * Option name for storing the Lite database version.
	 *
	 * @since 1.0.1
	 * @var string
	 */
	const LITE_DB_VERSION_OPTION = 'rewardswp_lite_db_version';

	/**
	 * Option name for storing the Pro database version.
	 *
	 * @since 1.0.1
	 * @var string
	 */
	const PRO_DB_VERSION_OPTION = 'rewardswp_pro_db_version';

	/**
	 * DI Container instance.
	 *
	 * @since 1.0.0
	 * @var Container
	 */
	private Container $container;

	/**
	 * List of table classes to be executed for initial table creation.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private array $tables = [];

	/**
	 * Versioned upgrade classes organized by plan and version.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private array $upgrades = [];

	/**
	 * Current plan's option name for version tracking.
	 *
	 * @since 1.0.1
	 * @var string
	 */
	private string $current_plan_option = self::LITE_DB_VERSION_OPTION;

	/**
	 * Constructor to inject the DI container.
	 *
	 * @since 1.0.0
	 *
	 * @param Container $container DI Container instance.
	 * @throws Exception In case it fails to load from the container.
	 */
	public function __construct( Container $container ) {
		$this->container = $container;
		$this->tables    = $this->container->get( 'database_table_classes' );
		$this->upgrades  = $this->container->get( 'plugin_upgrades' );
	}

	/**
	 * Set the current plan option for version tracking.
	 *
	 * @since 1.0.1
	 *
	 * @param string $option_name The option name to use for version tracking.
	 * @return void
	 */
	private function set_plan_option( string $option_name ): void {
		$this->current_plan_option = $option_name;
	}

	/**
	 * Get the current database version for the active plan.
	 *
	 * @since 1.0.0
	 *
	 * @return string Current database version.
	 */
	public function get_current_version(): string {
		return get_option( $this->current_plan_option, '1.0.0' );
	}

	/**
	 * Update the database version for the current plan.
	 *
	 * @since 1.0.0
	 *
	 * @param string $version Version to update to.
	 * @return void
	 */
	private function update_version( string $version ): void {
		update_option( $this->current_plan_option, $version, false );
	}

	/**
	 * Check if database needs upgrade for the current plan.
	 *
	 * @since 1.0.0
	 *
	 * @param string $new_version Version to check against.
	 * @return bool Whether an upgrade is needed.
	 */
	public function needs_upgrade( string $new_version ): bool {
		return version_compare( $this->get_current_version(), $new_version, '<' );
	}

	/**
	 * Run all pending upgrades up to the specified version for the current plan.
	 *
	 * @since 1.0.0
	 *
	 * @param string $target_version Version to upgrade to.
	 * @param string $plan           The plan to run upgrades for ('lite' or 'pro').
	 *
	 * @return void
	 * @throws Exception If upgrade fails.
	 */
	public function upgrade_to( string $target_version, string $plan ): void {
		$current_version = $this->get_current_version();

		// Get all versions that need to be upgraded for the current plan.
		$versions_to_upgrade = array_filter(
			array_keys( $this->upgrades[ $plan ] ?? [] ),
			function ( $version ) use ( $current_version, $target_version ) {
				return version_compare( $version, $current_version, '>' ) &&
					version_compare( $version, $target_version, '<=' );
			}
		);

		// Sort versions.
		usort( $versions_to_upgrade, 'version_compare' );

		// Run upgrades for each version.
		foreach ( $versions_to_upgrade as $version ) {
			if ( ! isset( $this->upgrades[ $plan ][ $version ] ) ) {
				continue;
			}

			foreach ( $this->upgrades[ $plan ][ $version ] as $upgrade_class ) {
				/**
				 * Database upgrade instance.
				 *
				 * @var AbstractDatabaseUpgrade $upgrade
				 */
				$upgrade = $this->container->get( $upgrade_class );
				$upgrade->up();
			}

			$this->update_version( $version );
		}

		// Finally update to target version.
		$this->update_version( $target_version );
	}

	/**
	 * Run any pending upgrades based on the current plugin's version.
	 *
	 * @since 1.0.1
	 *
	 * @return void
	 * @throws Exception If upgrade fails.
	 */
	public function run_upgrades(): void {
		$plugin_info = $this->container->get( 'plugin_info' );

		// Always run Lite upgrades first.
		if ( isset( $this->upgrades['lite'] ) ) {
			$this->set_plan_option( self::LITE_DB_VERSION_OPTION );
			if ( $this->needs_upgrade( $plugin_info['version'] ) ) {
				$this->upgrade_to( $plugin_info['version'], 'lite' );
			}
		}

		// If Pro upgrades exist, run those next.
		if ( isset( $this->upgrades['pro'] ) ) {
			$this->set_plan_option( self::PRO_DB_VERSION_OPTION );
			if ( $this->needs_upgrade( $plugin_info['version'] ) ) {
				$this->upgrade_to( $plugin_info['version'], 'pro' );
			}
		}
	}

	/**
	 * Run all the initial table upgrades.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 * @throws Exception If table creation fails.
	 */
	public function create_tables(): void {
		foreach ( $this->tables as $table_class ) {
			/**
			 * Database table instance.
			 *
			 * @var AbstractDatabaseTable $table
			 */
			$this->container->get( $table_class )->create_or_update_table();
		}
	}
}
