<?php
/**
 * Shipping mapping migration.
 *
 * @package Simplyin\Simplyin_W_Plugin\Admin
 */

namespace Simplyin\Simplyin_W_Plugin\Admin;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Mapping;

/**
 * One-time migration for shipping mapping options.
 *
 * Copies existing global shipping mapping options:
 * - simply_{method_id}_mapping
 *
 * Into per-zone options:
 * - simply_{method_id}_zone_{zone_id}_mapping
 *
 * The target zone is resolved by checking which WooCommerce shipping zone
 * contains the mapped shipping rate ID.
 */
class Shipping_Mapping_Migration {

	/**
	 * Migration completion option key.
	 *
	 * @var string
	 */
	private const MIGRATION_KEY = 'SimplyInShippingMappingZoneMigrationDone';

	/**
	 * Mapping cache option key.
	 *
	 * @var string
	 */
	private const CACHE_KEY = 'SimplyInShippingMappingCache';

	/**
	 * Rollback trigger query arg.
	 *
	 * @var string
	 */
	private const ROLLBACK_QUERY_ARG = 'simplyin_rollback_mapping_migration';

	/**
	 * Initialize hooks.
	 *
	 * @return void
	 */
	public function init(): void {
		add_action( 'admin_init', array( $this, 'run' ) );
		add_action( 'admin_init', array( $this, 'maybe_rollback' ) );
	}

	/**
	 * Run migration once.
	 *
	 * @return void
	 */
	public function run(): void {
		if ( false !== get_option( self::MIGRATION_KEY, false ) ) {
			return;
		}

		$mapping          = new Mapping();
		$simplyin_methods = $mapping->get_available_simplyin_methods();

		if ( empty( $simplyin_methods ) ) {
			return;
		}

		$rate_to_zone_map = $this->get_rate_to_zone_map();

		foreach ( $simplyin_methods as $method ) {
			$method_id = $method->get_id();
			$old_key   = sprintf( 'simply_%s_mapping', $method_id );
			$old_value = get_option( $old_key, null );

			if ( null === $old_value || false === $old_value || 'none' === $old_value ) {
				continue;
			}

			$target_zone_id = isset( $rate_to_zone_map[ $old_value ] )
				? (int) $rate_to_zone_map[ $old_value ]
				: 0;

			$zone_key = sprintf(
				'simply_%s_zone_%d_mapping',
				$method_id,
				$target_zone_id
			);

			if ( null === get_option( $zone_key, null ) ) {
				update_option( $zone_key, $old_value );
			}
		}

		$this->invalidate_mapping_cache();
		update_option( self::MIGRATION_KEY, 1 );
	}

	/**
	 * Build a map of WooCommerce shipping rate IDs to zone IDs.
	 *
	 * Example:
	 * [
	 *     'flat_rate:3' => 1,
	 *     'free_shipping:2' => 4,
	 *     'local_pickup:1' => 0,
	 * ]
	 *
	 * Named zones are checked first. Rest of World is added as zone 0.
	 *
	 * @return array<string, int>
	 */
	/**
	 * Build a map of WooCommerce shipping rate IDs and their legacy aliases to zone IDs.
	 *
	 * @return array<string, int>
	 */
	private function get_rate_to_zone_map(): array {
		$rate_to_zone_map = array();
		$zones            = \WC_Shipping_Zones::get_zones();

		foreach ( $zones as $zone_data ) {
			if ( empty( $zone_data['zone_id'] ) ) {
				continue;
			}

			$zone_id = (int) $zone_data['zone_id'];

			if ( empty( $zone_data['shipping_methods'] ) || ! is_array( $zone_data['shipping_methods'] ) ) {
				continue;
			}

			foreach ( $zone_data['shipping_methods'] as $shipping_method ) {
				if ( ! is_object( $shipping_method ) || ! method_exists( $shipping_method, 'get_rate_id' ) ) {
					continue;
				}

				$rate_id = (string) $shipping_method->get_rate_id();

				if ( '' === $rate_id ) {
					continue;
				}

				$rate_to_zone_map[ $rate_id ] = $zone_id;

				$legacy_rate_id                      = str_replace( ':', '_', $rate_id );
				$rate_to_zone_map[ $legacy_rate_id ] = $zone_id;
			}
		}

		$rest_of_world_zone = \WC_Shipping_Zones::get_zone( 0 );

		if ( $rest_of_world_zone instanceof \WC_Shipping_Zone ) {
			$shipping_methods = $rest_of_world_zone->get_shipping_methods();

			foreach ( $shipping_methods as $shipping_method ) {
				if ( ! is_object( $shipping_method ) || ! method_exists( $shipping_method, 'get_rate_id' ) ) {
					continue;
				}

				$rate_id = (string) $shipping_method->get_rate_id();

				if ( '' === $rate_id ) {
					continue;
				}

				$rate_to_zone_map[ $rate_id ] = 0;

				$legacy_rate_id                      = str_replace( ':', '_', $rate_id );
				$rate_to_zone_map[ $legacy_rate_id ] = 0;
			}
		}

		return $rate_to_zone_map;
	}

	/**
	 * DEV ONLY.
	 *
	 * Reverses the migration so it can be re-run for local testing.
	 * Trigger via:
	 * /wp-admin/?simplyin_rollback_mapping_migration=1
	 *
	 * @return void
	 */
	public function maybe_rollback(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- DEV-only tool, access guarded by current_user_can() below.
		if ( ! isset( $_GET[ self::ROLLBACK_QUERY_ARG ] ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- DEV-only tool, access guarded by current_user_can() below.
		$rollback_flag = sanitize_text_field( wp_unslash( $_GET[ self::ROLLBACK_QUERY_ARG ] ) );

		if ( empty( $rollback_flag ) ) {
			return;
		}

		if ( false === current_user_can( 'manage_options' ) ) {
			return;
		}

		$this->rollback();

		wp_safe_redirect(
			add_query_arg(
				array(
					'page'                   => 'simplyin-settings',
					'simplyin_rollback_done' => '1',
				),
				admin_url( 'admin.php' )
			)
		);

		exit;
	}

	/**
	 * Roll back migrated per-zone mapping options.
	 *
	 * @return void
	 */
	public function rollback(): void {
		$mapping          = new Mapping();
		$simplyin_methods = $mapping->get_available_simplyin_methods();
		$shipping_zones   = $mapping->get_shipping_zones();

		foreach ( $simplyin_methods as $method ) {
			$method_id = $method->get_id();

			foreach ( $shipping_zones as $zone ) {
				delete_option(
					sprintf(
						'simply_%s_zone_%d_mapping',
						$method_id,
						$zone->get_id()
					)
				);
			}

			delete_option(
				sprintf(
					'simply_%s_zone_%d_mapping',
					$method_id,
					0
				)
			);
		}

		delete_option( self::MIGRATION_KEY );
		$this->invalidate_mapping_cache();
	}

	/**
	 * Invalidate cached shipping mapping data.
	 *
	 * @return void
	 */
	private function invalidate_mapping_cache(): void {
		update_option( self::CACHE_KEY, array() );
	}
}
