<?php

namespace PostenBringCheckout\admin\bring\models\booking;

use DateTime;
use Exception;
use PostenBringCheckout\admin\api\models\OrderRequest;
use PostenBringCheckout\admin\api\models\OrderRequestPackage;
use PostenBringCheckout\admin\WeightUtils;
use WC_Order;
use WC_Order_Item_Product;

defined( 'ABSPATH' ) || exit;

class WebshopBookingMapper {
	public static function mapToWebshopBooking( $wc_order, OrderRequest $order_request ): WebshopBooking {
		$externalOrderId = $order_request->order_ref;
		$packages        = array();
		foreach ( $order_request->packages as $package ) {
			$packages[] = self::getPackage( $package );
		}

		$selected_shipping_option = $order_request->selected_shipping_option;
		$shippingOption           = self::getBookingProduct( $selected_shipping_option );
		$shippingDateTime         = self::getShippingDate( $wc_order );
		$sender                   = self::getSender( $externalOrderId );
		$recipient                = self::getRecipient( $wc_order, $selected_shipping_option, $externalOrderId );
		$selected_pickup_point    = self::getSelectedPickupPoint( $wc_order, $sender->countryCode, $recipient->countryCode, $selected_shipping_option );
		$selected_delivery_date   = self::getSelectedDeliveryDate( $wc_order, $selected_shipping_option );
		$product                  = new Product(
			$shippingOption,
			$order_request->customer_number,
			sizeof( $order_request->packages ) === 1 ? self::getEdiCustomsInformation( $selected_shipping_option, $sender->countryCode, $recipient->countryCode, $wc_order ) : null,
			self::getAdditionalServices( $selected_shipping_option, $recipient, $sender->countryCode )
		);
		$consignment              = new Consignment(
			$shippingDateTime,
			new Parties( $sender, $recipient, $selected_pickup_point ),
			$product,
			$packages,
			'1',
			$selected_delivery_date
		);
		$bookingRequest           = new MybringBookingRequest( $order_request->test_indicator, array( $consignment ) );
		return new WebshopBooking( $externalOrderId, $bookingRequest );
	}

	private static function getSender( $order_ref ): Party {

		$storeName 		 = get_option('blogname');
		$storeName 		 = trim(preg_split('/[(|-]/', $storeName)[0]);
		$storeAddress    = get_option( 'woocommerce_store_address' );
		$storeAddress2   = get_option( 'woocommerce_store_address_2' );
		$storeCity       = get_option( 'woocommerce_store_city' );
		$storePostcode   = get_option( 'woocommerce_store_postcode' );
		$storeRawCountry = get_option( 'woocommerce_default_country' );
		$splitCountry    = explode( ':', $storeRawCountry );
		$storeCountry    = $splitCountry[0];
		$storeEmail      = get_option( 'admin_email' );

		return new Party(
			$storeName,
			$storeAddress,
			$storeAddress2,
			$storePostcode,
			$storeCity,
			$storeCountry,
			new Contact( $storeName, $storeEmail, null ),
			"#$order_ref"
		);
	}

	private static function getBookingProduct( $service_code ): string {
		switch ( $service_code ) {
			case '0340':
				return 'PICKUP_PARCEL';
			case '0342':
				return 'PICKUP_PARCEL_BULK';
			case '0349':
				return 'HOME_DELIVERY_PARCEL';
			default:
				return $service_code;
		}
	}

	private static function getEdiCustomsInformation( string $serviceId, string $fromCountry, string $toCountry, $order ): ?EdiCustomsDeclaration {
		if ( in_array( $serviceId, array ( '0340', '0330' ) ) && $fromCountry === 'NO' && $toCountry !== 'NO' ) {
			$customsDeclarations = array_values(array_map(
				function ( $item ) use ( $fromCountry, $order ) {
					$product = $item->get_product();
					return new EdiCustomsDeclarationDetails(
						$fromCountry,
						$order->get_currency(),
						$product->get_name() . '. ' . $product->get_short_description(),
						WeightUtils::getWeightInKgs($product->get_weight()),
						$item->get_quantity(),
						$item->get_total()
					);
				},
				array_filter(
					$order->get_items(),
					function ( $item ) {
						return $item->get_type() === 'line_item';
					}
				)
			));

			return new EdiCustomsDeclaration( 'SALE_OF_GOODS', $customsDeclarations );
		}
		return null;
	}

	private static function getAdditionalServices( string $serviceId, Party $recipient, string $fromCountry ): ?array {
		if ( in_array( $serviceId, array( '0342', '0349', '0332' ) ) || in_array( $serviceId , array( '0340', '0330' ) ) && in_array( $fromCountry, array( 'SE', 'DK' ) ) ) {
			$mobile = self::sanitizeMobileNumber( $recipient->countryCode, $recipient->contact->phoneNumber );

			if ( ! empty( $mobile ) || ! empty( $recipient->contact->email ) ) {
				return array( new AdditionalService( 'EVARSLING' ) );
			}
		}
		return null;
	}

	private static function sanitizeMobileNumber( string $to, string $mobile, bool $strict = true ): ?string {
		if ( $mobile ) {
			$sanitized = preg_replace( '/[^\d+]|(?<=\+)\D/', '', $mobile );

			$sePattern = '/^(((\+|(00))46)?(0?7)\d{8})$/';
			$noPattern = '/^(((\+|(00))47)?[49]\d{7})$/';
			$dkPattern = '/^(((\+|(00))45)?\d{8})$/';
			$fiPattern = '/^(((\+|(00))358)?(0457|04\d|050)\d{7})$/';

			// Check and format the mobile number according to the country code
			if ( $to === 'SE' && preg_match( $sePattern, $sanitized ) ) {
				return '+467' . preg_replace( '/^((\+|(00))46)?0?7/', '', $sanitized );
			} elseif ( $to === 'FI' && preg_match( $fiPattern, $sanitized ) ) {
				return '+358' . preg_replace('/^(?:\+358|00358|0)/', '', $sanitized);
			} elseif ( $to === 'DK' && preg_match( $dkPattern, $sanitized ) ) {
				return '+45' . preg_replace( '/^(\+|(00))45/', '', $sanitized );
			} elseif ( $to === 'NO' && preg_match( $noPattern, $sanitized ) ) {
				return '+47' . preg_replace( '/^(\+|(00))47/', '', $sanitized );
			} elseif ( $strict ) {
				return null;
			} else {
				return $sanitized;
			}
		}

		return null;
	}

	private static function getRecipient( $wc_order, $selected_shipping_option, $order_ref ): Party {
		$shippingAddress = $wc_order->get_address( 'shipping' );
		$billingAddress  = $wc_order->get_address( 'billing' );
		$phoneNumber = !empty($shippingAddress['phone']) ? $shippingAddress['phone'] : $billingAddress['phone'];

		return new Party(
			$shippingAddress['first_name'] . ' ' . $shippingAddress['last_name'],
			$shippingAddress['address_1'],
			$shippingAddress['address_2'],
			$shippingAddress['postcode'],
			$shippingAddress['city'],
			$shippingAddress['country'],
			new Contact(
				$shippingAddress['first_name'] . ' ' . $shippingAddress['last_name'],
				$shippingAddress['email'] ?? $billingAddress['email'],
				self::sanitizeMobileNumber( $shippingAddress['country'], $phoneNumber, (
					in_array( $selected_shipping_option, array( '0342', '0349', '0332' ) ) || (
						in_array($selected_shipping_option , array( '0340', '0330' ) ) &&
						in_array( get_option( 'woocommerce_default_country' ), ['SE', 'DK', 'FI'] ) )
					)
				)
			),
			"#$order_ref"
		);
	}

	private static function getSelectedDeliveryDate($wc_order, string $selected_shipping_option ): ?string {
		$selected_delivery_date = $wc_order->get_meta( '_posten_bring_checkout_selected_delivery_date' );

		if ( $selected_shipping_option === '5600' && ! empty( $selected_delivery_date ) ) {
			try {
				$date_time = new DateTime( $selected_delivery_date );
				return $date_time->format( 'Y-m-d\TH:i:s' );
			} catch ( Exception $e ) {
				wc_get_logger()->error(
					'Could not parse selected delivery date. ' . esc_html($e->getMessage()),
					array(
						'source'    => 'posten-bring-checkout',
						'backtrace' => true,
					)
				);
			}
		}
		return null;
	}

	private static function getShippingDate( $wc_order ): string {
		$delivery_alt_shipping_date = $wc_order->get_meta( '_posten_bring_checkout_selected_delivery_shipping_date' );
		$date_time                  = new DateTime();
		if ( ! empty( $delivery_alt_shipping_date ) ) {
			try {
				$date_time = new DateTime( $delivery_alt_shipping_date );
			} catch ( Exception $e ) {
				wc_get_logger()->error(
					'Could not parse selected shipping date. ' . esc_html($e->getMessage()),
					array(
						'source'    => 'posten-bring-checkout',
						'backtrace' => true,
					)
				);
			}
		}
		return $date_time->format( 'Y-m-d\TH:i:s.v\Z' );
	}

	private static function getSelectedPickupPoint($wc_order, string $from_country_code, string $to_country_code, string $selected_shipping_option ): ?PickupPoint {
		$selected_pickup_point = $wc_order->get_meta( '_posten_bring_checkout_selected_pickup_point_id' );
		if ( (in_array($selected_shipping_option, array('5800', '0342', '5801', '0344')) || ($selected_shipping_option == '0340' && $from_country_code != 'NO' && $to_country_code != 'NO')) && ! empty( $selected_pickup_point ) ) {
			return new PickupPoint( $selected_pickup_point, $to_country_code );
		}
		return null;
	}

	private static function getPackage( OrderRequestPackage $package ): Package {
		$dimensions = $package->dimensions ? new Dimensions(
			$package->dimensions->length,
			$package->dimensions->width,
			$package->dimensions->height
		) : null;

		return new Package( $package->weight, null, $dimensions, '1' );
	}
}
