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

namespace DhlPolska\Point;

use DhlPolskaVendor\WPDesk\PluginBuilder\Plugin\Hookable;

/**
 * Preparing posted data.
 */
class CheckoutPostedDataPreparing implements Hookable {

	private const SESSION_CHOSEN_METHODS = 'dhl_polska_chosen_shipping_methods';

	/**
	 * Hooks.
	 */
	public function hooks() {
		add_filter( 'woocommerce_checkout_posted_data', [ $this, 'prepare_posted_data' ] );
		add_action( 'woocommerce_checkout_update_order_review', [ $this, 'save_point_data_to_session' ] );
		add_action( 'woocommerce_store_api_cart_select_shipping_rate', [ $this, 'maybe_clear_point_data_on_store_api_shipping_change' ], 10, 3 );
	}

	/**
	 * @param array<string, string> $data .
	 *
	 * @return array<string, string>
	 */
	public function prepare_posted_data( $data ): array {
		foreach ( $this->get_fields_map() as $field ) {
			$data[ $field ] = $this->sanitize_posted_field_value( $this->filter_input( INPUT_POST, $field ) );
		}

		$data[ CheckoutChoosePointField::FIELD_API_ERROR ] = $this->normalize_api_error(
			$this->sanitize_posted_field_value( $this->filter_input( INPUT_POST, CheckoutChoosePointField::FIELD_API_ERROR ) )
		);

		return $data;
	}

	/**
	 * @param string $posted_data .
	 *
	 * @return void
	 */
	public function save_point_data_to_session( $posted_data ): void {
		$session_storage = new SessionStorage();

		parse_str( wp_unslash( $posted_data ), $data );
		$data = $this->sanitize_checkout_data( $data );

		$this->maybe_clear_point_data_on_shipping_change( $data, $session_storage );
		if ( isset( $data[ CheckoutChoosePointField::FIELD_PARCELSHOP_ID ] ) ) {
			$session_storage->set_parcelshop_data_from_fields( $data );
			$session_storage->set_api_error( $this->normalize_api_error( $data[ CheckoutChoosePointField::FIELD_API_ERROR ] ?? 'no' ) );
		}
	}

	/**
	 * @param string|null    $package_id .
	 * @param string         $rate_id    .
	 * @param \WP_REST_Request $request  .
	 *
	 * @return void
	 */
	public function maybe_clear_point_data_on_store_api_shipping_change( $package_id, $rate_id, $request ): void {
		$session         = WC()->session;
		$current_methods = $this->normalize_shipping_methods( $session->get( 'chosen_shipping_methods', [] ) );
		if ( empty( $current_methods ) ) {
			return;
		}
		$previous_methods_raw = $session->get( self::SESSION_CHOSEN_METHODS, null );
		if ( null === $previous_methods_raw ) {
			$session->set( self::SESSION_CHOSEN_METHODS, $current_methods );

			return;
		}

		$previous_methods = $this->normalize_shipping_methods( $previous_methods_raw );
		if ( $current_methods !== $previous_methods ) {
			$shipping_country = $this->get_shipping_country_from_customer();
			if ( $this->should_clear_point_data_on_method_change( $previous_methods, $current_methods, $shipping_country ) ) {
				( new SessionStorage() )->clear_parcelshop_data();
			}
		}

		$session->set( self::SESSION_CHOSEN_METHODS, $current_methods );
	}

	/**
	 * @return string[]
	 */
	private function get_fields_map(): array {
		return [
			CheckoutChoosePointField::FIELD_PARCELSHOP_ID,
			CheckoutChoosePointField::FIELD_PARCELSHOP_SAP,
			CheckoutChoosePointField::FIELD_PARCELSHOP_NAME,
			CheckoutChoosePointField::FIELD_PARCELSHOP_ZIP,
			CheckoutChoosePointField::FIELD_PARCELSHOP_STREET,
			CheckoutChoosePointField::FIELD_PARCELSHOP_CITY,
			CheckoutChoosePointField::FIELD_PARCELSHOP_STREET_NO,
			CheckoutChoosePointField::FIELD_PARCELSHOP_HOUSE_NO,
			CheckoutChoosePointField::FIELD_PARCELSHOP_POSTNUMMER,
		];
	}

	/**
	 * @param array<string, mixed> $data             .
	 * @param SessionStorage      $session_storage .
	 *
	 * @return void
	 */
	private function maybe_clear_point_data_on_shipping_change( array $data, SessionStorage $session_storage ): void {
		$session = WC()->session;

		$posted_methods = $this->get_shipping_methods_from_posted_data( $data );
		if ( empty( $posted_methods ) ) {
			return;
		}

		$previous_methods_raw = $session->get( self::SESSION_CHOSEN_METHODS, null );
		if ( null === $previous_methods_raw ) {
			$session->set( self::SESSION_CHOSEN_METHODS, $posted_methods );

			return;
		}

		$previous_methods = $this->normalize_shipping_methods( $previous_methods_raw );

		if ( $posted_methods !== $previous_methods ) {
			$shipping_country = $this->get_shipping_country_from_data( $data );
			if ( $this->should_clear_point_data_on_method_change( $previous_methods, $posted_methods, $shipping_country ) ) {
				$session_storage->clear_parcelshop_data();
			}
		}

		$session->set( self::SESSION_CHOSEN_METHODS, $posted_methods );
	}

	/**
	 * @param array<string, mixed> $data .
	 *
	 * @return string[]
	 */
	private function get_shipping_methods_from_posted_data( array $data ): array {
		$posted_methods = $data['shipping_method'] ?? [];

		return $this->normalize_shipping_methods( $posted_methods );
	}

	/**
	 * @param mixed $methods .
	 *
	 * @return string[]
	 */
	private function normalize_shipping_methods( $methods ): array {
		if ( is_string( $methods ) ) {
			$methods = [ $methods ];
		}

		if ( ! is_array( $methods ) ) {
			return [];
		}

		return array_values(
			array_map(
				static fn ( $method ): string => (string) $method,
				$methods
			)
		);
	}

	/**
	 * @param array<string, mixed> $data .
	 *
	 * @return string
	 */
	private function get_shipping_country_from_data( array $data ): string {
		$shipping_country = strtoupper( sanitize_text_field( (string) ( $data['shipping_country'] ?? '' ) ) );
		if ( '' === $shipping_country ) {
			$shipping_country = strtoupper( sanitize_text_field( (string) ( $data['billing_country'] ?? '' ) ) );
		}

		return $shipping_country;
	}

	/**
	 * @return string
	 */
	private function get_shipping_country_from_customer(): string {
		$customer = WC()->customer;
		if ( null === $customer ) { /** @phpstan-ignore-line */
			return '';
		}

		$shipping_country = strtoupper( (string) $customer->get_shipping_country() );
		if ( '' === $shipping_country ) {
			$shipping_country = strtoupper( (string) $customer->get_billing_country() );
		}

		return $shipping_country;
	}

	/**
	 * @param string[] $previous_methods .
	 * @param string[] $current_methods  .
	 * @param string   $shipping_country .
	 *
	 * @return bool
	 */
	private function should_clear_point_data_on_method_change( array $previous_methods, array $current_methods, string $shipping_country ): bool {
		if ( 'PL' === $shipping_country || '' === $shipping_country ) {
			return true;
		}

		$previous_point_type = $this->get_point_type_from_methods( $previous_methods );
		$current_point_type  = $this->get_point_type_from_methods( $current_methods );

		if ( '' === $previous_point_type && '' === $current_point_type ) {
			return false;
		}

		return $previous_point_type !== $current_point_type;
	}

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

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

		foreach ( $methods as $method ) {
			if ( isset( $point_types[ $method ] ) ) {
				return (string) $point_types[ $method ];
			}
		}

		return '';
	}

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

		return (string) wc_clean( $value );
	}

	/**
	 * @param string $api_error .
	 *
	 * @return string
	 */
	private function normalize_api_error( string $api_error ): string {
		$api_error = strtolower( $api_error );

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

	/**
	 * @param array<string, mixed> $data .
	 *
	 * @return array<string, mixed>
	 */
	private function sanitize_checkout_data( array $data ): array {
		foreach ( $this->get_fields_map() as $field ) {
			if ( isset( $data[ $field ] ) ) {
				$data[ $field ] = $this->sanitize_posted_field_value( $data[ $field ] );
			}
		}

		if ( isset( $data[ CheckoutChoosePointField::FIELD_API_ERROR ] ) ) {
			$data[ CheckoutChoosePointField::FIELD_API_ERROR ] = $this->normalize_api_error(
				$this->sanitize_posted_field_value( $data[ CheckoutChoosePointField::FIELD_API_ERROR ] )
			);
		}

		if ( isset( $data['shipping_country'] ) ) {
			$data['shipping_country'] = strtoupper( sanitize_text_field( (string) $data['shipping_country'] ) );
		}

		if ( isset( $data['billing_country'] ) ) {
			$data['billing_country'] = strtoupper( sanitize_text_field( (string) $data['billing_country'] ) );
		}

		if ( isset( $data['shipping_method'] ) ) {
			$data['shipping_method'] = $this->normalize_shipping_methods( $data['shipping_method'] );
		}

		return $data;
	}

	/**
	 * @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 );
	}
}
