<?php

declare(strict_types=1);

use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Query\JsonOperator;
use KupShop\KupShopBundle\Util\StringUtil;
use KupShop\OrderingBundle\Exception\PaymentException;
use Query\Operator;

// Dokumentace ESSOX API: https://drive.google.com/drive/folders/1j7OFhsrUl1F3ZQt3Lo7d8yOyvLW7ioN5?usp=sharing

class Essox2Splatky extends Payment
{
    public static $name = 'Essox - splátky';

    public $class = 'Essox2Splatky';

    protected string $apiUrl = 'https://apiv32.essox.cz';
    protected string $apiTestUrl = 'https://testapiv32.essox.cz';

    protected $templateOrderView = 'payment.Essox2Splatky.orderView.tpl';

    protected $pay_method = Payment::METHOD_ONLINE;

    public function getPaymentUrl()
    {
        return createScriptURL([
            's' => 'payment',
            'IDo' => $this->order->id,
            'cf' => $this->order->getSecurityCode(),
            'step' => 1,
            'class' => $this->class,
            'absolute' => true,
        ]);
    }

    protected function getSpreadedInstalments(): bool
    {
        return false;
    }

    public function processStep_1()
    {
        $proposalData = $this->getProposalData();

        $redirectData = $this->getProposalRedirect($proposalData);

        $contractId = $redirectData['contractId'];

        $this->createPayment($contractId, $proposalData['price'], ['paymentClass' => $this->class]);

        redirection($redirectData['redirectionUrl']);
    }

    /** Return from gateway */
    public function processStep_2()
    {
        $this->info(translate('payment_waiting_for_confirmation', 'payment'));
    }

    public function checkPaidOrders()
    {
        $orderPayments = sqlQueryBuilder()->select('op.id, op.id_order, op.payment_data')
            ->from('order_payments', 'op')
            ->where(\Query\Operator::inIntArray([
                static::STATUS_CREATED,
                static::STATUS_PENDING,
                static::STATUS_UNKNOWN,
            ], 'op.status'))
            ->andWhere('op.date > (DATE_SUB(CURDATE(), INTERVAL 1 MONTH))')
            ->andWhere(Operator::inStringArray([$this->class],
                JsonOperator::value('op.payment_data', 'paymentClass')))
            ->andWhere(JsonOperator::exists('op.payment_data', 'session'))
            ->execute();

        foreach ($orderPayments as $orderPayment) {
            $paymentData = json_decode($orderPayment['payment_data'], true);
            if (empty($paymentData['session'])) {
                continue;
            }
            $this->setOrder($orderPayment['id_order']);
            $contractId = (int) $paymentData['session'];

            $response = $this->getPaymentStatus($contractId);

            if (!empty($response['errorCollection'])) {
                foreach ($response['errorCollection'] as $error) {
                    addActivityLog(ActivityLog::SEVERITY_ERROR,
                        ActivityLog::TYPE_COMMUNICATION,
                        translate('returnFailedMessage', 'orderPayment'),
                        $error);
                }
            }

            foreach ($response['businessCases'] as $businessCase) {
                $businessCaseStatus = (int) $businessCase['contractStatusId'];

                switch ($businessCaseStatus) {
                    case 1: // Zakaz. opustil před odesláním
                    case 2: // Čeká na posouzení
                    case 3: // Posuzuje se
                    case 4: // Odložen
                    case 18: // Čeká na nahrání dokumentů
                    case 19: // Čeká na platbu
                    case 9:
                    case 13: // Ke kontrole 9,13
                        $this->setStatus(self::STATUS_PENDING, $contractId);
                        break;
                    case 5:
                    case 10:
                    case 11: // Reklamace 5,10,11
                    case 7: // Zamítnuto / Neschválený návrh
                        $this->setStatus(self::STATUS_STORNO, $contractId);
                        break;
                    case 12: // V pořádku doručeno do ESSOXu proplaceno / Zkontrolováno
                        $newStatus = ($this->config['setPaymentCompletedManually'] ?? false) ? self::STATUS_PENDING : self::STATUS_FINISHED;
                        $this->setStatus($newStatus, $contractId);
                        break;
                    default:
                        $this->setStatus(self::STATUS_UNKNOWN, $contractId);
                }
            }
        }
    }

    protected function getPaymentStatus(int $contractId)
    {
        $response = $this->requestEssoxCurl('/consumergoods/v1/api/consumergoods/status?ContractId='.$contractId, [
            'Accept: application/json',
            'Authorization: Bearer '.$this->getAccessToken(),
        ], '', false);

        return json_decode($response, true);
    }

    protected function getAccessToken()
    {
        $loginHash = md5(($this->config['clientKey'] ?? '').':'.($this->config['clientSecret'] ?? '').':'.($this->config['test'] ?? ''));
        $tokenCacheKey = 'payment_essox_access_token'.$loginHash;

        $token = getCache($tokenCacheKey);

        if (!$token) {
            $retrievedToken = $this->retrieveAccessToken();
            $token = $retrievedToken['access_token'];
            setCache($tokenCacheKey, $token, $retrievedToken['expires_in'] - 60);
        }

        return $token;
    }

    protected function retrieveAccessToken(): array
    {
        $data = [
            'grant_type' => 'client_credentials',
            'scope' => 'scopeFinit.consumerGoods.eshop',
        ];

        $response = $this->requestEssoxCurl('/token', [
            'Content-Type: application/x-www-form-urlencoded',
            'accept: application/json',
            'Authorization: Basic '.base64_encode($this->config['clientKey'].':'.$this->config['clientSecret']),
        ], http_build_query($data));

        $decodedData = json_decode($response, true);

        return ['access_token' => (string) $decodedData['access_token'], 'expires_in' => (int) $decodedData['expires_in']];
    }

    public function getProposalData(): array
    {
        $phoneNumber = $this->order->invoice_phone;
        $checkPhonePrefixes = ['+420'];
        $phonePrefix = '';

        foreach ($checkPhonePrefixes as $checkPrefix) {
            if (StringUtil::startsWith($phoneNumber, $checkPrefix)) {
                $phoneNumber = str_replace($checkPrefix, '', $phoneNumber);
                $phonePrefix = $checkPrefix;
                break;
            }
        }

        return [
            'firstName' => $this->order->invoice_name,
            'surname' => $this->order->invoice_surname,
            'mobilePhonePrefix' => $phonePrefix,
            'mobilePhoneNumber' => $phoneNumber,
            'email' => $this->order->invoice_email,
            'price' => $this->order->getTotalPrice()->getPriceWithVat()->asFloat(),
            'orderId' => $this->order->order_no,
            'customerId' => $this->order->id_user,
            'transactionId' => 1,
            'shippingAddress' => [
                'street' => $this->order->delivery_street,
                'houseNumber' => '',
                'city' => $this->order->delivery_city,
                'zip' => $this->order->delivery_zip,
            ],
            'callbackUrl' => $this->getGenericPaymentUrl(2),
            'spreadedInstalments' => $this->getSpreadedInstalments(),
        ];
    }

    protected function getProposalRedirect($proposalData)
    {
        $response = $this->requestEssoxCurl('/consumergoods/v1/api/consumergoods/proposal', [
            'Content-Type: application/json',
            'Authorization: Bearer '.$this->getAccessToken(),
        ], json_encode($proposalData));

        return json_decode($response, true);
    }

    public function getEssoxCalcDetail(Decimal $price)
    {
        $price = roundPrice($price, -1, 'DB', 0)->asInteger();

        if (!($this->config['productDetail'] ?? false) || $price < ($this->config['minPrice'] ?? 2000) || (!empty($this->config['maxPrice']) && $price > $this->config['maxPrice'])) {
            return null;
        }

        return path('kupshop_ordering_payment_legacypayment', [
            'step' => 5,
            'class' => $this->class,
            'price' => $price,
        ]);
    }

    public function processStep_5()
    {
        $price = roundPrice(getVal('price'), -1, 'DB', 0)->asInteger();

        if ($price < ($this->config['minPrice'] ?? 2000) || (!empty($this->config['maxPrice']) && $price > $this->config['maxPrice'])) {
            return null;
        }

        redirection($this->getCalculatorUrl($price));
    }

    protected function getCalculatorUrl($price)
    {
        $body = [
            'price' => $price,
            'productId' => 0,
        ];

        $response = $this->requestEssoxCurl('/consumergoods/v1/api/consumergoods/calculator', [
            'Content-Type: application/json',
            'accept: application/json',
            'Authorization: Bearer '.$this->getAccessToken(),
        ], json_encode($body));

        $decodedData = json_decode($response, true);

        return $decodedData['redirectionUrl'];
    }

    protected function requestEssoxCurl(string $path, array $headers, string $encodedBody, $post = true)
    {
        $url = $this->getApiUrl().$path;
        $initTimeout = 30;
        $step = 1;
        $ch = curl_init();
        if ($post) {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedBody);
        }
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        while ($step <= 2) {
            curl_setopt($ch, CURLOPT_TIMEOUT, $initTimeout);

            $response = curl_exec($ch);
            $responseInfo = curl_getinfo($ch);
            $curl_errno = curl_errno($ch);
            curl_close($ch);

            if ($responseInfo['http_code'] === 0 && $step === 1) {
                $initTimeout = 10;
                $step++;
                continue;
            }

            if ($responseInfo['http_code'] != 200) {
                $debugInfo = ['url' => $url, 'responseInfo' => $responseInfo, 'curl_errno' => $curl_errno, 'RESPONSE' => $response];
                $this->handleError($debugInfo);
                throw new PaymentException('Komunikace s platební bránou selhala. Zkuste prosím znovu později.');
            }

            break;
        }

        return $response;
    }

    public function accept($totalPrice, $freeDelivery)
    {
        $price = $totalPrice->getPriceWithVat()->asFloat();
        if ($price <= 0 && $this->order) {
            $price = $this->order->total_price;
        }

        // price has to be greater than 2000 Kč according to the documentation
        return parent::accept($totalPrice, $freeDelivery) && $price >= 2000;
    }

    public function hasOnlinePayment()
    {
        return true;
    }

    protected function getApiUrl(): string
    {
        return ($this->config['test'] ?? false) ? $this->apiTestUrl : $this->apiUrl;
    }

    public static function isEnabled($className)
    {
        $cfg = Config::get();

        if (empty($cfg['Modules']['payments'][$className])) {
            return false;
        }

        return true;
    }

    public static function getSettingsConfiguration(): array
    {
        return [
            'fields' => [
                'clientKey' => [
                    'title' => 'ID klienta (client_id)',
                    'type' => 'text',
                ],
                'clientSecret' => [
                    'title' => 'Secret (clientSecret)',
                    'type' => 'text',
                ],
                'setPaymentCompletedManually' => [
                    'title' => 'Manuální potvrzení platby',
                    'tooltip' => 'Pokud je vypnuto, platba objednávky se nastaví jako uhrazená automaticky po schválení úvěru. Pokud je zapnuto, přepnutí platby na uhrazenou je potřeba udělat manuálně.',
                    'type' => 'toggle',
                ],
                'minPrice' => [
                    'title' => 'Minimální částka',
                    'type' => 'number',
                    'placeholder' => 2000,
                    'tooltip' => 'Minimální částka u které tuto možnost zobrazit na detailu produktu. (min. 2000 Kč)',
                ],
                'maxPrice' => [
                    'title' => 'Maximální částka',
                    'type' => 'number',
                    'tooltip' => 'Maximální částka u které tuto možnost zobrazit na detailu produktu.',
                ],
                'productDetail' => [
                    'title' => 'Zobrazit na detailu produktu',
                    'type' => 'toggle',
                ],
                'test' => [
                    'title' => 'Testovací režim',
                    'type' => 'toggle',
                ],
            ],
        ];
    }

    protected function handleError($debugInfo)
    {
        $message = $this->getName().': admin info - Komunikace s platební bránou selhala';

        $sentry = false;
        $csobFault = false;
        switch ($debugInfo['responseInfo']['http_code'] ?? -1) {
            case 400:
                $reason = 'Nevalidní request. V dotazu chybí povinné pole nebo je v
                nevhodném / nevalidním formátu.
                Nevalidní uživatel.
                Neplatné pověření.
                Uživatel není oprávněný používat autorizační typ';
                $sentry = true;
                break;
            case 401:
                unset($debugInfo['responseInfo']);
                $reason = 'Přístup odepřen (špatné přihlašovací údaje)';
                $message .= ' - '.$reason;
                break;
            case 403:
                $reason = 'Klient není oprávněný provádět tento dotaz.';
                $sentry = true;
                break;
            case 500:
                $reason = 'Chyba na straně CSOB serveru';
                $csobFault = true;
                break;
            case 503:
                $reason = 'CSOB služba je dočasně nedostupná. Může být způsobena přetížením serveru nebo z důvodu údržby';
                $csobFault = true;
                break;
            case 0:
                $reason = 'CSOB API timeout';
                $csobFault = true;
                break;
        }

        $debugInfo = array_merge(['reason' => $reason ?? 'Neznámá chyba'], $debugInfo);

        if ($sentry) {
            getRaven()->captureMessage('Essox API error', [], ['extra' => $debugInfo]);
        }
        if ($csobFault) {
            $message .= ' - chyba na straně CSOB';
        }

        addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, $message, $debugInfo);
    }
}
