<?php

/**
 * Class WebAPI
 *
 * @package Octolize\DhlPolska\API
 */
namespace DhlPolskaVendor\Octolize\DhlPolska\API;

use DhlPolskaVendor\Octolize\DhlPolska\API\Exception\DhlApiException;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\BookCourier;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\CancelCourierBooking;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\CreateShipment;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\CreateShipmentReturn;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\DeleteShipments;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\GetLabels;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\GetNearestServicepoints;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\GetPnp;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\GetPostalCodeServices;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Method\GetReturnByWaybill;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\SoapClient as WebApiSoapClient;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\CreateReturnShipmentRequest;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\CreateShipmentRequest;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ItemToPrint;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ReturnItemToPrint;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ShipmentOrderInfo;
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
     * @throws \SoapFault
     */
    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
     * @throws \SoapFault
     */
    protected function create_soap_client(string $wsdl, array $options): SoapClient
    {
        return new WebApiSoapClient(static::API, $wsdl, $options, $this->logger);
    }
    /**
     * Check connection.
     *
     * @throws \JsonException|DhlApiException|\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[]
     * @throws \JsonException|DhlApiException|\Exception
     */
    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
     * @throws \JsonException|DhlApiException|\Exception
     */
    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
     * @throws \Exception
     */
    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
     * @throws \Exception
     */
    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.
     * @throws \JsonException|DhlApiException|\Exception
     */
    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
     * @throws \JsonException|DhlApiException|\Exception
     */
    public function delete_shipment(string $shipment_id)
    {
        $this->delete_shipments([$shipment_id]);
    }
    /**
     * @param string[] $shipment_ids .
     *
     * @return void
     * @throws \JsonException|DhlApiException|\Exception
     */
    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).
     * @throws \JsonException|DhlApiException|\Exception
     */
    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).
     * @throws \JsonException|DhlApiException|\Exception
     */
    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 \JsonException|DhlApiException|\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 \JsonException|DhlApiException|\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      $country  .
     * @param string      $postcode .
     * @param int         $radius   .
     * @param string|null $city     .
     *
     * @return array<int, object>
     * @throws \JsonException|DhlApiException|\Exception
     */
    public function get_nearest_servicepoints(string $country, string $postcode, int $radius, ?string $city = null): array
    {
        $response = $this->get_client()->getNearestServicepoints(new GetNearestServicepoints($this->auth_data, $country, $postcode, $radius, $city));
        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
        $result = $response->getNearestServicepointsResult ?? null;
        if (null === $result) {
            throw new \Exception('DHL WebAPI getNearestServicepoints error.');
        }
        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
        $points = $result->points ?? null;
        if (!is_object($points) && !is_array($points)) {
            return [];
        }
        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
        $items = is_object($points) ? $points->item ?? null : $points['item'] ?? null;
        if (null === $items) {
            return [];
        }
        if (is_object($items)) {
            return [$items];
        }
        if (!is_array($items)) {
            return [];
        }
        return array_values($items);
    }
    /**
     * @param string $date .
     * @param string $type .
     *
     * @return array{file_name: string, file_data: string, mime_type: string}
     * @throws \JsonException|DhlApiException|\Exception
     */
    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];
    }
}
