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

namespace DhlPolska\Shipment;

use DhlPolska\API\ParcelShop\Struct\AddressData;
use DhlPolska\API\ParcelShop\Struct\BillingData;
use DhlPolska\API\ParcelShop\Struct\ContactData;
use DhlPolska\API\ParcelShop\Struct\PieceDefinition;
use DhlPolska\API\ParcelShop\Struct\Receiver;
use DhlPolska\API\ParcelShop\Struct\Ship;
use DhlPolska\API\ParcelShop\Struct\ShipmentData as ParcelShopShipmentData;
use DhlPolska\API\ParcelShop\Struct\ShipmentInfo;
use DhlPolska\API\ParcelShop\Struct\Shipper;
use DhlPolska\API\ParcelShop\Struct\SpecialService;
use DhlPolska\Shipment\WorkingDayDateCalculator;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use WC_Order;

/**
 * Prepares shipment data for DHL ParcelShop API.
 */
class PrepareParcelShopShipmentData {
	use UnitsConversionTrait;

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

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

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

	/**
	 * PrepareParcelShopShipmentData 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 ParcelShopShipmentData
	 */
	public function get_prepared_data(): ParcelShopShipmentData {
		return new ParcelShopShipmentData(
			$this->get_ship_data(),
			$this->get_shipment_info_data(),
			$this->get_pieces_data(),
			$this->shipment_data->get_content(),
			$this->shipment_data->get_notes(),
			$this->shipment_data->get_references()
		);
	}

	/**
	 * @return Ship
	 */
	private function get_ship_data(): Ship {
		return new Ship(
			$this->get_shipper_data(),
			$this->get_receiver_data(),
			$this->shipment_data->get_service_point()
		);
	}

	/**
	 * @return Shipper
	 */
	private function get_shipper_data(): Shipper {
		$postal_code_normalizer = new PostalCodeNormalizer();

		$address = new AddressData(
			(string) $this->settings->get_sender_name(),
			(string) $this->settings->get_sender_street(),
			(string) $this->settings->get_sender_house_number(),
			(string) $this->settings->get_sender_city(),
			$postal_code_normalizer->normalize( (string) $this->settings->get_sender_postcode(), 'PL' )
		);

		$contact = new ContactData(
			(string) $this->settings->get_sender_name(),
			(string) $this->settings->get_sender_phone(),
			(string) $this->settings->get_sender_email()
		);

		return new Shipper( $address, $contact );
	}

	/**
	 * @return Receiver
	 */
	private function get_receiver_data(): Receiver {
		$postal_code_normalizer = new PostalCodeNormalizer();
		$shipping_country       = (string) $this->order->get_shipping_country();

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

		$full_address = trim( $this->order->get_shipping_address_1() . ' ' . $this->order->get_shipping_address_2() );
		$parts        = preg_split( '/\s+/', $full_address );

		$house_number = array_pop( $parts );
		$street       = implode( ' ', $parts );

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

		$address = new AddressData(
			(string) $name,
			(string) $street,
			(string) $house_number,
			(string) $this->order->get_shipping_city(),
			$postal_code_normalizer->normalize( (string) $this->order->get_shipping_postcode(), $shipping_country )
		);

		$contact = new ContactData(
			(string) $this->order->get_shipping_first_name() . ' ' . $this->order->get_shipping_last_name(),
			(string) $this->order->get_billing_phone(),
			(string) $this->order->get_billing_email()
		);

		return new Receiver( $address, $contact );
	}

	/**
	 * @return ShipmentInfo
	 */
	private function get_shipment_info_data(): ShipmentInfo {
		$billing = new BillingData(
			'SHIPPER',
			(int) $this->settings->get_webapi_sap_number(),
			'BANK_TRANSFER'
		);

		[ $pickup_start, $pickup_end ] = $this->get_pickup_window();

		$special_services = [];
		if ( $this->shipment_data->get_cod_status() ) {
			$special_services[] = new SpecialService(
				SpecialService::TYPE_COD,
				(int) $this->shipment_data->get_cod_amount(),
				null,
				'BANK_TRANSFER'
			);
		}

		if ( $this->shipment_data->get_insurance_status() ) {
			$special_services[] = new SpecialService(
				SpecialService::TYPE_UBEZP,
				(int) $this->shipment_data->get_insurance_amount()
			);
		}

		return new ShipmentInfo(
			'REGULAR_PICKUP',
			'LM',
			$billing,
			$this->get_shipment_date(),
			$pickup_start,
			$pickup_end,
			strtoupper( (string) $this->settings->get_label_mode() ) ?: 'BLP',
			$special_services
		);
	}

	/**
	 * @return string[]
	 */
	private function get_pickup_window(): array {
		$pickup_start = trim( $this->settings->get_courier_pickup_start() );
		$pickup_end   = trim( $this->settings->get_courier_pickup_end() );

		if ( '' === $pickup_start ) {
			$pickup_start = '10:00';
		}

		if ( '' === $pickup_end ) {
			$pickup_end = '16:00';
		}

		return [ $pickup_start, $pickup_end ];
	}

	private function get_shipment_date(): string {
		$shipment_date = trim( $this->shipment_data->get_shipment_date() );
		if ( '' !== $shipment_date ) {
			return $shipment_date;
		}

		$calculator = new WorkingDayDateCalculator();
		$cutoff     = $this->settings->get_shipment_date_cutoff_time();
		$date       = $calculator->get_date_after_cutoff( $cutoff );

		if ( null !== $date ) {
			return $date;
		}

		return date( 'Y-m-d' );
	}

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

		foreach ( $this->shipment_data->get_packages() as $package ) {
			$weight = $this->get_weight_in_kg( (float) ( $package['weight'] ?? 0.0 ) );
			if ( null !== $weight ) {
				$weight = (float) ceil( $weight );
			}
			$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 PieceDefinition(
				$package['type'] ?? 'PACKAGE',
				(int) ( $package['quantity'] ?? 1 ),
				$weight,
				$width,
				$height,
				$length
			);
		}

		return $pieces;
	}
}
