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

namespace DhlPolska\Shipment;

use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Address;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Addressat;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\BillingData;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\AgeverService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\CodService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\CreateShipmentRequest;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\InsuranceService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\NadSobotaService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\PdiService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\PodService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\PieceDefinition;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\PreavisoContact;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReceiverAddress;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReceiverAddressat;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\RodService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\Service1722;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Ship;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ShipmentInfo;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ShipmentTime;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\SobotaService;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use WC_Order;

/**
 * Prepares createShipment request data for DHL WebAPI.
 */
class PrepareCreateShipmentRequest {
	use UnitsConversionTrait;

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

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

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

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

	private function get_shipment_info(): ShipmentInfo {
		return new ShipmentInfo(
			'REGULAR_PICKUP',
			$this->get_service_type(),
			$this->get_billing_data(),
			$this->get_shipment_time(),
			$this->get_label_type(),
			$this->get_special_services()
		);
	}

	private function get_service_type(): string {
		$selected_service = $this->shipment_data->get_service();
		if ( $selected_service === DhlServices::SERVICE_POINT ) {
			$shipping_country = strtoupper( (string) $this->shipment_data->get_shipping_country() );

			return 'PL' === $shipping_country ? 'SP' : DhlServices::SERVICE_EK;
		}

		return $selected_service;
	}

	private function get_label_type(): string {
		$label_type = strtoupper( (string) $this->settings->get_label_mode() );
		if ( '' === $label_type ) {
			return 'BLP';
		}

		return $label_type;
	}

	private function get_shipment_time(): ShipmentTime {
		$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 new ShipmentTime( $this->get_shipment_date(), $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();
		$shipment_date_fallback = new ShipmentDateFallback();
		$cutoff                 = $this->settings->get_shipment_date_cutoff_time();
		$date                   = $calculator->get_date_after_cutoff( $cutoff );

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

		return $shipment_date_fallback->tomorrow();
	}

	private function get_ship_data(): Ship {
		$postal_code_normalizer = new PostalCodeNormalizer();

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

		$shipper_contact = $this->create_preaviso_contact(
			(string) $this->settings->get_sender_name(),
			(string) $this->settings->get_sender_phone(),
			(string) $this->settings->get_sender_email()
		);

		$shipper = new Addressat( $shipper_contact, $shipper_contact, $shipper_address );

		$receiver_address = $this->get_receiver_address();
		$receiver_contact = $this->create_preaviso_contact(
			$this->get_receiver_contact_name(),
			(string) $this->order->get_billing_phone(),
			(string) $this->order->get_billing_email()
		);
		$receiver         = new ReceiverAddressat( $receiver_contact, $receiver_contact, $receiver_address );

		return new Ship( $shipper, $receiver, null, $this->get_service_point_account_number() );
	}

	private function get_receiver_address(): ReceiverAddress {
		$name = $this->get_receiver_contact_name();
		if ( $this->order->get_shipping_company() ) {
			$name = (string) $this->order->get_shipping_company();
		}

		$shipping_country               = strtoupper( (string) $this->shipment_data->get_shipping_country() );
		$is_international_service_point = $this->shipment_data->get_service() === DhlServices::SERVICE_POINT && 'PL' !== $shipping_country;

		$postal_code_normalizer = new PostalCodeNormalizer();

		$postal_code  = (string) $this->order->get_shipping_postcode();
		$city         = (string) $this->order->get_shipping_city();
		$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 = ' ';
		}

		if ( $is_international_service_point ) {
			$address_data = $this->apply_international_point_overrides(
				[
					'postal_code'  => $postal_code,
					'city'         => $city,
					'street'       => $street,
					'house_number' => $house_number,
				]
			);
			$postal_code  = $address_data['postal_code'];
			$city         = $address_data['city'];
			$street       = $address_data['street'];
			$house_number = $address_data['house_number'];
		}

		$is_packstation = null;
		$is_postfiliale = null;
		$postnummer     = null;
		if ( $is_international_service_point ) {
			if ( ShipmentData::POINT_TYPE_PACKSTATION === $this->shipment_data->get_point_type() ) {
				$is_packstation = true;
			} else {
				$is_postfiliale = true;
			}
		}

		if ( $is_packstation && 'DE' === $shipping_country ) {
			$parcelshop_data = $this->shipment_data->get_parcelshop_data();
			$postnummer      = trim( (string) ( $parcelshop_data['postnummer'] ?? '' ) );
			if ( '' === $postnummer ) {
				$postnummer = null;
			}
		}

		$address_type = $this->order->get_shipping_company() ? 'B' : 'C';
		if ( $this->shipment_data->get_service() === DhlServices::SERVICE_POINT ) {
			$address_type = 'C';
		}

		return new ReceiverAddress(
			$name,
			$postal_code_normalizer->normalize( $postal_code, $shipping_country ),
			$city,
			$street,
			$house_number,
			(string) $this->order->get_shipping_country(),
			$address_type,
			null,
			$is_packstation,
			$is_postfiliale,
			$postnummer
		);
	}

	private function get_receiver_contact_name(): string {
		return trim( $this->order->get_shipping_first_name() . ' ' . $this->order->get_shipping_last_name() );
	}

	/**
	 * @param array{postal_code: string, city: string, street: string, house_number: string} $address_data .
	 *
	 * @return array{postal_code: string, city: string, street: string, house_number: string}
	 */
	private function apply_international_point_overrides( array $address_data ): array {
		$parcelshop_data = $this->shipment_data->get_parcelshop_data();
		$point_street    = trim( (string) ( $parcelshop_data['street'] ?? '' ) );
		$point_street_no = trim( (string) ( $parcelshop_data['streetNo'] ?? '' ) );
		$point_city      = trim( (string) ( $parcelshop_data['city'] ?? '' ) );
		$point_zip       = trim( (string) ( $parcelshop_data['zip'] ?? '' ) );

		if ( '' !== $point_street || '' !== $point_street_no ) {
			$address_data['street'] = trim( $point_street . ' ' . $point_street_no );
		}

		if ( '' !== $point_city ) {
			$address_data['city'] = $point_city;
		}

		if ( '' !== $point_zip ) {
			$address_data['postal_code'] = $point_zip;
		}

		$service_point = $this->shipment_data->get_service_point();
		if ( '' !== $service_point ) {
			$address_data['house_number'] = $service_point;
		}

		return $address_data;
	}

	private function create_preaviso_contact( string $person_name, string $phone_number, string $email_address ): ?PreavisoContact {
		$person_name   = trim( $person_name );
		$phone_number  = trim( $phone_number );
		$email_address = trim( $email_address );

		if ( '' === $person_name && '' === $phone_number && '' === $email_address ) {
			return null;
		}

		return new PreavisoContact(
			'' === $person_name ? null : $person_name,
			'' === $phone_number ? null : $phone_number,
			'' === $email_address ? null : $email_address
		);
	}

	private function get_billing_data(): BillingData {
		return new BillingData(
			'SHIPPER',
			(int) $this->settings->get_webapi_sap_number(),
			'BANK_TRANSFER'
		);
	}

	private function get_service_point_account_number(): ?string {
		if ( $this->shipment_data->get_service() !== DhlServices::SERVICE_POINT ) {
			return null;
		}

		$shipping_country = strtoupper( (string) $this->shipment_data->get_shipping_country() );
		if ( 'PL' !== $shipping_country ) {
			return null;
		}

		$service_point = trim( $this->shipment_data->get_service_point() );
		if ( '' !== $service_point ) {
			return $service_point;
		}

		$parcelshop_data = $this->shipment_data->get_parcelshop_data();

		return '' !== (string) ( $parcelshop_data['id'] ?? '' ) ? (string) $parcelshop_data['id'] : null;
	}

	/**
	 * @return \DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Service[]
	 */
	private function get_special_services(): array {
		$services = [];

		if ( $this->shipment_data->get_cod_status() ) {
			$services[] = new CodService(
				$this->shipment_data->get_cod_amount(),
				'BANK_TRANSFER'
			);
		}

		if ( $this->shipment_data->get_insurance_status() ) {
			$services[] = new InsuranceService( $this->shipment_data->get_insurance_amount() );
		}

		foreach ( $this->get_template_services() as $service ) {
			$services[] = $service;
		}

		return $services;
	}

	/**
	 * @return \DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Service[]
	 */
	private function get_template_services(): array {
		$template_services = $this->shipment_data->get_template_services();
		$services          = [];

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_1722 ] ) ) {
			$services[] = new Service1722();
		}

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_SOBOTA ] ) ) {
			$services[] = new SobotaService();
		}

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_NAD_SOBOTA ] ) ) {
			$services[] = new NadSobotaService();
		}

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_PDI ] ) ) {
			$services[] = new PdiService();
		}

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_ROD ] ) ) {
			$services[] = new RodService();
		}

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_POD ] ) ) {
			$services[] = new PodService();
		}

		if ( ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_AGEVER ] ) ) {
			$services[] = new AgeverService();
		}

		return $services;
	}

	/**
	 * @return PieceDefinition[]
	 */
	private function get_pieces_data(): array {
		$pieces            = [];
		$template_services = $this->shipment_data->get_template_services();
		$non_standard      = ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_NST ] ) ? true : null;
		$euro_return       = ! empty( $template_services[ ShipmentData::TEMPLATE_SERVICE_EURO_RETURN ] ) ? true : null;

		foreach ( $this->shipment_data->get_packages() as $package ) {
			$weight = $this->get_weight_in_kg( (float) ( $package['weight'] ?? 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,
				$non_standard,
				null,
				$euro_return
			);
		}

		return $pieces;
	}
}
