<?php

namespace AppBundle\Service;

use Psr\Log\LoggerInterface;
use MercadoPago\Client\Payment\PaymentClient;
use MercadoPago\Client\PaymentMethod\PaymentMethodClient;
use MercadoPago\Client\Common\RequestOptions;
use MercadoPago\Exceptions\MPApiException;
use MercadoPago\MercadoPagoConfig;
use MercadoPago\Resources\PaymentMethod\PaymentMethodListResult;
use MercadoPago\Resources\Payment;
use Ramsey\Uuid\Uuid;
use Sylius\Component\Payment\Model\PaymentInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * @see https://www.mercadopago.com.mx/developers/es/guides/payments/api/other-features
 */
class MercadopagoManager
{
    private $settingsManager;
    private $checkoutLogger;

    public function __construct(
        SettingsManager $settingsManager,
        LoggerInterface $checkoutLogger)
    {
        $this->settingsManager = $settingsManager;
        $this->checkoutLogger = $checkoutLogger;
    }

    public function configure()
    {
        MercadoPagoConfig::setAccessToken($this->settingsManager->get('mercadopago_access_token'));
    }

    /**
     * @see https://mercadopago.com/developers/en/docs/checkout-bricks/card-payment-brick/payment-submission
     */
    public function authorize(PaymentInterface $payment): Payment
    {
        $order = $payment->getOrder();
        $restaurant = $order->getRestaurant();

        $applicationFee = 0;
        $accessToken = null;
        if (null !== $restaurant) {
            $account = $restaurant->getMercadopagoAccount();
            if ($account) {
                $applicationFee = $order->getFeeTotal();
                $accessToken = $account->getAccessToken();
            }
        }

        if (null !== $accessToken) {
            MercadoPagoConfig::setAccessToken($accessToken);
        } else {
            $this->configure();
        }

        $order = $payment->getOrder();

        $client = new PaymentClient();

        $requestOptions = new RequestOptions();
        $requestOptions->setCustomHeaders([
            sprintf('X-Idempotency-Key: %s', Uuid::uuid4()->toString())
        ]);

        $mpPaymentMethod = $this->getPaymentMethod($payment);

        $payload = [
            'transaction_amount' => ($payment->getAmount() / 100),
            'token' => $payment->getStripeToken(),
            'description' => sprintf('Order %s', $order->getNumber()),
            'installments' => (int) ($payment->getMercadopagoInstallments() ?? 1),
            'payment_method_id' => $payment->getMercadopagoPaymentMethod(),
            'issuer_id' => $payment->getMercadopagoIssuer(),
            'payer' => [
                'email' => $payment->getMercadopagoPayerEmail(),
            ],
        ];

        // Fist, we try with authorize/capture if possible
        $payload['capture'] = null !== $mpPaymentMethod && $mpPaymentMethod->deferred_capture === 'supported';

        if ($applicationFee > 0) {
            $payload['application_fee'] = ($applicationFee / 100);
        }

        try {
            return $client->create($payload, $requestOptions);
        } catch (MPApiException $e) {

            $this->checkoutLogger->error(
                sprintf('Mercadopago - API error %s while trying to authorize payment: %s', $e->getApiResponse()->getStatusCode(), json_encode($e->getApiResponse()->getContent()))
            );

            if (400 === $e->getApiResponse()->getStatusCode()) {

                $responseContent = $e->getApiResponse()->getContent();
                $errorCode = $responseContent['cause'][0]['code'];

                // https://www.mercadopago.com/developers/en/reference/payments/_payments/post
                // 2077 - Deferred capture not supported.
                // If this error occurs, note that deferred capture is not supported
                // and adjust your request accordingly.
                if (2077 === $errorCode) {
                    $payload['capture'] = true;

                    $result = $client->create($payload, $requestOptions);

                    // In this case, the payment is already captured
                    $payment->setState(PaymentInterface::STATE_COMPLETED);

                    return $result;
                }

            }

            throw $e;
        }
    }

    /**
     * @see https://www.mercadopago.com/developers/en/docs/checkout-api/payment-management/capture-authorized-payment
     */
    public function capture(PaymentInterface $payment): Payment
    {
        // FIXME: should be refactored

        $order = $payment->getOrder();

        $accessToken = null;
        if (null !== $order->getRestaurant()) {
            $account = $order->getRestaurant()->getMercadopagoAccount();
            if ($account) {
                $accessToken = $account->getAccessToken();
            }
        }

        if (null !== $accessToken) {
            MercadoPagoConfig::setAccessToken($accessToken);
        } else {
            $this->configure();
        }

        $client = new PaymentClient();

        $requestOptions = new RequestOptions();
        $requestOptions->setCustomHeaders([
            sprintf('X-Idempotency-Key: %s', Uuid::uuid4()->toString())
        ]);

        return $client->capture($payment->getMercadopagoPaymentId(), ($payment->getAmount() / 100), $requestOptions);
    }

    public function getPayment(PaymentInterface $payment): ?Payment
    {
        $order = $payment->getOrder();

        $accessToken = null;
        if (null !== $order->getRestaurant()) {
            $account = $order->getRestaurant()->getMercadopagoAccount();
            if ($account) {
                $accessToken = $account->getAccessToken();
            }
        }

        if (null !== $accessToken) {
            MercadoPagoConfig::setAccessToken($accessToken);
        } else {
            $this->configure();
        }

        $client = new PaymentClient();

        return $client->get($payment->getMercadopagoPaymentId());
    }

    public function getPaymentMethod(PaymentInterface $payment): ?PaymentMethodListResult
    {
        try {

            $this->configure();

            $client = new PaymentMethodClient();

            $result = $client->list();

            foreach ($result->data as $paymentMethod) {
                if ($paymentMethod->id === $payment->getMercadopagoPaymentMethod()) {
                    return $paymentMethod;
                }
            }
        } catch(\Exception $e) {
            $this->checkoutLogger->error(
                sprintf('Mercadopago - Error %s while trying to read payment method with id %s', $e->getMessage(), $payment->getMercadopagoPaymentMethod())
            );
            return null;
        }

        $this->logger->error(
            sprintf('Mercadopago - Error MercadoPago\PaymentMethod not found for payment method with id %s', $payment->getMercadopagoPaymentMethod())
        );
        return null;
    }
}
