<?php

declare( strict_types=1 );

namespace StrykeWP\Core;

/**
 * Handles one-time migration from individual STRYKE plugins to the master plugin.
 *
 * Runs automatically on the first boot after activation. Detects existing data
 * from the four individual plugins and:
 *
 *  1. Enables the corresponding module.
 *  2. Migrates option values to the new namespace where the option name changed.
 *  3. Schedules cron events that on_enable() would normally set up.
 *
 * Safe to call on every boot — guarded by the `migration_v1_done` flag.
 *
 * Options preserved as-is (no rename needed — modules read them directly):
 *   stryke_hub_api_key_hash    (Connector/Auth)
 *   stryke_backup_options      (Backup/BackupSettings)
 *
 * Options migrated (old name → new name):
 *   stryke_wip_active           → stryke_wp_module_wip_mode_active
 *   stryke_wip_allowed_pages_ids → stryke_wp_module_wip_mode_allowed_pages
 *
 * @package StrykeWP\Core
 */
final class Migrator {

	/**
	 * Transient key for the migration success notice.
	 * Cleared after display in the STRYKE dashboard overview.
	 */
	public const TRANSIENT_MIGRATION_NOTICE = 'stryke_wp_migration_notice';

	/**
	 * Individual plugin slugs for conflict detection.
	 * Keys match module IDs; values are the plugin file relative to plugins dir.
	 *
	 * @var array<string, string>
	 */
	private const INDIVIDUAL_PLUGINS = [
		'connector' => 'stryke-connector/stryke-hub-connector.php',
		'branding'  => 'stryke-branding/stryke-marketing-branding.php',
		'backup'    => 'stryke-backup/stryke-backup.php',
		'wip_mode'  => 'stryke-wip/stryke-wip.php',
	];

	// -----------------------------------------------------------------------
	// Public API
	// -----------------------------------------------------------------------

	/**
	 * Runs the one-time migration if it hasn't been performed yet.
	 *
	 * Safe to call on every boot — exits immediately after the first run
	 * via the `migration_v1_done` option flag.
	 *
	 * @param OptionManager $options Shared option manager.
	 * @param Logger        $logger  Shared logger.
	 * @return void
	 */
	public static function maybe_run( OptionManager $options, Logger $logger ): void {
		// --- V1 migration (all four modules) ---
		if ( ! $options->get( 'migration_v1_done' ) ) {
			$migrated = [];

			if ( self::migrate_connector( $options ) ) {
				$migrated[] = 'connector';
			}

			if ( self::migrate_branding( $options ) ) {
				$migrated[] = 'branding';
			}

			if ( self::migrate_backup( $options ) ) {
				$migrated[] = 'backup';
			}

			if ( self::migrate_wip( $options ) ) {
				$migrated[] = 'wip_mode';
			}

			// Store results so the dashboard can display a one-time success notice.
			if ( ! empty( $migrated ) ) {
				set_transient( self::TRANSIENT_MIGRATION_NOTICE, $migrated, WEEK_IN_SECONDS );
				$logger->info( 'Legacy plugin data migrated.', [ 'modules' => $migrated ] );
			}

			// Mark migration as complete — no further runs needed.
			$options->set( 'migration_v1_done', true );
		}

		// --- V1.1 patch: branding migration was missing from original v1 release ---
		// Runs independently so existing installs that already completed v1 still
		// get branding auto-enabled if the legacy plugin was active.
		if ( ! $options->get( 'migration_v1_branding_done' ) ) {
			if ( self::migrate_branding( $options ) ) {
				$logger->info( 'Branding module auto-enabled (v1.1 migration patch).' );
			}
			$options->set( 'migration_v1_branding_done', true );
		}
	}

	/**
	 * Returns a list of individual STRYKE plugins that are still active alongside
	 * the master plugin. These cause functional overlap and should be deactivated.
	 *
	 * @return array<string, string> [ module_id => plugin_slug ] for each conflict.
	 */
	public static function get_conflicts(): array {
		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$conflicts = [];

		foreach ( self::INDIVIDUAL_PLUGINS as $module_id => $plugin_slug ) {
			if ( is_plugin_active( $plugin_slug ) ) {
				$conflicts[ $module_id ] = $plugin_slug;
			}
		}

		return $conflicts;
	}

	// -----------------------------------------------------------------------
	// Per-module migration helpers
	// -----------------------------------------------------------------------

	/**
	 * Migrates the STRYKE Marketing Branding plugin.
	 *
	 * Detection: the old branding plugin is currently active, OR any user already
	 * has the `stryke-dark` admin color scheme (set by the old branding plugin).
	 * Data migration: none required — Branding uses user_meta only.
	 *
	 * @param OptionManager $options Shared option manager.
	 * @return bool True if migration ran, false if skipped.
	 */
	private static function migrate_branding( OptionManager $options ): bool {
		if ( $options->is_module_enabled( 'branding' ) ) {
			return false;
		}

		// Check if the old branding plugin is still active.
		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$old_plugin_active = is_plugin_active( self::INDIVIDUAL_PLUGINS['branding'] );

		if ( ! $old_plugin_active ) {
			// Fallback: check if any user has the STRYKE dark color scheme,
			// which means the old branding plugin was active at some point.
			$users_with_stryke = get_users( [
				'meta_key'   => 'admin_color',
				'meta_value' => 'stryke-dark',
				'number'     => 1,
				'fields'     => 'ID',
			] );

			if ( empty( $users_with_stryke ) ) {
				return false;
			}
		}

		$options->set_module_enabled( 'branding', true );

		return true;
	}

	/**
	 * Migrates the Hub Connector plugin.
	 *
	 * Detection: the `stryke_hub_api_key_hash` option exists.
	 * Data migration: none required — Auth reads the option by its original name.
	 * Cron: schedules the daily log-pruning hook if not already scheduled.
	 *
	 * @param OptionManager $options Shared option manager.
	 * @return bool True if migration ran, false if skipped.
	 */
	private static function migrate_connector( OptionManager $options ): bool {
		// Skip if already enabled (user may have enabled it manually before migration ran).
		if ( $options->is_module_enabled( 'connector' ) ) {
			return false;
		}

		// No legacy API key → connector was never used here.
		if ( ! get_option( 'stryke_hub_api_key_hash' ) ) {
			return false;
		}

		$options->set_module_enabled( 'connector', true );

		// The DB table is already created by SchemaManager::create_all() on activation.
		// Schedule the log-pruning cron if it isn't already running.
		if ( ! wp_next_scheduled( 'stryke_hub_prune_logs' ) ) {
			wp_schedule_event( time(), 'daily', 'stryke_hub_prune_logs' );
		}

		return true;
	}

	/**
	 * Migrates the STRYKE Backup plugin.
	 *
	 * Detection: the `stryke_backup_options` option exists and is a non-empty array.
	 * Data migration: none required — BackupSettings reads the option by its original name.
	 * Cron: schedules the backup event based on the stored schedule settings.
	 *
	 * @param OptionManager $options Shared option manager.
	 * @return bool True if migration ran, false if skipped.
	 */
	private static function migrate_backup( OptionManager $options ): bool {
		if ( $options->is_module_enabled( 'backup' ) ) {
			return false;
		}

		$backup_opts = get_option( 'stryke_backup_options' );

		if ( ! is_array( $backup_opts ) || empty( $backup_opts ) ) {
			return false;
		}

		$options->set_module_enabled( 'backup', true );

		// Schedule the backup cron using the same logic as BackupModule::on_enable().
		if ( ! wp_next_scheduled( 'stryke_scheduled_backup_event' ) ) {
			$time_parts = explode( ':', $backup_opts['schedule_time'] ?? '03:00' );
			$hour       = (int) ( $time_parts[0] ?? 3 );
			$minute     = (int) ( $time_parts[1] ?? 0 );

			$tz         = wp_timezone();
			$now        = new \DateTimeImmutable( 'now', $tz );
			$target     = $now->setTime( $hour, $minute, 0 );
			if ( $target <= $now ) {
				$target = $target->modify( '+1 day' );
			}
			$start_time = $target->getTimestamp();

			$schedule = match ( $backup_opts['schedule_frequency'] ?? 'daily' ) {
				'weekly'  => 'stryke_weekly',
				'monthly' => 'stryke_monthly',
				default   => 'daily',
			};

			wp_schedule_event( $start_time, $schedule, 'stryke_scheduled_backup_event' );
		}

		return true;
	}

	/**
	 * Migrates the STRYKE WIP Mode plugin.
	 *
	 * Detection: the `stryke_wip_active` option exists (even if set to 'inactive').
	 * Data migration: renames options to the stryke_wp_ namespace.
	 *   stryke_wip_active           → stryke_wp_module_wip_mode_active (bool)
	 *   stryke_wip_allowed_pages_ids → stryke_wp_module_wip_mode_allowed_pages (int[])
	 *
	 * @param OptionManager $options Shared option manager.
	 * @return bool True if migration ran, false if skipped.
	 */
	private static function migrate_wip( OptionManager $options ): bool {
		if ( $options->is_module_enabled( 'wip_mode' ) ) {
			return false;
		}

		$legacy_active = get_option( 'stryke_wip_active', null );

		if ( null === $legacy_active ) {
			return false;
		}

		$options->set_module_enabled( 'wip_mode', true );

		// Migrate active status: legacy stores 'active' / 'inactive' string; new stores bool.
		$options->module_set( 'wip_mode', 'active', 'active' === $legacy_active );

		// Migrate allowed-pages list.
		$legacy_pages = get_option( 'stryke_wip_allowed_pages_ids', null );
		$options->module_set(
			'wip_mode',
			'allowed_pages',
			( null !== $legacy_pages && is_array( $legacy_pages ) )
				? array_values( array_map( 'absint', $legacy_pages ) )
				: []
		);

		return true;
	}
}
