<?php
/**
 * Class PrepareReturnShipmentData
 *
 * @package DhlPolska\Shipment
 */

namespace DhlPolska\Shipment;

use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\BillingData;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\CreateReturnShipmentRequest;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\PreavisoContact;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReturnAddressat;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReturnPackage;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReturnShip;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReturnShipmentInfo;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReturnShipmentTime;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ShipperAddress;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use WC_Order;

/**
 * Prepares return shipment data for DHL API.
 */
class PrepareReturnShipmentData {
	use UnitsConversionTrait;

	private const RETURN_SERVICE_DOMESTIC = 'ZK';
	private const RETURN_SERVICE_CONNECT  = 'ZC';
	private const SETTINGS_COUNTRY        = 'PL';

	/**
	 * @var ShipmentData
	 */
	private ShipmentData $shipment_data;

	/**
	 * @var WC_Order
	 */
	private WC_Order $order;

	/**
	 * @var WooCommerceDHLPolskaSettings
	 */
	private WooCommerceDHLPolskaSettings $settings;

	/**
	 * PrepareReturnShipmentData constructor.
	 *
	 * @param ShipmentData                 $shipment_data .
	 * @param WC_Order                     $order .
	 * @param WooCommerceDHLPolskaSettings $settings .
	 */
	public function __construct( ShipmentData $shipment_data, WC_Order $order, WooCommerceDHLPolskaSettings $settings ) {
		$this->shipment_data = $shipment_data;
		$this->order         = $order;
		$this->settings      = $settings;
	}

	/**
	 * @return CreateReturnShipmentRequest
	 */
	public function get_prepared_data(): CreateReturnShipmentRequest {
		$comment   = trim( $this->shipment_data->get_notes() );
		$reference = trim( $this->shipment_data->get_references() );
		$waybill   = trim( $this->shipment_data->get_tracking_number() );

		return new CreateReturnShipmentRequest(
			$this->get_shipment_info( $waybill ),
			$this->shipment_data->get_content(),
			$this->get_ship_data(),
			$this->get_pieces_data(),
			'' !== $comment ? $comment : null,
			'' !== $reference ? $reference : null,
			'' !== $waybill ? $waybill : null
		);
	}

	private function get_shipment_info( string $waybill ): ReturnShipmentInfo {
		$waybill = '' !== $waybill ? $waybill : null;

		return new ReturnShipmentInfo(
			$this->get_return_service_type(),
			$this->should_book_courier(),
			$this->get_billing_data(),
			$this->get_shipment_time(),
			$this->settings->get_label_mode()
		);
	}

	private function get_shipment_time(): ReturnShipmentTime {
		return new ReturnShipmentTime( $this->get_label_expiration_date() );
	}

	private function get_label_expiration_date(): string {
		$days = (int) $this->settings->get_return_label_expiration();
		if ( $days < 1 ) {
			$days = 1;
		}

		if ( function_exists( 'current_datetime' ) ) {
			$date = current_datetime();
		} else {
			$timezone = function_exists( 'wp_timezone' ) ? wp_timezone() : new \DateTimeZone( 'UTC' );
			$date     = new \DateTimeImmutable( 'now', $timezone );
		}

		return $date->modify( sprintf( '+%d days', $days ) )->format( 'Y-m-d' );
	}

	private function get_billing_data(): BillingData {
		$sap_number = (int) $this->settings->get_webapi_sap_number();

		return new BillingData(
			'RECEIVER',
			$sap_number,
			'BANK_TRANSFER'
		);
	}

	private function should_book_courier(): bool {
		if ( $this->is_international_return() ) {
			return false;
		}

		return 'courier' === $this->settings->get_return_shipment_type();
	}

	private function get_return_service_type(): string {
		$service  = $this->shipment_data->get_service();
		$country  = $this->shipment_data->get_shipping_country();
		$services = ( new DhlServices() )->get_international_services();

		if ( 'PL' !== $country || isset( $services[ $service ] ) ) {
			return self::RETURN_SERVICE_CONNECT;
		}

		return self::RETURN_SERVICE_DOMESTIC;
	}

	private function is_international_return(): bool {
		$country = strtoupper( trim( (string) $this->shipment_data->get_shipping_country() ) );
		if ( '' === $country ) {
			return false;
		}

		return self::SETTINGS_COUNTRY !== $country;
	}

	private function get_ship_data(): ReturnShip {
		return new ReturnShip(
			$this->get_shipper_data(),
			$this->get_receiver_data()
		);
	}

	private function get_shipper_data(): ReturnAddressat {
		return new ReturnAddressat(
			$this->get_shipper_contact(),
			$this->get_shipper_contact(),
			$this->get_shipper_address()
		);
	}

	private function get_receiver_data(): ReturnAddressat {
		return new ReturnAddressat(
			$this->get_receiver_contact(),
			$this->get_receiver_contact(),
			$this->get_receiver_address()
		);
	}

	private function get_shipper_contact(): PreavisoContact {
		$name  = $this->get_shipping_name();
		$phone = (string) $this->order->get_billing_phone();
		$email = (string) $this->order->get_billing_email();

		return new PreavisoContact(
			'' !== $name ? $name : null,
			'' !== $phone ? $phone : null,
			'' !== $email ? $email : null
		);
	}

	private function get_receiver_contact(): PreavisoContact {
		$name  = (string) $this->settings->get_sender_name();
		$phone = (string) $this->settings->get_sender_phone();
		$email = (string) $this->settings->get_sender_email();

		return new PreavisoContact(
			'' !== $name ? $name : null,
			'' !== $phone ? $phone : null,
			'' !== $email ? $email : null
		);
	}

	private function get_shipper_address(): ShipperAddress {
		$country                = $this->shipment_data->get_shipping_country();
		$address                = $this->get_shipping_address_parts();
		$postal_code_normalizer = new PostalCodeNormalizer();

		return new ShipperAddress(
			$country,
			$this->get_shipping_name(),
			$postal_code_normalizer->normalize( $address['postcode'], (string) $country ),
			$address['city'],
			$address['street'],
			$address['house_number'],
			$address['apartment_number']
		);
	}

	private function get_receiver_address(): ShipperAddress {
		$country                = self::SETTINGS_COUNTRY;
		$postal_code_normalizer = new PostalCodeNormalizer();
		$postcode               = $postal_code_normalizer->normalize( (string) $this->settings->get_sender_postcode(), $country );

		return new ShipperAddress(
			$country,
			(string) $this->settings->get_sender_name(),
			$postcode,
			(string) $this->settings->get_sender_city(),
			(string) $this->settings->get_sender_street(),
			(string) $this->settings->get_sender_house_number()
		);
	}

	/**
	 * @return array{street: string, house_number: string, apartment_number: string|null, city: string, postcode: string}
	 */
	private function get_shipping_address_parts(): array {
		$address_1 = trim( (string) $this->order->get_shipping_address_1() );
		$address_2 = trim( (string) $this->order->get_shipping_address_2() );

		if ( '' === $address_1 ) {
			$address_1 = trim( (string) $this->order->get_billing_address_1() );
			$address_2 = trim( (string) $this->order->get_billing_address_2() );
		}

		$parts        = preg_split( '/\s+/', $address_1 );
		$house_number = (string) array_pop( $parts );
		$street       = trim( implode( ' ', $parts ) );

		if ( '' === $street ) {
			$street       = $house_number;
			$house_number = '';
		}

		$city     = (string) $this->order->get_shipping_city();
		$postcode = (string) $this->order->get_shipping_postcode();
		if ( '' === $city ) {
			$city     = (string) $this->order->get_billing_city();
			$postcode = (string) $this->order->get_billing_postcode();
		}

		return [
			'street'           => $street,
			'house_number'     => $house_number,
			'apartment_number' => '' !== $address_2 ? $address_2 : null,
			'city'             => $city,
			'postcode'         => $postcode,
		];
	}

	private function get_shipping_name(): string {
		$company = (string) $this->order->get_shipping_company();
		if ( '' !== $company ) {
			return $company;
		}

		$name = trim( $this->order->get_shipping_first_name() . ' ' . $this->order->get_shipping_last_name() );
		if ( '' !== $name ) {
			return $name;
		}

		$company = (string) $this->order->get_billing_company();
		if ( '' !== $company ) {
			return $company;
		}

		return trim( $this->order->get_billing_first_name() . ' ' . $this->order->get_billing_last_name() );
	}

	/**
	 * @return ReturnPackage[]
	 */
	private function get_pieces_data(): array {
		$pieces = [];

		foreach ( $this->shipment_data->get_packages() as $package ) {
			$type     = (string) ( $package['type'] ?? 'PACKAGE' );
			$quantity = (int) ( $package['quantity'] ?? 1 );

			$weight = $this->get_weight_in_kg( (float) ( $package['weight'] ?? 0 ) );
			$width  = $this->get_dimension_in_cm( (float) ( $package['width'] ?? 0 ) );
			$height = $this->get_dimension_in_cm( (float) ( $package['height'] ?? 0 ) );
			$length = $this->get_dimension_in_cm( (float) ( $package['length'] ?? 0 ) );

			$pieces[] = new ReturnPackage(
				$type,
				$quantity,
				null !== $weight ? (int) round( $weight ) : null,
				$width,
				$height,
				$length
			);
		}

		return $pieces;
	}
}
