<?php
/**
 * Class WebAPI
 *
 * @package DhlPolska\API
 */

namespace DhlPolska\API;

use DhlPolska\API\WebApi\Method\CreateShipment;
use DhlPolska\API\WebApi\Method\DeleteShipments;
use DhlPolska\API\WebApi\Method\GetLabels;
use DhlPolska\API\WebApi\Method\GetPnp;
use DhlPolska\API\WebApi\Method\GetPostalCodeServices;
use DhlPolska\API\WebApi\Method\GetReturnByWaybill;
use DhlPolska\API\WebApi\Method\BookCourier;
use DhlPolska\API\WebApi\Method\CancelCourierBooking;
use DhlPolska\API\WebApi\Method\CreateShipmentReturn;
use DhlPolska\API\WebApi\Struct\ItemToPrint;
use DhlPolska\API\WebApi\Struct\CreateReturnShipmentRequest;
use DhlPolska\API\WebApi\Struct\CreateShipmentRequest;
use DhlPolska\API\WebApi\Struct\ReturnItemToPrint;
use DhlPolska\API\WebApi\Struct\ShipmentOrderInfo;
use DhlPolska\API\WebApi\SoapClient as WebApiSoapClient;
use DhlPolskaVendor\Psr\Log\LoggerInterface;
use DhlPolskaVendor\Psr\Log\NullLogger;
use SoapClient;

/**
 * WebAPI DHL24.
 */
class WebAPI {

	public const API = 'WebApi';

	public const TEST_WSDL = 'https://sandbox.dhl24.com.pl/webapi2';
	public const PROD_WSDL = 'https://dhl24.com.pl/webapi2';

	/**
	 * @var AuthDataInterface
	 */
	protected AuthDataInterface $auth_data;

	/**
	 * @var string
	 */
	protected string $sap;

	/**
	 * @var bool
	 */
	protected bool $test_mode;

	/**
	 * @var LoggerInterface
	 */
	protected LoggerInterface $logger;

	/**
	 * @var SoapClient|WebApiSoapClient|null
	 */
	protected ?SoapClient $client = null;

	/**
	 * WebAPI constructor.
	 *
	 * @param AuthDataInterface    $auth_data .
	 * @param string               $sap       .
	 * @param bool                 $test_mode .
	 * @param LoggerInterface|null $logger    .
	 */
	public function __construct( AuthDataInterface $auth_data, string $sap, bool $test_mode, ?LoggerInterface $logger = null ) {
		$this->auth_data = $auth_data;
		$this->sap       = $sap;
		$this->test_mode = $test_mode;
		$this->logger    = $logger ?? new NullLogger();
	}

	/**
	 * @return string
	 */
	protected function get_wsdl(): string {
		return $this->test_mode ? static::TEST_WSDL : static::PROD_WSDL;
	}

	/**
	 * @return SoapClient|WebApiSoapClient
	 */
	protected function get_client(): SoapClient {
		if ( ! $this->client ) {
			$options      = [
				'stream_context' => stream_context_create(
					[
						'http' => [
							'header' => 'DHLApiOrgin: Octolize',
						],
					]
				),
			];
			$this->client = $this->create_soap_client( $this->get_wsdl(), $options );
		}

		return $this->client;
	}

	/**
	 * @param string $wsdl
	 * @param array  $options
	 *
	 * @return SoapClient|WebApiSoapClient
	 * @codeCoverageIgnore
	 */
	protected function create_soap_client( string $wsdl, array $options ): SoapClient {
		return new WebApiSoapClient( static::API, $wsdl, $options, $this->logger );
	}

	/**
	 * Check connection.
	 *
	 * @throws \Exception .
	 */
	public function check_connection() {
		$this->get_client()->getPostalCodeServices(
			new GetPostalCodeServices(
				$this->auth_data,
				'00001',
				date( 'Y-m-d' )
			)
		);
	}

	/**
	 * @param string                $pickup_date .
	 * @param string                $pickup_time_from .
	 * @param string                $pickup_time_to .
	 * @param string[]              $shipment_ids .
	 * @param ShipmentOrderInfo|null $shipment_order_info .
	 * @param string                $additional_info .
	 * @param bool|null             $courier_with_label .
	 *
	 * @return string[]
	 */
	public function book_courier(
		string $pickup_date,
		string $pickup_time_from,
		string $pickup_time_to,
		array $shipment_ids,
		?ShipmentOrderInfo $shipment_order_info = null,
		string $additional_info = '',
		?bool $courier_with_label = null
	): array {
		$response = $this->get_client()->bookCourier(
			new BookCourier(
				$this->auth_data,
				$pickup_date,
				$pickup_time_from,
				$pickup_time_to,
				$additional_info,
				$shipment_ids,
				$shipment_order_info,
				$courier_with_label
			)
		);

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$result = $response->bookCourierResult->item ?? [];
		if ( is_string( $result ) ) {
			return [ $result ];
		}

		if ( is_array( $result ) ) {
			return array_values( $result );
		}

		return [];
	}

	/**
	 * @param string[] $booking_ids .
	 *
	 * @return void
	 */
	public function cancel_courier_booking( array $booking_ids ): void {
		if ( empty( $booking_ids ) ) {
			return;
		}

		foreach ( $booking_ids as $booking_id ) {
			$response = $this->get_client()->cancelCourierBooking( new CancelCourierBooking( $this->auth_data, [ $booking_id ] ) );
			$this->validate_cancel_courier_booking_response( $response );
		}
	}

	/**
	 * @param object $response .
	 *
	 * @return void
	 */
	private function validate_cancel_courier_booking_response( object $response ): void {
		$items = $this->get_cancel_courier_booking_items( $response );
		if ( null === $items ) {
			return;
		}

		foreach ( $items as $item ) {
			$this->validate_cancel_courier_booking_item( $item );
		}
	}

	/**
	 * @param object $response .
	 *
	 * @return array<int, mixed>|null
	 */
	private function get_cancel_courier_booking_items( object $response ): ?array {
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$result = $response->cancelCourierBookingResult ?? null;
		if ( null === $result ) {
			return null;
		}

		$items = $result->shipments ?? ( $result->item ?? null );
		if ( null === $items ) {
			return null;
		}

		if ( is_object( $items ) ) {
			return [ $items ];
		}

		if ( ! is_array( $items ) ) {
			return null;
		}

		return $items;
	}

	/**
	 * @param mixed $item .
	 *
	 * @return void
	 */
	private function validate_cancel_courier_booking_item( $item ): void {
		$item_result = is_object( $item ) ? ( $item->result ?? null ) : ( $item['result'] ?? null );
		if ( $this->is_false_result( $item_result ) ) {
			$error_message = is_object( $item ) ? ( $item->error ?? '' ) : ( $item['error'] ?? '' );
			$error_message = (string) $error_message;
			if ( '' === $error_message ) {
				$error_message = 'DHL WebAPI cancel courier booking error.';
			}

			throw new \Exception( $error_message );
		}
	}

	/**
	 * @param mixed $value .
	 *
	 * @return bool
	 */
	private function is_false_result( $value ): bool {
		return false === $value || 'false' === $value || 0 === $value || '0' === $value;
	}

	/**
	 * @param CreateShipmentRequest $shipment_data .
	 *
	 * @return string Shipment ID.
	 */
	public function create_shipment( CreateShipmentRequest $shipment_data ): string {
		$response = $this->get_client()->createShipment( new CreateShipment( $this->auth_data, $shipment_data ) );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$result = $response->createShipmentResult ?? null;

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$tracking_number = is_object( $result ) ? ( $result->shipmentTrackingNumber ?? $result->shipmentNotificationNumber ?? '' ) : '';

		return (string) $tracking_number;
	}

	/**
	 * @param string $shipment_id .
	 *
	 * @return void
	 */
	public function delete_shipment( string $shipment_id ) {
		$this->delete_shipments( [ $shipment_id ] );
	}

	/**
	 * @param string[] $shipment_ids .
	 *
	 * @return void
	 */
	public function delete_shipments( array $shipment_ids ) {
		$response = $this->get_client()->deleteShipments( new DeleteShipments( $this->auth_data, $shipment_ids ) );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$items = $response->deleteShipmentsResult->item ?? null;
		if ( null === $items ) {
			return;
		}

		if ( is_object( $items ) ) {
			$items = [ $items ];
		}

		if ( ! is_array( $items ) ) {
			return;
		}

		foreach ( $items as $item ) {
			$result = is_object( $item ) ? ( $item->result ?? null ) : ( $item['result'] ?? null );
			if ( false === $result || 'false' === $result || 0 === $result || '0' === $result ) {
				$error_message = is_object( $item ) ? ( $item->error ?? '' ) : ( $item['error'] ?? '' );
				$error_message = (string) $error_message;
				if ( '' === $error_message ) {
					$error_message = 'DHL WebAPI delete shipment error.';
				}

				throw new \Exception( $error_message );
			}
		}
	}

	/**
	 * @param string $shipment_id .
	 * @param string $label_type  .
	 *
	 * @return string Label content (base64).
	 */
	public function get_label( string $shipment_id, string $label_type ): string {
		$item = new ItemToPrint( (int) $shipment_id, $label_type );

		return $this->get_labels( [ $item ] );
	}

	/**
	 * @param ItemToPrint[] $items_to_print .
	 *
	 * @return string Label content (base64).
	 */
	public function get_labels( array $items_to_print ): string {
		$response = $this->get_client()->getLabels( new GetLabels( $this->auth_data, $items_to_print ) );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		return $response->getLabelsResult->item->labelData;
	}

	/**
	 * @param CreateReturnShipmentRequest $shipment .
	 *
	 * @return array{label_format: string, label_content: string, label_type: string, tracking_number: string}
	 * @throws \Exception .
	 */
	// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
	public function create_return_shipment( CreateReturnShipmentRequest $shipment ): array {
		$response = $this->get_client()->createShipmentReturn( new CreateShipmentReturn( $this->auth_data, $shipment ) );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$result = $response->createShipmentReturnResult->item ?? null;
		if ( null === $result ) {
			throw new \Exception( 'DHL WebAPI create return shipment error.' );
		}

		$items = is_object( $result ) ? [ $result ] : ( is_array( $result ) ? $result : [] );
		$item  = $items[0] ?? null;
		if ( null === $item ) {
			throw new \Exception( 'DHL WebAPI create return shipment error.' );
		}

		$label = is_object( $item ) ? ( $item->label ?? null ) : ( $item['label'] ?? null );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$label_format = is_object( $label ) ? ( $label->labelFormat ?? '' ) : ( $label['labelFormat'] ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$label_content = is_object( $label ) ? ( $label->labelContent ?? '' ) : ( $label['labelContent'] ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$label_type = is_object( $label ) ? ( $label->labelType ?? '' ) : ( $label['labelType'] ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$tracking_number = is_object( $item ) ? ( $item->shipmentTrackingNumber ?? '' ) : ( $item['shipmentTrackingNumber'] ?? '' );

		$label_format    = strtolower( (string) $label_format );
		$label_content   = (string) $label_content;
		$label_type      = (string) $label_type;
		$tracking_number = (string) $tracking_number;

		if ( '' === $label_content ) {
			throw new \Exception( 'DHL WebAPI create return shipment error.' );
		}

		return [
			'label_format'    => $label_format,
			'label_content'   => $label_content,
			'label_type'      => $label_type,
			'tracking_number' => $tracking_number,
		];
	}

	/**
	 * @param string $shipment_id .
	 * @param string $waybill     .
	 * @param string $label_type  .
	 *
	 * @return array{label_format: string, label_content: string, label_type: string}
	 * @throws \Exception .
	 */
	// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
	public function get_return_label_by_waybill( string $shipment_id, string $waybill, string $label_type ): array {
		$item     = new ReturnItemToPrint( $shipment_id, $waybill, $label_type );
		$response = $this->get_client()->getReturnByWaybill( new GetReturnByWaybill( $this->auth_data, [ $item ] ) );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$result = $response->getReturnByWaybillResult->item ?? null;
		if ( null === $result ) {
			throw new \Exception( 'DHL WebAPI get return label error.' );
		}

		$items = is_object( $result ) ? [ $result ] : ( is_array( $result ) ? $result : [] );
		$item  = $items[0] ?? null;
		if ( null === $item ) {
			throw new \Exception( 'DHL WebAPI get return label error.' );
		}

		$label = is_object( $item ) ? ( $item->label ?? null ) : ( $item['label'] ?? null );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$label_format = is_object( $label ) ? ( $label->labelFormat ?? '' ) : ( $label['labelFormat'] ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$label_content = is_object( $label ) ? ( $label->labelContent ?? '' ) : ( $label['labelContent'] ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$label_type = is_object( $label ) ? ( $label->labelType ?? $label_type ) : ( $label['labelType'] ?? $label_type );

		$label_format  = strtolower( (string) $label_format );
		$label_content = (string) $label_content;
		$label_type    = (string) $label_type;

		if ( '' === $label_content ) {
			throw new \Exception( 'DHL WebAPI get return label error.' );
		}

		return [
			'label_format'  => $label_format,
			'label_content' => $label_content,
			'label_type'    => $label_type,
		];
	}

	/**
	 * @param string $date .
	 * @param string $type .
	 *
	 * @return array{file_name: string, file_data: string, mime_type: string}
	 */
	public function get_pnp( string $date, string $type ): array {
		$response = $this->get_client()->getPnp( new GetPnp( $this->auth_data, $date, $type ) );

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$result = $response->getPnpResult ?? null;
		if ( ! is_object( $result ) ) {
			throw new \Exception( 'DHL WebAPI getPnp error.' );
		}

		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$file_name = (string) ( $result->fileName ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$file_data = (string) ( $result->fileData ?? '' );
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$mime_type = (string) ( $result->fileMimeType ?? '' );

		if ( '' === $file_name || '' === $file_data || '' === $mime_type ) {
			throw new \Exception( 'DHL WebAPI getPnp error.' );
		}

		return [
			'file_name' => $file_name,
			'file_data' => $file_data,
			'mime_type' => $mime_type,
		];
	}
}
