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

namespace DhlPolska\Shipment;

use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\AgeverService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\EuroReturnService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\NadSobotaService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\NstService;
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\Services\RodService;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\Service1722;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\Services\SobotaService;
use DhlPolska\SanitizeTrait;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use DhlPolska\Shipment\DhlServices;
use DhlPolska\Shipment\WorkingDayDateCalculator;
use WC_Order;
use WPDesk_Flexible_Shipping_Shipment;

/**
 * Shipment data.
 */
class ShipmentData {
	use SanitizeTrait;

	public const OPTION_SERVICE               = 'service';
	public const OPTION_TEMPLATE              = 'template_id';
	public const OPTION_TEMPLATE_SERVICES     = 'template_services';
	public const OPTION_COD                   = 'cod';
	public const OPTION_COD_AMOUNT            = 'cod_amount';
	public const OPTION_INSURANCE             = 'insurance';
	public const OPTION_INSURANCE_AMOUNT      = 'insurance_amount';
	public const OPTION_REFERENCES            = 'references';
	public const OPTION_NOTES                 = 'notes';
	public const OPTION_PACKAGES              = 'packages';
	public const OPTION_PARCELSHOP_ID         = 'parcelshop_id';
	public const OPTION_PARCELSHOP_SAP        = 'parcelshop_sap';
	public const OPTION_PARCELSHOP_NAME       = 'parcelshop_name';
	public const OPTION_PARCELSHOP_ZIP        = 'parcelshop_zip';
	public const OPTION_PARCELSHOP_STREET     = 'parcelshop_street';
	public const OPTION_PARCELSHOP_CITY       = 'parcelshop_city';
	public const OPTION_PARCELSHOP_STREET_NO  = 'parcelshop_streetNo';
	public const OPTION_PARCELSHOP_HOUSE_NO   = 'parcelshop_houseNo';
	public const OPTION_PARCELSHOP_POSTNUMMER = 'parcelshop_postnummer';
	public const OPTION_POINT_TYPE            = 'point_type';
	public const OPTION_POINT_TYPE_PL         = 'point_type_pl';
	public const POINT_TYPE_PARCELSHOP        = 'parcelShop';
	public const POINT_TYPE_PACKSTATION       = 'packStation';
	public const POINT_TYPE_PL_ALL            = 'all';
	public const POINT_TYPE_PL_BOX            = self::POINT_TYPE_PACKSTATION;
	public const POINT_TYPE_PL_POP            = self::POINT_TYPE_PARCELSHOP;
	public const OPTION_SHIPMENT_ID           = 'shipment_id';
	public const OPTION_PARCEL_NUMBER         = 'parcel_number';
	public const OPTION_COURIER_BOOKING_ID    = 'courier_booking_id';
	public const OPTION_SHIPMENT_DATE         = 'shipment_date';
	public const OPTION_RETURN_LABEL_CONTENT  = 'return_label_content';
	public const OPTION_RETURN_LABEL_FORMAT   = 'return_label_format';
	public const OPTION_RETURN_LABEL_TYPE     = 'return_label_type';
	public const OPTION_RETURN_LABEL_NUMBER   = 'return_label_number';
	public const FIELD_ORDER_WEIGHT           = 'order_weight';
	public const TEMPLATE_SERVICE_1722        = 'SERVICE_' . Service1722::TYPE;
	public const TEMPLATE_SERVICE_SOBOTA      = SobotaService::TYPE;
	public const TEMPLATE_SERVICE_NAD_SOBOTA  = NadSobotaService::TYPE;
	public const TEMPLATE_SERVICE_INSURANCE   = self::OPTION_INSURANCE;
	public const TEMPLATE_SERVICE_COD         = self::OPTION_COD;
	public const TEMPLATE_SERVICE_PDI         = PdiService::TYPE;
	public const TEMPLATE_SERVICE_ROD         = RodService::TYPE;
	public const TEMPLATE_SERVICE_POD         = PodService::TYPE;
	public const TEMPLATE_SERVICE_AGEVER      = AgeverService::TYPE;
	public const TEMPLATE_SERVICE_NST         = NstService::TYPE;
	public const TEMPLATE_SERVICE_EURO_RETURN = EuroReturnService::TYPE;
	public const TEMPLATE_SERVICE_KEYS        = [
		self::TEMPLATE_SERVICE_1722,
		self::TEMPLATE_SERVICE_SOBOTA,
		self::TEMPLATE_SERVICE_NAD_SOBOTA,
		self::TEMPLATE_SERVICE_INSURANCE,
		self::TEMPLATE_SERVICE_COD,
		self::TEMPLATE_SERVICE_PDI,
		self::TEMPLATE_SERVICE_ROD,
		self::TEMPLATE_SERVICE_POD,
		self::TEMPLATE_SERVICE_AGEVER,
		self::TEMPLATE_SERVICE_NST,
		self::TEMPLATE_SERVICE_EURO_RETURN,
	];

	/**
	 * @var WPDesk_Flexible_Shipping_Shipment
	 */
	private $shipment;

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

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

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

	/**
	 * @return string
	 */
	public function get_service(): string {
		return (string) $this->shipment->get_meta( self::OPTION_SERVICE, DhlServices::SERVICE_AH ); // AH = DHL Parcel Polska
	}

	/**
	 * @return bool
	 */
	public function get_cod_status(): bool {
		return $this->get_bool_value( $this->shipment->get_meta( self::OPTION_COD, 'no' ) );
	}

	/**
	 * @return float
	 */
	public function get_cod_amount(): float {
		$amount = $this->shipment->get_meta( self::OPTION_COD_AMOUNT );
		if ( empty( $amount ) ) {
			return (float) $this->order->get_total();
		}

		return $this->get_float_value( (string) $amount );
	}

	/**
	 * @return bool
	 */
	public function get_insurance_status(): bool {
		return $this->get_bool_value( $this->shipment->get_meta( self::OPTION_INSURANCE, 'no' ) );
	}

	/**
	 * @return float
	 */
	public function get_insurance_amount(): float {
		$amount = $this->shipment->get_meta( self::OPTION_INSURANCE_AMOUNT );
		if ( empty( $amount ) ) {
			return (float) $this->order->get_total();
		}

		return $this->get_float_value( (string) $amount );
	}

	/**
	 * @return array<string, bool>
	 */
	public function get_template_services(): array {
		$services = $this->shipment->get_meta( self::OPTION_TEMPLATE_SERVICES, [] ); /** @phpstan-ignore-line */
		if ( ! is_array( $services ) ) {
			$services = [];
		}

		$normalized_template_services = [];
		foreach ( self::TEMPLATE_SERVICE_KEYS as $service_key ) {
			$normalized_template_services[ $service_key ] = $this->get_bool_value( $services[ $service_key ] ?? false );
		}

		return $normalized_template_services;
	}

	/**
	 * @return string
	 */
	public function get_references(): string {
		return (string) $this->shipment->get_meta( self::OPTION_REFERENCES, $this->get_default_references() );
	}

	/**
	 * @return string
	 */
	public function get_notes(): string {
		return (string) $this->shipment->get_meta( self::OPTION_NOTES, '' );
	}

	/**
	 * @return string
	 */
	public function get_content(): string {
		return (string) apply_filters( 'dhl_polska_shipment_content', __( 'Przesyłka', 'dhl-polska' ), $this->shipment );
	}

	/**
	 * @return string
	 */
	public function get_service_point(): string {
		$service_point = (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_SAP, '' );
		if ( '' !== $service_point ) {
			return $service_point;
		}

		return (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_ID, '' );
	}

	/**
	 * @return array
	 */
	public function get_parcelshop_data(): array {
		return [
			'id'         => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_ID, '' ),
			'sap'        => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_SAP, '' ),
			'name'       => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_NAME, '' ),
			'zip'        => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_ZIP, '' ),
			'street'     => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_STREET, '' ),
			'city'       => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_CITY, '' ),
			'streetNo'   => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_STREET_NO, '' ),
			'houseNo'    => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_HOUSE_NO, '' ),
			'postnummer' => (string) $this->shipment->get_meta( self::OPTION_PARCELSHOP_POSTNUMMER, '' ),
		];
	}

	public function get_point_type(): string {
		$point_type = (string) $this->shipment->get_meta( self::OPTION_POINT_TYPE, self::POINT_TYPE_PARCELSHOP );

		return self::normalize_point_type( $point_type );
	}

	/**
	 * @param string $point_type .
	 *
	 * @return string
	 */
	public static function normalize_point_type( string $point_type ): string {
		$normalized_point_type = strtolower( trim( $point_type ) );
		if ( 'parcelshop' === $normalized_point_type ) {
			return self::POINT_TYPE_PARCELSHOP;
		}

		if ( 'packstation' === $normalized_point_type ) {
			return self::POINT_TYPE_PACKSTATION;
		}

		return $point_type;
	}

	public static function normalize_point_type_pl( string $point_type ): string {
		$normalized_point_type_pl = strtolower( trim( $point_type ) );

		if ( 'packstation' === $normalized_point_type_pl ) {
			return self::POINT_TYPE_PL_BOX;
		}

		if ( 'parcelshop' === $normalized_point_type_pl ) {
			return self::POINT_TYPE_PL_POP;
		}

		if ( in_array( $normalized_point_type_pl, [ '', self::POINT_TYPE_PL_ALL, 'all_points', 'all-points', 'allpoints' ], true ) ) {
			return self::POINT_TYPE_PL_ALL;
		}

		return self::POINT_TYPE_PL_ALL;
	}

	/**
	 * @param array<string, mixed> $fs_method .
	 * @param string               $shipping_country .
	 *
	 * @return string
	 */
	public static function resolve_point_type_from_method( array $fs_method, string $shipping_country ): string {
		$shipping_country = strtoupper( trim( $shipping_country ) );

		if ( 'PL' === $shipping_country ) {
			return self::resolve_point_type_for_pl( $fs_method );
		}

		return self::resolve_point_type_for_non_pl( $fs_method );
	}

	/**
	 * @param array<string, mixed> $fs_method .
	 *
	 * @return string
	 */
	private static function resolve_point_type_for_non_pl( array $fs_method ): string {
		$point_type = $fs_method[ self::OPTION_POINT_TYPE ] ?? ( $fs_method['dhl_polska_point_type'] ?? self::POINT_TYPE_PARCELSHOP );

		return self::normalize_point_type( (string) $point_type );
	}

	/**
	 * @param array<string, mixed> $fs_method .
	 *
	 * @return string
	 */
	private static function resolve_point_type_for_pl( array $fs_method ): string {
		if (
			array_key_exists( self::OPTION_POINT_TYPE_PL, $fs_method )
			|| array_key_exists( 'dhl_polska_point_type_pl', $fs_method )
		) {
			$point_type_pl = $fs_method[ self::OPTION_POINT_TYPE_PL ] ?? ( $fs_method['dhl_polska_point_type_pl'] ?? self::POINT_TYPE_PL_ALL );
			$point_type_pl = self::normalize_point_type_pl( (string) $point_type_pl );
			if ( self::POINT_TYPE_PL_ALL === $point_type_pl ) {
				return '';
			}

			return $point_type_pl;
		}

		if ( array_key_exists( self::OPTION_POINT_TYPE, $fs_method ) || array_key_exists( 'dhl_polska_point_type', $fs_method ) ) {
			$point_type = $fs_method[ self::OPTION_POINT_TYPE ] ?? ( $fs_method['dhl_polska_point_type'] ?? self::POINT_TYPE_PARCELSHOP );

			return self::normalize_point_type( (string) $point_type );
		}

		return '';
	}

	/**
	 * @return string
	 */
	public function get_shipment_id(): string {
		return (string) $this->shipment->get_meta( self::OPTION_SHIPMENT_ID, '' );
	}

	/**
	 * @return string
	 */
	public function get_tracking_number(): string {
		return (string) $this->shipment->get_meta( self::OPTION_PARCEL_NUMBER, '' );
	}

	/**
	 * @return string
	 */
	public function get_courier_booking_id(): string {
		return trim( (string) $this->shipment->get_meta( self::OPTION_COURIER_BOOKING_ID, '' ) );
	}

	public function get_shipment_date(): string {
		return (string) $this->shipment->get_meta( self::OPTION_SHIPMENT_DATE, '' );
	}

	public function get_shipping_country(): string {
		$shipping_country = (string) $this->order->get_shipping_country();
		if ( '' !== $shipping_country ) {
			return $shipping_country;
		}

		return (string) $this->order->get_billing_country();
	}

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

		return $this->get_default_shipment_date();
	}

	public function get_default_shipment_date(): string {
		$calculator             = new WorkingDayDateCalculator();
		$shipment_date_fallback = new ShipmentDateFallback();
		$cutoff                 = $this->shipping_settings->get_shipment_date_cutoff_time();
		$date                   = $calculator->get_date_after_cutoff( $cutoff );

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

		if ( $this->get_service() === DhlServices::SERVICE_POINT ) {
			return $shipment_date_fallback->today();
		}

		return $shipment_date_fallback->tomorrow();
	}

	/**
	 * @return array
	 */
	public function get_packages(): array {
		// @phpstan-ignore argument.type
		$order_packages = $this->shipment->get_meta( self::OPTION_PACKAGES, [] );

		if ( empty( $order_packages ) || ! is_array( $order_packages ) ) {
			return [
				[
					'weight' => $this->shipping_settings->get_default_weight(),
					'type'   => 'PACKAGE',
				],
			];
		}

		return $order_packages;
	}

	/**
	 * @return float
	 */
	public function get_weight(): float {
		$weight = 0.0;

		foreach ( $this->get_packages() as $package ) {
			$weight += (float) ( $package['weight'] ?? 0.0 );
		}

		return $weight;
	}

	/**
	 * @return string
	 */
	private function get_default_references(): string {
		$default_references = sprintf(
			// Translators: order number.
			__( 'Zamówienie #%d', 'dhl-polska' ),
			$this->order->get_order_number()
		);

		return (string) apply_filters( 'dhl_polska_shipment_default_references', $default_references, $this->shipment );
	}
}
