<?php

declare(strict_types=1);

namespace Macopedia\OrlenShipping\Model\Api;

use Exception;
use Macopedia\OrlenShipping\Api\ClientInterface;
use Magento\Framework\Webapi\Soap\ClientFactory;
use Macopedia\OrlenShipping\Helper\Data;
use Psr\Log\LoggerInterface;
use Macopedia\OrlenShipping\Model\Anonymizer;
use SoapClient;
use stdClass;

class Client implements ClientInterface
{
    /**
     * @param ClientFactory $soapClientFactory
     * @param Data $data
     * @param LoggerInterface $logger
     * @param Anonymizer $anonymizer
     * @param SoapClient|null $client
     */
    public function __construct(
        protected readonly ClientFactory $soapClientFactory,
        protected readonly Data $data,
        protected readonly LoggerInterface $logger,
        protected readonly Anonymizer $anonymizer,
        protected SoapClient|null $client = null
    ) {
    }

    /**
     * Get client
     *
     * @return SoapClient
     * @throws OrlenApiException
     */
    protected function getClient(): SoapClient
    {
        if (!isset($this->client)) {
            try {
                $isDebug = $this->data->isDebug();
                $this->client = $this->soapClientFactory->create(
                    $this->data->getApiUrl(),
                    [
                        'soap_version' => SOAP_1_2,
                        'trace' => $isDebug ? 1 : 0,
                        'exceptions' => $isDebug
                    ]
                );
                $this->client->__setLocation($this->data->getApiUrl());
            } catch (Exception $e) {
                throw new OrlenApiException(
                    "Error occurred while trying to get SOAP client: " . $e->getMessage(),
                    1730664551,
                    $e
                );
            }
        }

        return $this->client;
    }

    /**
     * Soap call
     *
     * @param string $functionName
     * @param array $arguments
     * @return Response
     * @throws OrlenApiException
     */
    protected function soapCall(string $functionName, array $arguments = []): Response
    {
        try {
            $client = $this->getClient();
            $response = $client->$functionName($arguments);

            if ($this->data->isDebug()) {
                $this->logger->debug("SOAP Request Headers:\n" . $client->__getLastRequestHeaders());
                $this->logger->debug(
                    "SOAP Request Body:\n" . $this->anonymizer->anonymizeRequest($client->__getLastRequest())
                );
            }

            $response = $this->parseResponseToObject($functionName, $response);
            if ($this->data->isDebug()) {
                $this->logger->debug("SOAP Response: " . json_encode($response));
                if ($response->isError()) {
                    $this->logger->debug("SOAP ERROR: " . $response->getErrorDescription());
                    throw new OrlenApiException(
                        $response->getErrorDescription()
                    );
                }
            }
            return $response;
        } catch (Exception $e) {
            throw new OrlenApiException(
                "Error occurred while trying to call {$functionName} API function: " . $e->getMessage(),
                1609715515,
                $e
            );
        }
    }

    /**
     * Parse response to object
     *
     * @param string $functionName
     * @param stdClass $responseApi
     * @return Response
     * @throws Exception
     */
    protected function parseResponseToObject(string $functionName, stdClass $responseApi): Response
    {
        $resultName = $functionName . 'Result';
        if (isset($responseApi->$resultName->any)) {
            $xml = new \SimpleXMLElement($responseApi->$resultName->any, LIBXML_NOCDATA);
        } else {
            $xml = $responseApi->$resultName;
        }

        $response = new Response();
        if ($functionName == 'Ping') {
            $response->PingStatus = $xml;
        } elseif ($functionName == 'GetAvailablePickups') {
            $response->AvailablePickupDay = $xml->Data->AvailablePickupDay ?? null;
        } elseif ($functionName == 'CallPickupNew') {
            $response->CourierOrderNumber = $xml->Data ?? null;
        }

        if (isset($xml->NewDataSet) && $xml->NewDataSet->count() > 0) {
            foreach ($xml->NewDataSet->children() as $child) {
                $data = json_decode(json_encode($child));
                $dataSource = new \ReflectionObject($data);
                foreach ($dataSource->getProperties() as $property) {
                    $name = $property->getName();
                    $response->{$name} = $data->$name;
                }
            }
        }

        if ($functionName == 'LabelPrintDuplicateTwo') {
            $response->Label = $responseApi->Label ?? null;
        }

        if (in_array($functionName, [
                'GenerateProtocol',
                'GenerateLabelBusinessPackTwo',
            ])
        ) {
            $response->LabelData = $responseApi->LabelData ?? null;
        }

        if (isset($xml->Err) && isset($xml->ErrDes)) {
            $response->Err = (string)$xml->Err;
            $response->ErrDes = (string)$xml->ErrDes;
        }

        return $response;
    }

    /**
     * @inheritdoc
     */
    public function generateLabelBusinessPackTwo(array $parameters): Response
    {
        return $this->soapCall('GenerateLabelBusinessPackTwo', $parameters);
    }

    /**
     * @inheritdoc
     */
    public function generateProtocol(array $parameters): Response
    {
        return $this->soapCall('GenerateProtocol', $parameters);
    }

    /**
     * @inheritdoc
     */
    public function givePartnerStatus(array $parameters): Response
    {
        return $this->soapCall('GivePartnerStatus', $parameters);
    }

    /**
     * @inheritdoc
     */
    public function ping(): Response
    {
        return $this->soapCall('Ping');
    }

    /**
     * @inheritdoc
     */
    public function getAvailablePickupWindows(array $parameters): Response
    {
        return $this->soapCall('GetAvailablePickups', $parameters);
    }

    /**
     * @inheritdoc
     */
    public function callPickupNew(array $parameters): Response
    {
        return $this->soapCall('CallPickupNew', $parameters);
    }
}
