<?php
/**
 * Shipping mapping class.
 *
 * @package Simplyin\Simplyin_W_Plugin\Shipping_Mapping
 */

namespace Simplyin\Simplyin_W_Plugin\Shipping_Mapping;

use Simplyin\Simplyin_W_Plugin\Admin\Shipping_Mapping_Options;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Apaczka\Apaczka_Integration_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Door_Delivery_Integration_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Fs\Fs_Integration_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Furgonetka\Furgonetka_Integration_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Inpost_Plugin\Inpost_Plugin_Integration_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Dhl_Polska\Dhl_Polska_Integration_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Integration_Factory_Interface;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Integration_Interface;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Integration\Multi_Integration_Interface;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Simplyin\Simplyin_Method;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Simplyin\Simplyin_Method_Factory;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Simplyin\Simplyin_Method_Interface;
use WC_Shipping_Method;
use WC_Shipping_Zones;

class Mapping {

	/**
	 * @var WC_Shipping_Method[]
	 */
	protected static array $woo_shipping_methods_cache = [];

	/**
	 * @var string[]
	 */
	protected static array $available_providers_cache = [];

	/**
	 * @var Integration_Factory_Interface[]
	 */
	protected static array $integration_factory_cache = [];

	/**
	 * @var Mapping_Item[]
	 */
	private array $mapping_items = [];

	/**
	 * Returns list of available logistic providers.
	 *
	 * @param bool $for_api_request Whether to filter out door_delivery for API requests.
	 *
	 * @return string[]
	 */
	public function get_available_providers( bool $for_api_request = false ): array {
		if ( ! empty( self::$available_providers_cache ) ) {
			return self::$available_providers_cache;
		}

		$providers = [];
		foreach ( $this->get_available_integrations() as $integration ) {
			if ( $integration instanceof Multi_Integration_Interface ) {
				foreach ( $integration->get_logistic_providers() as $provider ) {
					$providers[] = $provider;
				}
			} else {
				$providers[] = $integration->get_logistic_provider();
			}
		}

		self::$available_providers_cache = $providers;
		$providers                       = array_values( array_unique( $providers ) );

		self::$available_providers_cache = $providers;

		if ( $for_api_request ) {
			$key = array_search( 'door_delivery', $providers, true );
			if ( false !== $key ) {
				unset( $providers[ $key ] );
				$providers = array_values( $providers );
			}
		}

		return $providers;
	}


	/**
	 * @return Mapping_Item[]
	 */
	public function build_mapping(): array {
		$result           = [];
		$simplyin_methods = $this->get_available_simplyin_methods();
		$integrations     = $this->get_available_integrations();

		foreach ( $simplyin_methods as $simplyin_method ) {
			$item = new Mapping_Item();
			$item->set_simplyin_method( $simplyin_method );
			foreach ( $integrations as $integration ) {
				if ( $simplyin_method->is_integration_supported( $integration ) ) {

					$item->put_integration( $integration );
				}
			}
			$result[] = $item;

		}

		return $result;
	}

	/**
	 * Returns integrations stored in the options cache.
	 *
	 * @return array
	 */
	public function get_cached_integrations(): array {
		$result = get_option( 'SimplyInShippingIntegrations' );

		return ! $result ? [] : (array) $result;
	}

	/**
	 * Returns the shipping mapping cache from options.
	 *
	 * @return array
	 */
	public function get_mapping_cache(): array {
		return (array) get_option( 'SimplyInShippingMappingCache' );
	}

	/**
	 * Handles checkout by resolving shipping mapping and setting cookies.
	 *
	 * @return void
	 */
	public function handle_checkout(): void {

		if ( WC()->cart ) {
			WC()->cart->calculate_shipping();
			$packages = WC()->cart->get_shipping_packages();
			foreach ( $packages as $package_key => $package ) {
				WC()->session->set( 'shipping_for_package_' . $package_key, null );
			}
		}

		$mapping_options = new Shipping_Mapping_Options( $this );
		$mapping_options->rebuild_mapping();

		$checkout               = new Checkout();
		$available_delivery     = $checkout->get_available_delivery();
		$mapping                = $this->get_mapping_cache();
		$available_integrations = $this->get_cached_integrations();

		// Detect customer's shipping zone for per-zone mapping resolution.
		$customer_zone_id = 0; // 0 = Rest of the World fallback.
		if ( WC()->cart ) {
			$packages = array_values( WC()->cart->get_shipping_packages() );
			if ( ! empty( $packages ) ) {
				$customer_zone    = WC_Shipping_Zones::get_zone_matching_package( $packages[0] );
				$customer_zone_id = $customer_zone ? $customer_zone->get_id() : 0;
			}
		}

		$result                      = [];
		$available_providers_in_cart = [];
		foreach ( $available_integrations as $simplyin_method_id => $integrations ) {
			$simplyin_method = $this->get_simplyin_method( $simplyin_method_id );

			$resolved_key = $this->resolve_mapping_key( $simplyin_method_id, $customer_zone_id, $mapping );

			$actual_key = $resolved_key;
			if ( $resolved_key && method_exists( $available_delivery, 'get_items' ) ) {
				$available_items = $available_delivery->get_items();
				$available_keys = array_keys( $available_items );

				if ( ! in_array( $resolved_key, $available_keys, true ) ) {
					foreach ( $integrations as $integration_key => $integration ) {
						if ( in_array( $integration_key, $available_keys, true ) ) {
							$actual_key = $integration_key;
							break;
						}
					}
				}
			}

			if ( is_array( $integrations ) ) {
				foreach ( $integrations as $k => $integration ) {
					$available_delivery_item_in_cart = $available_delivery->get_item( $k );

					if ( $available_delivery_item_in_cart && $actual_key && $actual_key === $k ) {
						unset( $integration['title'] );
						$integration['cost']                 = $available_delivery_item_in_cart->get_cost();
						$result[ $simplyin_method_id ][ $k ] = $integration;
						$available_providers_in_cart[]       = $simplyin_method->get_logistic_provider();
					}
				}
			}
		}

		$available_providers_in_cart = array_unique( $available_providers_in_cart );
		$available_providers_in_cart = array_values( $available_providers_in_cart );

		// Build resolved mapping for cookie (zone-specific values override global).
		$resolved_mapping = [];
		foreach ( $this->get_available_simplyin_methods() as $simplyin_method ) {
			$method_id    = $simplyin_method->get_id();
			$resolved_val = $this->resolve_mapping_key( $method_id, $customer_zone_id, $mapping );
			if ( $resolved_val ) {
				$resolved_mapping[ $method_id ] = $resolved_val;
			}
		}

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf(
				'[Checkout] [check_delivery_parameters] %s',
				wp_json_encode(
					[
						'result'                     => $result,
						'mapping'                    => $mapping,
						'resolved_mapping'           => $resolved_mapping,
						'customer_zone_id'           => $customer_zone_id,
						'simply_available_providers' => $available_providers_in_cart,
					]
				),
			),
			'shipping_mapping'
		);

		if ( ! headers_sent() ) {
			simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
				sprintf(
					'[Checkout] [check_delivery_parameters] [wc_setcookie] %s',
					wp_json_encode(
						[
							'result'      => $result,
							'result_json' => wp_json_encode( $result ),
						]
					),
				),
				'shipping_mapping'
			);

			wc_setcookie(
				'simply_available_delivery',
				wp_json_encode( $result ),
				$this->get_session_expiration(),
				false,
				false
			);

			wc_setcookie(
				'simply_shipping_mapping',
				wp_json_encode( $resolved_mapping ),
				$this->get_session_expiration(),
				false,
				false
			);

			wc_setcookie(
				'simply_available_providers',
				wp_json_encode( $available_providers_in_cart ),
				$this->get_session_expiration(),
				false,
				false
			);
		}
	}

	/**
	 * Resolves the WC rate-id to use for a given SimplyIn method and customer zone.
	 *
	 * Fallback chain:
	 *   1. Zone-specific setting   (e.g. simply_door_delivery_zone_1_mapping)
	 *   2. Default zone (zone_0)   (e.g. simply_door_delivery_zone_0_mapping)
	 *      — only tried when customer is NOT in zone_0 itself
	 *   3. Global legacy option    (e.g. simply_door_delivery_mapping)
	 *      — backward compat for shops that pre-date the migration
	 *
	 * 'none' in any slot means "explicitly disabled at that level"; the chain
	 * stops and null is returned (no further fallback).
	 *
	 * @param string $simplyin_method_id SimplyIn method identifier.
	 * @param int    $customer_zone_id   WooCommerce shipping zone ID for the customer.
	 * @param array  $mapping            Full SimplyInShippingMappingCache contents.
	 *
	 * @return string|null  WC rate-id to use, or null if nothing is configured.
	 */
	private function resolve_mapping_key(
		string $simplyin_method_id,
		int $customer_zone_id,
		array $mapping
	): ?string {
		$zone_val = $mapping[ "{$simplyin_method_id}_zone_{$customer_zone_id}" ] ?? null;
		if ( 'none' === $zone_val ) {
			return null;
		}
		if ( ! empty( $zone_val ) ) {
			return $zone_val;
		}

		if ( 0 !== $customer_zone_id ) {
			$zone0_val = $mapping[ "{$simplyin_method_id}_zone_0" ] ?? null;
			if ( 'none' === $zone0_val ) {
				return null;
			}
			if ( ! empty( $zone0_val ) ) {
				return $zone0_val;
			}
		}

		$global = $mapping[ $simplyin_method_id ] ?? null;

		return ( ! empty( $global ) && 'none' !== $global ) ? $global : null;
	}

	/**
	 * Returns session expiration timestamp.
	 *
	 * @return int
	 */
	private function get_session_expiration() {
		if ( isset( WC()->session ) ) {
			$cookie = WC()->session->get_session_cookie();
			if ( isset( $cookie[1] ) ) {
				return WC()->session->get_session_cookie()[1];
			}
		}

		return strtotime( '+2 days' );
	}

	/**
	 * Returns cached mapping items, building them if necessary.
	 *
	 * @return Mapping_Item[]
	 */
	public function get_mapping_items(): array {

		if ( empty( $this->mapping_items ) ) {
			$this->mapping_items = $this->build_mapping();
		}

		return $this->mapping_items;
	}

	/**
	 * Returns all WooCommerce shipping zones including the Rest of the World zone.
	 *
	 * @return \WC_Shipping_Zone[]
	 */
	public function get_shipping_zones(): array {
		$zones      = array( new \WC_Shipping_Zone( 0 ) );
		$data_store = \WC_Data_Store::load( 'shipping-zone' );
		$raw_zones  = $data_store->get_zones();
		foreach ( $raw_zones as $raw_zone ) {
			$zones[] = new \WC_Shipping_Zone( $raw_zone );
		}

		return $zones;
	}

	/**
	 * @param \WC_Shipping_Zone $zone Shipping zone to build mapping items for.
	 *
	 * @return Mapping_Item[]
	 */
	public function get_mapping_items_for_zone( \WC_Shipping_Zone $zone ): array {
		$simplyin_methods = $this->get_available_simplyin_methods();
		$integrations     = $this->get_integrations_for_zone( $zone );
		$result           = [];

		foreach ( $simplyin_methods as $simplyin_method ) {
			$item = new Mapping_Item();
			$item->set_simplyin_method( $simplyin_method );
			foreach ( $integrations as $integration ) {
				if ( $simplyin_method->is_integration_supported( $integration ) ) {
					$item->put_integration( $integration );
				}
			}
			$result[] = $item;
		}

		return $result;
	}

	/**
	 * @param \WC_Shipping_Zone $zone Shipping zone to retrieve integrations for.
	 *
	 * @return Integration_Interface[]
	 */
	private function get_integrations_for_zone( \WC_Shipping_Zone $zone ): array {
		$door_factory = new Door_Delivery_Integration_Factory();
		$integrations = [];

		foreach ( $zone->get_shipping_methods() as $method ) {
			$found = 0;
			foreach ( $this->get_available_integration_factories() as $factory ) {
				$instance = $factory->create( $method );
				if ( $instance ) {
					$integrations[] = $instance;
					++$found;
				}
			}
			if ( 0 === $found ) {
				$integrations[] = $door_factory->create( $method );
			}
		}

		return $integrations;
	}

	/**
	 * Returns all available WooCommerce shipping methods across all zones.
	 *
	 * @return WC_Shipping_Method[]
	 */
	private function get_available_shipping_methods(): array {
		if ( ! empty( self::$woo_shipping_methods_cache ) ) {
			return self::$woo_shipping_methods_cache;
		}

		$available = [];

		foreach ( $this->get_shipping_zones() as $zone ) {
			$zone_shipping_methods = $zone->get_shipping_methods();
			foreach ( $zone_shipping_methods as $method ) {
				$available[ $method->get_rate_id() ] = $method;
			}
		}

		self::$woo_shipping_methods_cache = $available;

		return $available;
	}

	/**
	 * @return Simplyin_Method_Interface[]
	 */
	public function get_available_simplyin_methods(): array {
		return ( new Simplyin_Method_Factory() )->get_available_simply_methods();
	}

	/**
	 * Returns a SimplyIn method by its identifier.
	 *
	 * @param string $simplyin_method_id SimplyIn method identifier.
	 *
	 * @return Simplyin_Method|null
	 */
	public function get_simplyin_method( string $simplyin_method_id ): ?Simplyin_Method {
		$methods = $this->get_available_simplyin_methods();
		foreach ( $methods as $method ) {
			if ( $method->get_id() === $simplyin_method_id ) {
				return $method;
			}
		}

		return null;
	}

	/**
	 * @return Integration_Interface[]
	 */
	public function get_available_integrations(): array {

		$available_methods = $this->get_available_shipping_methods();
		$result            = [];

		$door_integration_factory = new Door_Delivery_Integration_Factory();

		foreach ( $available_methods as $method ) {
			$found_integrations = 0;
			foreach ( $this->get_available_integration_factories() as $integration_factory ) {

				$instance = $integration_factory->create( $method );
				if ( $instance ) {
					$result[] = $instance;
					++$found_integrations;
				}
			}
			if ( 0 === $found_integrations ) {
				$instance = $door_integration_factory->create( $method );
				$result[] = $instance;
			}
		}

		return $result;
	}

	/**
	 * @return Integration_Factory_Interface[]
	 */
	public function get_available_integration_factories(): array {
		if ( empty( self::$integration_factory_cache ) ) {
			$this->setup_available_integration_factories();
		}

		return self::$integration_factory_cache;
	}

	/**
	 * Registers all available integration factories into the static cache.
	 *
	 * @return void
	 */
	private function setup_available_integration_factories(): void {
		self::$integration_factory_cache[] = new Inpost_Plugin_Integration_Factory();
		self::$integration_factory_cache[] = new Dhl_Polska_Integration_Factory();
		self::$integration_factory_cache[] = new Fs_Integration_Factory();
		self::$integration_factory_cache[] = new Apaczka_Integration_Factory();
		self::$integration_factory_cache[] = new Furgonetka_Integration_Factory();
	}
}
