<?php

declare(strict_types=1);

namespace External\HannahBundle\SAP\Util;

use External\HannahBundle\SAP\Exception\SAPErrorException;
use External\HannahBundle\SAP\Exception\SAPException;
use External\HannahBundle\SAP\Exception\SAPManyException;
use External\HannahBundle\Util\Configuration;
use External\HannahBundle\Util\OrderUtil;

class SAPClient
{
    public const COUPON_STATE_BLOCKED = 'B';
    public const COUPON_STATE_SOLD = 'S';

    private ?\SoapClient $_lastClient = null;

    private int $timeout = 60;

    public function __construct(
        private Configuration $configuration,
        private OrderUtil $orderUtil,
    ) {
    }

    public function setTimeout(int $timeout): SAPClient
    {
        $this->timeout = $timeout;

        return $this;
    }

    public function orderInfo(string $orderId, ?string $process = null): ?object
    {
        try {
            $result = $this->doSoapCall(
                [
                    'OrderId' => $orderId,
                ],
                'ZSprpOrderInfo',
                'orderInfo',
                'order',
                'POST',
                $process
            );
        } catch (SAPException $e) {
            if (str_contains($e->getMessage(), 'Zakázka nenalezena')) {
                return null;
            }

            throw $e;
        }

        return $result->OrderData;
    }

    public function orderCancel(string $orderNo, string $reason = 'CHANGE', ?string $processName = null)
    {
        return $this->doSoapCall(
            [
                'OrderStorno' => [
                    'OrderId' => $orderNo,
                    'Reason' => $reason,
                ],
            ],
            'ZSprpOrderCancel',
            'orderCancel',
            'order',
            'POST',
            $processName
        );
    }

    public function orderPayment(\Order $order, string $transactionId = '', string $state = 'PAID', ?string $processName = null)
    {
        return $this->doSoapCall(
            [
                'OrderUpdatePayment' => [
                    'OrderId' => $order->order_no,
                    'PaymentState' => $state,
                    'PaymentTransactionId' => $transactionId,
                ],
            ],
            'ZSprpOrderPayment',
            'orderPayment',
            'order',
            'POST',
            $processName ?: $this->orderUtil->getOrderProcess($order)
        );
    }

    public function orderCreate(array $data, ?string $processName = null)
    {
        return $this->doSoapCall(
            [
                'Order' => $data,
            ],
            'ZSprpOrderCreate',
            'orderCreate',
            'order',
            'POST',
            $processName
        );
    }

    public function orderPricing(array $data)
    {
        return $this->doSoapCall(
            [
                'Order' => $data,
            ],
            'ZSprpOrderPricing',
            'orderPricing',
            'order',
            'POST'
        );
    }

    public function returnCreate(array $data, ?string $processName = null)
    {
        return $this->doSoapCall(
            [
                'ReturnData' => $data,
            ],
            'ZSprpOrderReturn',
            'orderReturn',
            'order',
            'POST',
            $processName
        );
    }

    public function couponGetInfo(string $coupon, ?string $couponStatus = 'S'): ?object
    {
        try {
            $coupon = $this->doSoapCall(
                [
                    'CouponId' => '',
                    'VerificationCode' => $coupon,
                ],
                'ZWsGetCouponInfo',
                'couponGetInfo',
                'get_coupon_info',
                'POST'
            );

            if ($couponStatus && ($coupon->CouponStatus ?? false) != $couponStatus) {
                return null;
            }

            return $coupon;
        } catch (SAPException $e) {
            return null;
        }
    }

    /**
     * state: B - blokovano (pri uplatnovani), S - prodano (pri nakupu kuponu).
     */
    public function couponChangeState(string $coupon, string $state = self::COUPON_STATE_BLOCKED, ?string $causingDocument = null): mixed
    {
        $data = [
            'CouponId' => '',
            'VerificationCode' => $coupon,
            'NewState' => $state,
            'CausingDocument' => $causingDocument ?: '',
        ];

        return $this->doSoapCall(
            $data,
            'ZWsCouponChangeState',
            'couponChangeState',
            'coupon_change_state',
            'POST'
        );
    }

    public function customerGetData(string $sapId)
    {
        return $this->doSoapCall(
            [
                'Customer' => $sapId,
            ],
            'ZSprpCustomerGetData',
            'customerGetData',
            'customer',
            'POST'
        );
    }

    public function customerCreate(array $data, ?string $processName)
    {
        return $this->doSoapCall(
            [
                'Data' => $data,
            ],
            'ZSprpCustomerCreate',
            'customerCreate',
            'customer',
            'POST',
            $processName
        );
    }

    public function customerUpdate(string $sapId, array $data, ?string $processName)
    {
        return $this->doSoapCall(
            [
                'Customer' => $sapId,
                'Data' => $data,
            ],
            'ZSprpCustomerUpdate',
            'customerUpdate',
            'customer',
            'POST',
            $processName
        );
    }

    public function customerValidate(string $email, string $password, ?string $processName = null)
    {
        return $this->doSoapCall(
            [
                'Email' => $email,
                'Password' => $password,
            ],
            'ZSprpCustomerValidate',
            'customerValidate',
            'customer',
            'POST',
            $processName
        );
    }

    public function customerChangePassword(string $sapId, string $password)
    {
        return $this->doSoapCall(
            [
                'Customer' => $sapId,
                'Password' => [
                    'Paswd' => '',
                    'PaswdHash' => $password,
                ],
            ],
            'ZSprpCustomerChangePswd',
            'customerChangePswd',
            'customer',
            'POST'
        );
    }

    public function getHandledResult($result)
    {
        $error = $result->Error ?? '';
        if ($error instanceof \stdClass) {
            $error = (array) $error;
        }

        if (!empty($error)) {
            if (isset($result->Messages)) {
                $messages = $result->Messages->item ?? [];
            } else {
                $messages = $error['item'] ?? [];
            }

            if ($messages instanceof \stdClass) {
                $messages = [$messages];
            }

            $exception = new SAPManyException();
            foreach ($messages as $message) {
                $exception->add(
                    new SAPErrorException(
                        sprintf('[SAP-Error] %s: %s', $message->Number ?? 0, $message->Message ?? ''),
                        $message->Number ?? 0,
                        $message->Message ?? '',
                        (array) $message
                    )
                );
            }

            throw $exception;
        }

        return $result;
    }

    public function doSoapCall(array $data, string $callMethod, string $endpoint, string $wsdl, string $method = 'GET', ?string $processName = null)
    {
        if (!$processName) {
            $processName = $this->getProcessName();
        }

        try {
            return $this->getHandledResult(
                $this->getClient($endpoint, $wsdl, $method)
                    ->__soapCall($callMethod,
                        [
                            array_merge($data, ['Process' => $processName]),
                        ]
                    )
            );
        } catch (\SoapFault $e) {
            throw new SAPException('[SAP-Error] '.$e->getMessage(), $e->getCode(), $e);
        }
    }

    private function getClient(string $endpoint, string $wsdl, string $method = 'GET'): \SoapClient
    {
        $context = stream_context_create([
            'ssl' => [
                'verify_peer' => false,
                'verify_peer_name' => false,
                'allow_self_signed' => true,
            ],
        ]);

        ini_set('default_socket_timeout', (string) $this->timeout);

        return $this->_lastClient = new \SoapClient(__DIR__.'/../../Resources/wsdl/'.$wsdl.'.xml',
            [
                'location' => $this->getAPIUrl($endpoint, $method),
                'trace' => true,
                'stream_context' => $context,
                'exceptions' => true,
                'keep_alive' => false,
                'connection_timeout' => $this->timeout,
            ]
        );
    }

    private function getAPIUrl(string $endpoint, string $method = 'GET', array $params = []): string
    {
        $timestamp = (new \DateTime())->format('Y-m-d\TH:i:s.000');

        $params = array_merge(
            $params,
            [
                'timestamp' => $timestamp,
                'token' => $this->getToken($method, $endpoint, $timestamp),
            ]
        );

        $queryParams = '';
        if ($params) {
            $queryParams = '?'.http_build_query($params);
        }

        return $this->getConfig()['url'].'/'.$endpoint.$queryParams;
    }

    private function getToken(string $method, string $endpoint, string $timestamp): string
    {
        $data = [
            $method,
            '/wpj/'.$endpoint,
            $timestamp,
        ];

        return base64_encode(hash_hmac('sha1', implode('+', $data), $this->getConfig()['secret'], true));
    }

    private function getProcessName(): string
    {
        return $this->configuration->getProcess();
    }

    public function getConfig(): ?array
    {
        return $this->configuration->get('sap.api');
    }
}
