<?php

use Adyen\Webhook\Exception\AuthenticationException;
use Adyen\Webhook\Receiver\HmacSignature;
use Adyen\Webhook\Receiver\NotificationReceiver;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\BankAutoPaymentBundle\BankAutoPaymentBundle;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\OrderingBundle\Exception\PaymentException;
use KupShop\OrderingBundle\Util\Order\PaymentOrderSubMethodTrait;

/**
 * "adyen/php-api-library": "^15.2.0",
 * "adyen/php-webhook-module": "^0.8.0".
 */
class Adyen extends Payment
{
    use PaymentOrderSubMethodTrait;

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

    public static bool $canAutoReturn = true;

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

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

    public $class = 'Adyen';

    protected $pay_method = Payment::METHOD_ONLINE;

    protected $method;

    public function createAdyenSession()
    {
        $session = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService('session');
        $adyenSession = $session->get('adyen_'.$this->orderId);
        // Aby když mě to přesměruje na detail a nestihl přijit webhook, naukázal se mi znova platební widget
        if ($adyenSession['paymentResult'] == \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED) {
            return false;
        }

        // Když chci změnit metodu, tak tohle přeskočit
        if (!getVal('rpm')) {
            // Pokud mám sessionId a není starší pěti minut, použiji ho.
            if ($adyenSession['sessionId'] && $adyenSession['expired'] > time() - (5 * 60)) {
                return true;
            }
        }

        $service = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());

        $createCheckoutSessionRequest = new \Adyen\Model\Checkout\CreateCheckoutSessionRequest();
        $createCheckoutSessionRequest->setCountryCode(Contexts::get(\KupShop\KupShopBundle\Context\CountryContext::class)->getActiveId())
            ->setMerchantAccount($this->config['merchantAccount'])
            ->setReturnUrl($this->getGenericPaymentUrl(5))
            ->setReference($this->order->order_no.'_'.time())
            ->setShopperEmail($this->order->getUserEmail())
            ->setShopperLocale($this->getLocale());

        $amount = new \Adyen\Model\Checkout\Amount();
        $amount->setValue($this->order->getRemainingPayment(true)->mul(DecimalConstants::hundred())->asInteger())
            ->setCurrency($this->order->getCurrency());

        $createCheckoutSessionRequest->setAmount($amount);

        $this->kibanaLogger->notice('[Adyen] Create session', [
            'order' => $this->order->order_no,
            'amount' => $amount->getValue(),
        ]);

        if ($this->method && !getVal('rpm')) {
            $adyenMethods = [$this->method];
        } else {
            $methods = $this->getAvailableMethods($amount);
            $adyenMethods = array_values(array_map(fn ($val) => $val['type'], $methods));
        }
        $createCheckoutSessionRequest->setAllowedPaymentMethods($adyenMethods);

        $items = [];
        foreach ($this->order->fetchItems() as $item) {
            $items[] = (new \Adyen\Model\Checkout\LineItem())
                ->setQuantity($item['pieces'])
                ->setTaxPercentage($item['vat'])
                ->setDescription($item['descr'])
                ->setAmountIncludingTax($item['total_price']['value_with_vat']->div(toDecimal($item['pieces']))->mul(DecimalConstants::hundred())->asInteger());
        }
        $createCheckoutSessionRequest->setLineItems($items);

        $adyenSessionRequestResult = $service->sessions($createCheckoutSessionRequest);
        $session->set('adyen_'.$this->orderId, [
            'sessionId' => $adyenSessionRequestResult->getId(),
            'sessionData' => $adyenSessionRequestResult->getSessionData(),
            'expired' => time(),
            'orderId' => $this->orderId,
        ]
        );

        return true;
    }

    /** Return from gateway */
    public function processStep_5()
    {
        $sessionId = $this->request->get('sessionId');
        $sessionResult = $this->request->get('sessionResult');

        if ($sessionResult) {
            $result = $this->getSessionStatus($sessionId, $sessionResult);
        }

        // Z informace redirectResult vubec nic nedostanu. Takze proste zakaznikovi zakazu znova zaplatit a bude se cekat az vysledek dorazi pres webhook.
        if (($result ?? false) == \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED || $this->request->get('redirectResult')) {
            $adyenSession = $this->request->getSession()->get('adyen_'.$this->orderId) ?: [];
            $this->request->getSession()->set('adyen_'.$this->orderId, $adyenSession + ['paymentResult' => \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED]);
        }

        $this->success(translate('paymentSuccess', 'payment'));
    }

    public function getSessionStatus($sessionId, $sessionResult)
    {
        $paymentsApi = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
        $sessionResultResponse = $paymentsApi->getResultOfPaymentSession($sessionId, ['queryParams' => ['sessionResult' => $sessionResult]]);

        return $sessionResultResponse->getStatus();
    }

    public function startPayment()
    {
        $pathData = ['id' => $this->order->id];
        if (!\User::getCurrentUser()) {
            $pathData['cf'] = $this->order->getSecurityCode();
        }
        redirection(path('payment-redirect', $pathData));
    }

    public function processStep_1()
    {
        redirection($this->order->getDetailUrl());
    }

    /** Return from gateway */
    public function processStep_10()
    {
        $request = json_decode($this->request->getContent() ?? '', true);

        if (empty($request['notificationItems'])) {
            $this->error('Not found !!');
        }

        // Setup NotificationReceiver with dependency injection or create an instance as follows
        $notificationReceiver = new NotificationReceiver(new HmacSignature());

        $_SERVER['PHP_AUTH_USER'] = $this->request->headers->get('php-auth-user');
        $_SERVER['PHP_AUTH_PW'] = $this->request->headers->get('php-auth-pw');

        if (!$notificationReceiver->isAuthenticated(
            $request['notificationItems'][0]['NotificationRequestItem'],
            $this->config['merchantAccount'],
            $this->config['basic_name'],
            $this->config['basic_pass']
        )) {
            throw new AuthenticationException('Incoming webhook wasn\'t authenticated!');
        }

        foreach ($request['notificationItems'] as $notificationItem) {
            $notifItem = $notificationItem['NotificationRequestItem'];

            if ($notificationReceiver->validateHmac($notifItem, $this->config['hmac']) && in_array($notifItem['eventCode'], ['AUTHORISATION', 'REFUND'])) {
                $isRefund = ($notifItem['eventCode'] == 'REFUND');
                $message = ($isRefund ? '[Adyen] Refund payment' : '[Adyen] Incoming payment');
                $this->kibanaLogger->notice($message, [
                    'notificationItem' => $notifItem,
                ]);

                $merchantReference = $notifItem['merchantReference'];
                $ref = explode('_', $merchantReference);
                $id_order = ($ref[0] ?? false);

                if ($notifItem['success'] == 'true') {
                    if ($id_order) {
                        try {
                            $order = Order::createFromDbOrderNo($id_order);
                            $payment_data = [
                                'paymentClass' => self::class,
                                'session' => $notifItem['additionalData']['checkoutSessionId'] ?? null,
                                'pspReference' => $notifItem['pspReference'] ?? null,
                            ];
                            $price = $notifItem['amount']['value'] / 100;
                            $note = "Platba modulu {$this->class}";
                            if ($isRefund) {
                                $price *= -1;
                                $note = "Vrácení platby: objednávka {$order->order_no}, ID platby: {$notifItem['originalReference']}";
                                $payment_data['originalReference'] = $notifItem['originalReference'];
                            }
                            $order?->insertPayment($price, $note, method: self::METHOD_ONLINE, payment_data: json_encode($payment_data));

                            if ($order && !empty($notifItem['paymentMethod'])) {
                                $this->setPaymentSubMethod($notifItem['paymentMethod'], $order);
                            }
                        } catch (InvalidArgumentException $exception) {
                            getRaven()->captureException($exception);
                        }
                    }
                } else {
                    addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
                        "Objednávka {$id_order}: {$message} error: {$notifItem['reason']}", $notifItem, [BankAutoPaymentBundle::LOG_TAG_ADYEN]);
                }
            }
        }

        $this->sendNotificationResponse(200, '[accepted]');
    }

    public function doReturnPayment(array $payment, float $amount)
    {
        if ($paymentPspReference = $payment['payment_data']['pspReference'] ?? null) {
            $class = new \Adyen\Service\Checkout\ModificationsApi($this->getApiClient());
            $paymentRefundRequest = new \Adyen\Model\Checkout\PaymentRefundRequest();
            $paymentRefundRequest->setMerchantAccount($this->config['merchantAccount']);
            $paymentRefundRequest->setReference($this->order->order_no.'_'.time());
            $amountCents = (int) floor($amount * -100); // musí být integer v centech
            $amount = new \Adyen\Model\Checkout\Amount();
            $amount->setValue($amountCents)->setCurrency($this->order->getCurrency());
            $paymentRefundRequest->setAmount($amount);
            $paymentRefundResponse = $class->refundCapturedPayment($paymentPspReference, $paymentRefundRequest);
            if ($paymentRefundResponse->getStatus() == 'received') {
                // the refund request was successfully received by Adyen - no result yet,
                // because you receive the outcome of the refund request asynchronously, in a REFUND webhook.

                // Vrácení platby bylo odesláno na platební bránu.
                throw new PaymentException(translate('returnSucceed', 'orderPayment'), 'Adyen');
            }
        }

        // Vrácení platby se nezdařilo, vyřešte, prosím, na platební bráně.);
        throw new PaymentException(translate('returnFailed', 'orderPayment'));
    }

    public function getApiClient(): Adyen\Client
    {
        if (!isset($this->apiClient)) {
            $config = new \Adyen\Config();
            $testEnv = (isDevelopment() || ($this->config['test'] ?? '0') == '1');

            if (!$testEnv) {
                $config->set('prefix', $this->config['api_urls_prefix']);
            }

            $client = new \Adyen\Client($config);

            $client->setXApiKey($this->config['apikey']);
            $client->setEnvironment($testEnv ? \Adyen\Environment::TEST : \Adyen\Environment::LIVE);
            $client->setTimeout(5);
            $this->apiClient = $client;
        }

        return $this->apiClient;
    }

    /**
     * @throws \Adyen\AdyenException
     */
    public function getAvailableMethods(Adyen\Model\Checkout\Amount|Decimal|null $amount = null)
    {
        $countryContext = Contexts::get(\KupShop\KupShopBundle\Context\CountryContext::class);
        $currencyContext = Contexts::get(\KupShop\KupShopBundle\Context\CurrencyContext::class);
        $currency = $currencyContext->getActiveId();
        $country = $countryContext->getActiveId();
        $cacheKey = "adyen-methods-{$currency}-{$country}-{$this->getLocale()}";
        if (!$amount) {
            if ($paymentMethods = getCache($cacheKey)) {
                return $paymentMethods;
            }
        }

        try {
            $class = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());

            $paymentMethodsRequest = new \Adyen\Model\Checkout\PaymentMethodsRequest();
            $paymentMethodsRequest->setMerchantAccount($this->config['merchantAccount']);
            $paymentMethodsRequest->setCountryCode($countryContext->getActiveId());
            $paymentMethodsRequest->setShopperLocale($this->getLocale());

            if ($amount instanceof Decimal) {
                $amount = (new \Adyen\Model\Checkout\Amount())->setValue($amount->mul(DecimalConstants::hundred())->asInteger())->setCurrency($currencyContext->getActiveId());
            }

            if ($amount) {
                $paymentMethodsRequest->setAmount($amount);
            }

            $paymentMethodsResponse = $class->paymentMethods($paymentMethodsRequest);

            $paymentMethods = [];
            foreach ($paymentMethodsResponse->getPaymentMethods() as $method) {
                if ($method->getType() == 'scheme') {
                    $images = array_map(function ($brand) {
                        return "https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/{$brand}.svg";
                    }, $method->getBrands());
                } else {
                    $images = ["https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/{$method->getType()}.svg"];
                }

                $paymentMethods[$method->getType()] = [
                    'name' => $method->getName(),
                    'type' => $method->getType(),
                    'images' => $images,
                ];
            }
        } catch (Exception $e) {
            addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
                'Chyba Adyen při načítání platebních metod: '.$e->getMessage(),
                ['key' => $cacheKey, 'config' => $this->config], [BankAutoPaymentBundle::LOG_TAG_ADYEN]);

            return [];
        }

        if (!$amount) {
            setCache($cacheKey, $paymentMethods);
        }

        return $paymentMethods;
    }

    public function hasOnlinePayment()
    {
        return true;
    }

    public static function getSettingsConfiguration(): array
    {
        return ['fields' => [
            'apikey' => [
                'title' => 'API klíč',
                'type' => 'text',
            ],
            'merchantAccount' => [
                'title' => 'Merchant Account',
                'type' => 'text',
            ],
            'clientKey' => [
                'title' => 'Client Key',
                'type' => 'text',
            ],
            'hmac' => [
                'title' => 'HMAC key',
                'type' => 'text',
            ],
            'api_urls_prefix' => [
                'title' => 'API URLs prefix',
                'type' => 'text',
            ],
            'basic_name' => [
                'title' => 'Basic auth name',
                'type' => 'text',
            ],
            'basic_pass' => [
                'title' => 'Basic auth pass',
                'type' => 'text',
            ],
            'test' => [
                'title' => 'Testovací režim',
                'type' => 'toggle',
            ],
        ]];
    }

    public function getLocale()
    {
        $languageContext = Contexts::get(\KupShop\KupShopBundle\Context\LanguageContext::class);

        return $languageContext->getActive()->getLocale();
    }

    //    private function checkWebhooks()
    //    {
    //        Maybe later alligator
    //
    //        $merchantAccount = $this->config['merchantAccount'];
    //        $webhooksApi = new \Adyen\Service\Management\WebhooksMerchantLevelApi($this->getApiClient());
    //        $allWebhooks = $webhooksApi->listAllWebhooks($merchantAccount);
    //
    //        $webhooks = $allWebhooks->getData();
    //
    //        if ($webhooks) {
    //            return true;
    //        }
    //
    //        $createMerchantWebhookRequest = new \Adyen\Model\Management\CreateMerchantWebhookRequest();
    //        $createMerchantWebhookRequest->setUrl(path('kupshop_ordering_payment_legacypayment', ['class' => 'Adyen', 'step' => 10], \Symfony\Component\Routing\Router::ABSOLUTE_URL))
    //            ->setDescription('API - wpjshop')
    //            ->setType('standard')
    //            ->setCommunicationFormat('json');
    //
    //        $webhooksApi->setUpWebhook($merchantAccount, $createMerchantWebhookRequest);
    //    }
}
