<?php
/**
 * Class CheckoutChoosePointField
 *
 * @package DhlPolska\Point
 */

namespace DhlPolska\Point;

use DhlPolska\Shipment\DhlServices;
use DhlPolska\Shipment\ShipmentData;
use DhlPolskaVendor\WPDesk\PluginBuilder\Plugin\Hookable;
use DhlPolskaVendor\WPDesk\View\Renderer\Renderer;
use WC_Shipping_Rate;

/**
 * Add button for choosing map on checkout page.
 */
class CheckoutChoosePointField implements Hookable {
	use ServicePointTrait;

	public const SESSION_SERVICE_POINT_RATES = 'dhl_polska_service_point_rates';
	public const SESSION_SERVICE_POINT_TYPES = 'dhl_polska_service_point_types';
	public const SESSION_SERVICE_POINT_COD   = 'dhl_polska_service_point_cod';

	public const FIELD_PARCELSHOP_ID         = ShipmentData::OPTION_PARCELSHOP_ID;
	public const FIELD_PARCELSHOP_SAP        = ShipmentData::OPTION_PARCELSHOP_SAP;
	public const FIELD_PARCELSHOP_NAME       = ShipmentData::OPTION_PARCELSHOP_NAME;
	public const FIELD_PARCELSHOP_ZIP        = ShipmentData::OPTION_PARCELSHOP_ZIP;
	public const FIELD_PARCELSHOP_STREET     = ShipmentData::OPTION_PARCELSHOP_STREET;
	public const FIELD_PARCELSHOP_CITY       = ShipmentData::OPTION_PARCELSHOP_CITY;
	public const FIELD_PARCELSHOP_STREET_NO  = ShipmentData::OPTION_PARCELSHOP_STREET_NO;
	public const FIELD_PARCELSHOP_HOUSE_NO   = ShipmentData::OPTION_PARCELSHOP_HOUSE_NO;
	public const FIELD_PARCELSHOP_POSTNUMMER = ShipmentData::OPTION_PARCELSHOP_POSTNUMMER;
	public const FIELD_API_ERROR             = 'dhl_polska_api_error';

	/**
	 * @var Renderer
	 */
	private Renderer $renderer;

	/**
	 * CheckoutChoosePointField constructor.
	 *
	 * @param Renderer $renderer .
	 */
	public function __construct( Renderer $renderer ) {
		$this->renderer = $renderer;
	}

	/**
	 * Hooks.
	 */
	public function hooks() {
		add_action( 'woocommerce_checkout_update_order_review', [ $this, 'reset_session' ] );
		add_filter( 'woocommerce_shipping_packages', [ $this, 'add_service_point_rates_to_session' ] );
		add_action( 'woocommerce_review_order_after_shipping', [ $this, 'add_field' ] );
	}

	/**
	 * @return void
	 */
	public function reset_session(): void {
		if ( null === WC()->session ) {
			return;
		}

		WC()->session->set( self::SESSION_SERVICE_POINT_RATES, [] );
		WC()->session->set( self::SESSION_SERVICE_POINT_TYPES, [] );
		WC()->session->set( self::SESSION_SERVICE_POINT_COD, [] );
	}

	/**
	 * @param array<int, string[]> $packages .
	 *
	 * @return array<int, string[]>
	 */
	public function add_service_point_rates_to_session( $packages ): array {
		if ( null === WC()->session ) {
			return $packages;
		}

		WC()->session->set( self::SESSION_SERVICE_POINT_RATES, $this->get_service_point_rates_from_packages( $packages ) );
		WC()->session->set( self::SESSION_SERVICE_POINT_TYPES, $this->get_service_point_types_from_packages( $packages ) );
		WC()->session->set( self::SESSION_SERVICE_POINT_COD, $this->get_service_point_cod_from_packages( $packages ) );

		return $packages;
	}

	/**
	 * @return void
	 */
	public function add_field(): void {
		$cart = WC()->cart;

		if ( null === $cart || ! $this->is_service_point_option() ) { /** @phpstan-ignore-line */
			return;
		}

		$form_data = $this->get_posted_data();

		$session_storage = new SessionStorage();

		$parcelshop_data = $this->get_parcelshop_data_from_form( $form_data, $session_storage );
		$api_error       = $this->normalize_api_error( $form_data[ self::FIELD_API_ERROR ] ?? $session_storage->get_api_error() );
		$point_types     = $this->get_point_types_from_session();
		$point_cods      = $this->get_point_cod_from_session();

		$customer_shipping_country = $cart->get_customer()->get_shipping_country();
		$cart_contents_weight      = wc_get_weight( $cart->get_cart_contents_weight(), 'kg' );
		$point_type                = $this->get_point_type_for_chosen_rate( $form_data );

		$this->renderer->output_render(
			'point/checkout-review-order-row',
			[
				'parcelshop_data'            => $parcelshop_data,
				'api_error'                  => $api_error,
				'customer_shipping_country'  => $customer_shipping_country,
				'cart_contents_weight'       => $cart_contents_weight,
				'point_type'                 => $point_type,
				'point_types'                => $point_types,
				'point_cods'                 => $point_cods,
			]
		);
	}

	/**
	 * @param array<int, string[]> $packages .
	 *
	 * @return string[]
	 */
	private function get_service_point_rates_from_packages( $packages ): array {
		$service_point_rates = [];

		foreach ( $packages as $package ) {
			if ( empty( $package['rates'] ) ) {
				continue;
			}

			foreach ( $package['rates'] as $rate_id => $rate ) { /** @phpstan-ignore-line */
				if ( ! $rate instanceof WC_Shipping_Rate ) {
					continue;
				}

				if ( $this->is_service_point_rate( $rate ) ) {
					$service_point_rates[] = $rate_id;
				}
			}
		}

		return array_unique( $service_point_rates );
	}

	/**
	 * @param array<int, string[]> $packages .
	 *
	 * @return array<string, string>
	 */
	private function get_service_point_types_from_packages( $packages ): array {
		$service_point_types = [];

		foreach ( $packages as $package ) {
			if ( empty( $package['rates'] ) ) {
				continue;
			}

			$shipping_country = $this->get_shipping_country_from_package( $package );

			foreach ( $package['rates'] as $rate_id => $rate ) { /** @phpstan-ignore-line */
				if ( ! $rate instanceof WC_Shipping_Rate ) {
					continue;
				}

				if ( ! $this->is_service_point_rate( $rate ) ) {
					continue;
				}

				$service_point_types[ $rate_id ] = $this->get_point_type_from_rate( $rate, $shipping_country );
			}
		}

		return $service_point_types;
	}

	/**
	 * @param WC_Shipping_Rate $rate .
	 *
	 * @return bool
	 */
	private function is_service_point_rate( WC_Shipping_Rate $rate ): bool {
		$meta_data = $rate->get_meta_data();

		$service_from_method      = ! empty( $meta_data['_fs_method'][ ShipmentData::OPTION_SERVICE ] )
			&& DhlServices::SERVICE_POINT === $meta_data['_fs_method'][ ShipmentData::OPTION_SERVICE ];
		$service_from_integration = ! empty( $meta_data['_fs_method']['dhl_polska_service'] )
			&& DhlServices::SERVICE_POINT === $meta_data['_fs_method']['dhl_polska_service'];

		return $service_from_method || $service_from_integration;
	}

	/**
	 * @param WC_Shipping_Rate $rate .
	 *
	 * @return string
	 */
	private function get_point_type_from_rate( WC_Shipping_Rate $rate, string $shipping_country ): string {
		$meta_data = $rate->get_meta_data();
		$fs_method = $meta_data['_fs_method'] ?? [];
		if ( ! is_array( $fs_method ) ) {
			$fs_method = [];
		}

		return ShipmentData::resolve_point_type_from_method( $fs_method, $shipping_country );
	}

	/**
	 * @param array<int, string[]> $packages .
	 *
	 * @return array<string, string>
	 */
	private function get_service_point_cod_from_packages( $packages ): array {
		$service_point_cod = [];

		foreach ( $packages as $package ) {
			if ( empty( $package['rates'] ) ) {
				continue;
			}

			foreach ( $package['rates'] as $rate_id => $rate ) { /** @phpstan-ignore-line */
				if ( ! $rate instanceof WC_Shipping_Rate ) {
					continue;
				}

				if ( ! $this->is_service_point_rate( $rate ) ) {
					continue;
				}

				$service_point_cod[ $rate_id ] = $this->get_cod_from_rate( $rate );
			}
		}

		return $service_point_cod;
	}

	/**
	 * @param WC_Shipping_Rate $rate .
	 *
	 * @return string
	 */
	private function get_cod_from_rate( WC_Shipping_Rate $rate ): string {
		$meta_data = $rate->get_meta_data();
		$fs_method = $meta_data['_fs_method'] ?? [];
		$cod_value = $fs_method[ ShipmentData::OPTION_COD ] ?? ( $fs_method['dhl_polska_cod'] ?? '' );

		if ( is_bool( $cod_value ) ) {
			return $cod_value ? 'yes' : 'no';
		}

		$normalized_cod_value = strtolower( trim( (string) $cod_value ) );

		return in_array( $normalized_cod_value, [ 'yes', 'true', '1', 't' ], true ) ? 'yes' : 'no';
	}

	/**
	 * @return string
	 */
	private function get_point_type_for_chosen_rate( array $form_data = [] ): string {
		$session = WC()->session;

		$point_types = $session->get( self::SESSION_SERVICE_POINT_TYPES, [] );
		if ( ! is_array( $point_types ) ) {
			$point_types = [];
		}

		$chosen_methods = $this->get_chosen_shipping_methods_from_form( $form_data );
		if ( [] === $chosen_methods ) {
			$chosen_methods = $this->get_chosen_shipping_methods();
		}

		foreach ( $chosen_methods as $chosen_method ) {
			if ( isset( $point_types[ $chosen_method ] ) ) {
				return (string) $point_types[ $chosen_method ];
			}
		}

		return $this->get_point_type_from_cart_rates( $chosen_methods );
	}

	/**
	 * @param array<string, mixed> $form_data .
	 *
	 * @return string[]
	 */
	private function get_chosen_shipping_methods_from_form( array $form_data ): array {
		if ( empty( $form_data['shipping_method'] ) ) {
			return [];
		}

		return wp_parse_list( $form_data['shipping_method'] );
	}

	/**
	 * @param string[] $chosen_methods .
	 *
	 * @return string
	 */
	private function get_point_type_from_cart_rates( array $chosen_methods ): string {
		$cart = WC()->cart;
		if ( null === $cart ) { /** @phpstan-ignore-line */
			return '';
		}

		if ( [] === $chosen_methods ) {
			return '';
		}

		$packages = $cart->get_shipping_packages();
		foreach ( $packages as $package ) {
			if ( empty( $package['rates'] ) || ! is_array( $package['rates'] ) ) {
				continue;
			}

			foreach ( $chosen_methods as $chosen_method ) {
				if ( isset( $package['rates'][ $chosen_method ] ) && $package['rates'][ $chosen_method ] instanceof WC_Shipping_Rate ) {
					return $this->get_point_type_from_rate(
						$package['rates'][ $chosen_method ],
						$this->get_shipping_country_from_package( $package )
					);
				}
			}
		}

		return '';
	}

	/**
	 * @return array<string, string>
	 */
	private function get_point_types_from_session(): array {
		$session = WC()->session;
		if ( null === $session ) { /** @phpstan-ignore-line */
			return [];
		}

		$point_types = $session->get( self::SESSION_SERVICE_POINT_TYPES, [] );
		if ( ! is_array( $point_types ) ) {
			return [];
		}

		return $point_types;
	}

	/**
	 * @param array<string, mixed> $package .
	 *
	 * @return string
	 */
	private function get_shipping_country_from_package( array $package ): string {
		$destination = $package['destination'] ?? [];
		if ( is_array( $destination ) ) {
			$country = strtoupper( (string) ( $destination['country'] ?? '' ) );
			if ( '' !== $country ) {
				return $country;
			}
		}

		return $this->get_shipping_country_from_customer();
	}

	/**
	 * @return string
	 */
	private function get_shipping_country_from_customer(): string {
		$customer         = WC()->cart->get_customer();
		$shipping_country = strtoupper( (string) $customer->get_shipping_country() );
		if ( '' !== $shipping_country ) {
			return $shipping_country;
		}

		return strtoupper( (string) $customer->get_billing_country() );
	}

	/**
	 * @return array<string, string>
	 */
	private function get_point_cod_from_session(): array {
		$session = WC()->session;
		if ( null === $session ) { /** @phpstan-ignore-line */
			return [];
		}

		$point_cods = $session->get( self::SESSION_SERVICE_POINT_COD, [] );
		if ( ! is_array( $point_cods ) ) {
			return [];
		}

		return $point_cods;
	}

	/**
	 * @param array<string, string> $form_data       .
	 * @param SessionStorage        $session_storage .
	 *
	 * @return array<string, string>
	 */
	private function get_parcelshop_data_from_form( array $form_data, SessionStorage $session_storage ): array {
		$session_data = $session_storage->get_parcelshop_data();

		return [
			'id'         => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_ID ] ?? $session_data['id'] ),
			'sap'        => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_SAP ] ?? $session_data['sap'] ),
			'name'       => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_NAME ] ?? $session_data['name'] ),
			'zip'        => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_ZIP ] ?? $session_data['zip'] ),
			'street'     => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_STREET ] ?? $session_data['street'] ),
			'city'       => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_CITY ] ?? $session_data['city'] ),
			'streetNo'   => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_STREET_NO ] ?? $session_data['streetNo'] ),
			'houseNo'    => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_HOUSE_NO ] ?? $session_data['houseNo'] ),
			'postnummer' => $this->sanitize_form_value( $form_data[ self::FIELD_PARCELSHOP_POSTNUMMER ] ?? $session_data['postnummer'] ),
		];
	}

	/**
	 * @return array<string, string>
	 */
	private function get_posted_data(): array {
		$form_data = [];
		$post_data = $this->filter_input( INPUT_POST, 'post_data' );

		if ( $post_data ) {
			wp_parse_str( wp_unslash( $post_data ), $form_data );
			$form_data = wc_clean( $form_data );
		}

		return $form_data;
	}

	/**
	 * @param mixed $value .
	 *
	 * @return string
	 */
	private function sanitize_form_value( $value ): string {
		if ( ! is_scalar( $value ) ) {
			return '';
		}

		return (string) wc_clean( $value );
	}

	/**
	 * @param mixed $value .
	 *
	 * @return string
	 */
	private function normalize_api_error( $value ): string {
		$api_error = strtolower( $this->sanitize_form_value( $value ) );

		return 'yes' === $api_error ? 'yes' : 'no';
	}

	/**
	 * @param int    $type     .
	 * @param string $var_name .
	 *
	 * @return mixed
	 * @codeCoverageIgnore
	 */
	protected function filter_input( int $type, string $var_name ) {
		return filter_input( $type, $var_name );
	}
}
