<?php

use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\DomainContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\EANValidator;
use KupShop\OrderingBundle\Exception\PaymentException;
use KupShop\OrderingBundle\Util\Order\PaymentOrderSubMethodTrait;
use ThePay\ApiClient\Exception\ApiException;
use ThePay\ApiClient\Exception\MissingExtensionException;
use ThePay\ApiClient\Filter\PaymentMethodFilter;
use ThePay\ApiClient\Model\CreatePaymentCustomer;
use ThePay\ApiClient\Model\CreatePaymentItem;
use ThePay\ApiClient\ValueObject\LanguageCode;

/**
 * composer require "thepay/api-client":"1.2.4".
 */
class ThePay20 extends Payment
{
    use PaymentOrderSubMethodTrait;

    public static $name = 'ThePay 2.0 platební brána';

    public static bool $canAutoReturn = true;

    protected $templateCart = 'payment.ThePay20.cart.tpl';

    public $class = 'ThePay20';

    public $method;

    protected $pay_method = Payment::METHOD_ONLINE;

    protected $apiContext;

    protected $gatewayUrl;

    /** Get payment icon url and name.
     * @return array|null
     */
    public function getIcon()
    {
        $method = getVal($this->method, $this->getAvailableMethods());
        if ($method) {
            return [
                'url' => $method['image'],
                'name' => $method['name'],
            ];
        }

        return null;
    }

    public function getGatewayUrl(): string
    {
        if (!isset($this->gatewayUrl)) {
            $this->processStep_1(false);
        }

        return $this->gatewayUrl;
    }

    public function processStep_1($redirectToGateway = true)
    {
        $apiContext = $this->getApiContext();

        if (empty($this->method)) {
            $paymentData = $this->order->getData('payment_data');
            if ($paymentData) {
                $this->loadPaymentInfo($paymentData);
            }
        }

        $paymentUid = uniqid($this->order->id.'_', true);
        $currencyContext = Contexts::get(CurrencyContext::class);
        $params = new \ThePay\ApiClient\Model\CreatePaymentParams(
            $amount = toDecimal($this->order->getRemainingPayment())
                ->mul(DecimalConstants::hundred())->asInteger(),
            $currencyContext->getActiveId(),
            $paymentUid
        );
        $params->setReturnUrl($this->getGenericPaymentUrl(2));
        $params->setNotifUrl($this->getGenericPaymentUrl(10));
        $params->setOrderId($this->order->order_no);
        //        $params->setDescriptionForCustomer($this->order->order_no);
        //        $params->setDescriptionForMerchant($this->order->order_no);
        $params->setCustomer(new CreatePaymentCustomer(
            $this->order->invoice_name,
            $this->order->invoice_surname,
            $this->order->invoice_email,
            $this->order->invoice_phone,
            ($this->order->invoice_country != '' && $this->order->invoice_city != '' && $this->order->invoice_zip != '' && $this->order->invoice_street != '') ?
            new \ThePay\ApiClient\Model\Address(
                $this->order->invoice_country,
                $this->order->invoice_city,
                $this->order->invoice_zip,
                $this->order->invoice_street
            ) : null
        ));

        foreach ($this->order->fetchItems() as $item) {
            if ($item['pieces'] < 1) {
                continue;
            }
            $tmpItem = [
                'type' => is_null($item['id_product']) ? 'delivery' : 'item',
                'name' => $item['descr'],
                'amount' => $item['total_price']['value_with_vat']->mul(DecimalConstants::hundred())->asInteger(),
                'count' => (int) $item['pieces'],
            ];
            if (isset($item['ean']) && EANValidator::checkEAN($item['ean'])) {
                $tmpItem['ean'] = $item['ean'];
            }
            $params->addItem(
                new CreatePaymentItem($tmpItem['type'], $tmpItem['name'], $tmpItem['amount'], $tmpItem['count'], $tmpItem['ean'] ?? null)
            );
        }

        // check PaymentMethod beforehand: this should eliminate error "21 in not valid value"
        // when the order was created with the old ThePay and then the eshop switched to ThePay20
        try {
            $tmp = new \ThePay\ApiClient\ValueObject\PaymentMethodCode($this->method);
            $method = $this->method;
        } catch (InvalidArgumentException $e) {
            $method = null;
        }

        $response = $apiContext->createPayment($params, $method);
        $this->gatewayUrl = $response->getPayUrl();

        $this->createPayment(
            $paymentUid,
            Decimal::fromInteger($amount)->mul(Decimal::fromFloat(0.01))->asFloat(),
            ['paymentClass' => self::class]
        );
        if ($redirectToGateway) {
            redirection($this->gatewayUrl);
        }
    }

    /** Return from gateway */
    public function processStep_2()
    {
        $this->checkPaymentStatus();
    }

    /** Webhook handler */
    public function processStep_10()
    {
        $this->setIsNotification(true);
        $this->checkPaymentStatus();
        $this->sendNotificationResponse(200, 'OK');
    }

    private function checkPaymentStatus()
    {
        $paymentID = getVal('payment_uid', null, false);
        if (!$paymentID) {
            $this->error(translate('payment_id_missing', 'payment'));
        }

        $response = $this->getApiContext()->getPayment($paymentID);
        $thirdPartyPaymentState = $response->getState(); // https://dataapi21.docs.apiary.io/#paymentStatesEnum

        // determine kupshop unified payment state from third party payment state
        if (in_array($thirdPartyPaymentState, ['waiting_for_payment', 'waiting_for_confirmation'])) {
            $this->status = $unifiedState = Payment::STATUS_CREATED;
        } elseif (in_array($thirdPartyPaymentState, ['paid'])) {
            $this->status = $unifiedState = Payment::STATUS_FINISHED;
        } elseif (in_array($thirdPartyPaymentState, [
            'expired', 'preauth_cancelled', 'preauth_expired', 'partially_refunded',
        ])
        ) {
            $this->status = $unifiedState = Payment::STATUS_STORNO;
        } else {
            logError(__FILE__, __LINE__, 'ThePay20 unexpected payment state "'.$thirdPartyPaymentState.'"');
            $this->error(translate('payment_unexpected_status', 'payment'));
        }

        // change payment status
        if (!$this->setStatus($unifiedState, $paymentID)) {
            logError(__FILE__, __LINE__, 'ThePay20::updatePaymentStatus: setStatus failed!');
            throw new \Exception('Set status failed');
        }

        $this->setPaymentSubMethod($response->getPaymentMethod());

        switch ($unifiedState) {
            case Payment::STATUS_FINISHED:
                $this->success(translate('paymentSuccess', 'payment'));
                break;
            case Payment::STATUS_STORNO:
                $this->info(translate('payment_storno', 'payment'));
                break;
            case Payment::STATUS_PENDING:
            case Payment::STATUS_CREATED:
                $this->info(translate('payment_waiting_for_confirmation', 'payment'));
                break;
        }
    }

    public function getApiContext(): ThePay\ApiClient\TheClient
    {
        if (!isset($this->apiContext)) {
            $languageContext = Contexts::get(LanguageContext::class);
            $apiContext = new \ThePay\ApiClient\TheClient(new \ThePay\ApiClient\TheConfig(
                $this->config['merchantId'],
                $this->config['projectId'],
                $this->config['apiPassword'],
                !empty($this->config['test']) ? 'https://demo.api.thepay.cz/' : 'https://api.thepay.cz/',
                !empty($this->config['test']) ? 'https://demo.gate.thepay.cz/' : 'https://gate.thepay.cz/',
                $this->getPreferredLanguage($this->config['language'] ?? $languageContext->getActiveId())
            ));
            $this->apiContext = $apiContext;
        }

        return $this->apiContext;
    }

    public function getAvailableMethods()
    {
        $currencyContext = Contexts::get(CurrencyContext::class);
        $currency = $currencyContext->getActiveId();
        $domainContext = Contexts::get(DomainContext::class);
        $domain = $domainContext->getActiveId();
        $languageContext = Contexts::get(LanguageContext::class);
        $language = $languageContext->getActiveId();
        $cacheKey = "thepay20-methods-{$currency}-{$domain}-{$language}";
        $methods = getCache($cacheKey);
        if (!isset($methods) || $methods === false || !is_array($methods)) {
            $methods = $this->fetchAvailableMethods($currency);
            setCache($cacheKey, $methods);
        }

        return $methods;
    }

    public function fetchAvailableMethods($currency)
    {
        // check config presence
        if (empty($this->config['merchantId'])
            || empty($this->config['projectId'])
            || empty($this->config['apiPassword'])
        ) {
            addActivityLog(
                ActivityLog::SEVERITY_ERROR,
                ActivityLog::TYPE_COMMUNICATION,
                'ThePay: Chybí údaje pro požadovaný jazyk v nastavení eshopu, případně využijte omezení platby/dopravy, aby se způsob doručení v tomto jazyce nenabízel.',
                [
                    ...ActivityLog::addObjectData([$this->getId() => $this->getName()], 'deliveryPayment'),
                    ...['context-languageID' => Contexts::get(LanguageContext::class)->getActiveId()],
                ]
            );

            return [];
        }

        $overwriteMethodNames = $this->config['overwriteMethodNames'] ?? [];
        $methods = [];
        try {
            $preferredLang = $this->getPreferredLanguage(
                Contexts::get(LanguageContext::class)->getActiveId()
            );

            $overrideNamePerLang = [
                'de' => ['card' => 'Kartenzahlung'], // preferred lang falls back to 'de' for 'at' too
                'pl' => ['card' => 'Płatność kartą'],
            ];
            foreach ($this->getApiContext()->getActivePaymentMethods(new PaymentMethodFilter([$currency], [], []), new LanguageCode($preferredLang)) as $method) {
                $methods[$method->getCode()] = [
                    'name' => $overwriteMethodNames[$method->getCode()] ?? $overrideNamePerLang[$preferredLang][$method->getCode()] ?? $method->getTitle(),
                    'id' => $method->getCode(),
                    'image' => $method->getImageUrl()->getValue(),
                ];
            }
        } catch (ApiException|MissingExtensionException|\RuntimeException $e) {
            addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'Chyba ThePay při načítání platebních metod: '.$e->getMessage());
            $sentry = getRaven();
            $sentry->captureException($e);
        }

        return $methods;
    }

    public function hasOnlinePayment()
    {
        return true;
    }

    public function doReturnPayment(array $payment, float $amount)
    {
        $amountCents = (int) floor($amount * -100); // musí být integer v centech
        $full = (($payment['price'] + $amount) == 0);
        try {
            $this->getApiContext()->createPaymentRefund($payment['payment_data']['session'], $amountCents, 'vrácení '.(($full) ? 'platby' : 'přeplatku'));
            $result = ['id' => $payment['payment_data']['session']];

            return $result;
        } catch (ApiException|InvalidArgumentException|RuntimeException $e) {
            $message = translate('returnFailed', 'orderPayment');
            $message .= translate('returnFailedMessage', 'orderPayment').$e->getMessage();

            throw new PaymentException($message);
        }

        return true;
    }

    /** Check paid orders */
    public function checkPaidOrders()
    {
        if (getVal('test', $this->config)) {
            return false;
        }
        if (empty($this->config['merchantId']) || empty($this->config['projectId']) || empty($this->config['apiPassword'])) {
            return false;
        }
        $context = $this->getApiContext();
        $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))')
            ->execute();
        foreach ($orderPayments as $orderPayment) {
            $paymentData = json_decode($orderPayment['payment_data'], true);
            if ($paymentData['paymentClass'] !== 'ThePay20' || empty($paymentData['session'])) {
                continue;
            }
            $thirdPartyPaymentState = false;
            try {
                $response = $context->getPayment($paymentData['session']);
                $thirdPartyPaymentState = $response->getState(); // https://dataapi21.docs.apiary.io/#paymentStatesEnum
            } catch (ApiException|InvalidArgumentException $e) {
                addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'ThePay error: '.$e->getMessage());
                continue;
            }
            if ($thirdPartyPaymentState === 'paid') {
                $this->setOrder($orderPayment['id_order']);
                try {
                    $this->setStatus(Payment::STATUS_FINISHED, $paymentData['session']);
                } catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
                    throw $e;
                } catch (Exception $e) {
                    getRaven()->captureException($e);
                }
            } elseif ($thirdPartyPaymentState === 'expired' || $thirdPartyPaymentState === 'partially_refunded' || $thirdPartyPaymentState === 'preauth_cancelled') {
                $this->setOrder($orderPayment['id_order']);
                try {
                    $this->setStatus(Payment::STATUS_STORNO, $paymentData['session']);
                } catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
                    throw $e;
                } catch (Exception $e) {
                    getRaven()->captureException($e);
                }
            }
        }

        return 0;
    }

    private function getPreferredLanguage(string $language): string
    {
        $thePaySupportedLanguages = [
            'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo',
            'br', 'bs', 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', 'el', 'en', 'eo', 'es',
            'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
            'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj',
            'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv',
            'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr',
            'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc',
            'sd', 'se', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg',
            'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo',
            'xh', 'yi', 'yo', 'za', 'zh', 'zu',
        ];

        $preferredLang = $language;

        if (!in_array($preferredLang, $thePaySupportedLanguages)) {
            if ($preferredLang === 'at') {
                $preferredLang = 'de'; // use german language for at - austria
            } else {
                $preferredLang = 'en'; // if not supported fallback to english
            }
        }

        return $preferredLang;
    }
}
