<?php

/*
 * This file is part of the VitiCore 2.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Controller\Site;

use CpCreation\VitiCore\Behavior\HasPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\AxeptaPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\CawlPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\EtransactionsPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\MoneticoPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\PayPlugPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\Sherlock2Payment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\SogeCommercePayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\SystemPayPayment;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Payment\Model\Payment;
use OnlinePayments\Sdk\Domain\GetHostedCheckoutResponse;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Translation\TranslatorInterface;

final class MethodPaymentController extends AbstractController
{
    public function __construct(
        private readonly EtransactionsPayment $etransactionsPayment,
        private readonly array $sherlockDir,
        private readonly MoneticoPayment $moneticoPayment,
        private readonly SystemPayPayment $systempay,
        private readonly SogeCommercePayment $sogeCommercePayment,
        private readonly AxeptaPayment $axeptaPayment,
        private readonly Sherlock2Payment $sherlock2Payment,
        private readonly PayPlugPayment $payPlugPayment,
        private readonly TranslatorInterface $translator,
        private readonly RequestStack $requestStack,
        private readonly ParameterBagInterface $parameterBag,
        private readonly CawlPayment $cawlPayment,
    ) {
    }

    private function getSession(): ?SessionInterface
    {
        return $this->requestStack->getSession();
    }

    private function isStandalonePaymentFlow(): bool
    {
        return 'Payment' === $this->getSession()?->get('payment');
    }

    private function redirectCheckoutWithFlash(string $level, string $message): RedirectResponse
    {
        $this->addFlash($level, $message);

        $cartId = $this->resolveReturnCartId();

        if (null !== $cartId) {
            return $this->redirectToRoute('shop_checkout_cart', [
                'cartId' => $cartId,
            ]);
        }

        return $this->redirectToRoute('shop_checkout');
    }

    private function redirectCheckoutSuccess(): RedirectResponse
    {
        $cartId = $this->resolveReturnCartId();

        if (null === $cartId) {
            return $this->redirectToRoute('shop_checkout');
        }

        return $this->redirectToRoute('shop_checkout_success', [
            'cartId' => (string) $cartId,
        ]);
    }

    private function resolveReturnCartId(): ?string
    {
        $request = $this->requestStack->getCurrentRequest() ?? $this->requestStack->getMainRequest();
        $cartId = $request?->get('cartId');

        if (\is_string($cartId) && '' !== $cartId) {
            return $cartId;
        }

        $sessionCartId = $this->getSession()?->get('cart');

        return \is_string($sessionCartId) && '' !== $sessionCartId ? $sessionCartId : null;
    }

    private function renderLegacyPaymentReturn(string $message, ?string $error = null): Response
    {
        return $this->render('payment/payment_return.html.twig', [
            'classMenu' => 'black',
            'msg' => $message,
            'error' => $error,
        ]);
    }

    private function buildCartReturnMessage(string $status, ?string $reason = null): string
    {
        if ('success' === $status) {
            return $this->translator->trans('shop.return.success');
        }

        $key = 'shop.return.'.$status;
        if (null !== $reason) {
            $key .= '_with_reason';

            return $this->translator->trans($key, ['%reason%' => $reason]);
        }

        return $this->translator->trans($key);
    }

    /**
     * @return array{msg: string, error: ?string}
     */
    private function buildLegacyPaymentReturnPayload(string $status, ?string $reason = null): array
    {
        return match ($status) {
            'success' => [
                'msg' => $this->translator->trans('payment.success'),
                'error' => null,
            ],
            'canceled' => [
                'msg' => $this->translator->trans('payment.canceled'),
                'error' => $reason,
            ],
            default => [
                'msg' => $this->translator->trans('payment.refused'),
                'error' => $reason ?? $this->translator->trans('payment.error'),
            ],
        };
    }

    private function respondToPaymentReturn(string $status, ?string $reason = null): Response
    {
        $level = match ($status) {
            'success' => 'success',
            'canceled' => 'warning',
            default => 'error',
        };

        if (!$this->isStandalonePaymentFlow()) {
            if ('success' === $status) {
                return $this->redirectCheckoutSuccess();
            }

            return $this->redirectCheckoutWithFlash($level, $this->buildCartReturnMessage($status, $reason));
        }

        $payload = $this->buildLegacyPaymentReturnPayload($status, $reason);

        return $this->renderLegacyPaymentReturn($payload['msg'], $payload['error']);
    }

    /**
     * @param array<string, mixed> $context
     */
    private function extractReturnReason(Request $request, array $context = []): ?string
    {
        $payload = array_replace($request->query->all(), $request->request->all(), $context);
        $keys = [
            'motifrefus',
            'reason',
            'error_description',
            'error_message',
            'vads_error_message',
            'vads_payment_error',
            'message',
            'error',
            'Status',
            'status',
            'Code',
            'Erreur',
            'vads_extra_result',
            'vads_result',
            'vads_auth_result',
            'responseCode',
            'acquirerResponseCode',
        ];

        foreach ($keys as $key) {
            if (!array_key_exists($key, $payload)) {
                continue;
            }

            $reason = $this->normalizeReturnReason($payload[$key]);
            if (null !== $reason) {
                return $reason;
            }
        }

        return null;
    }

    private function normalizeReturnReason(mixed $value): ?string
    {
        if (\is_array($value) || null === $value) {
            return null;
        }

        $reason = trim(strip_tags((string) $value));
        if ('' === $reason) {
            return null;
        }

        $normalized = mb_strtolower($reason);
        if (\in_array($normalized, [
            '00000',
            '00',
            '000',
            '001',
            'ok',
            'authorized',
            'authorised',
            'payment',
            'payetest',
            'success',
            'succeeded',
            'paid',
            'payment_created',
            'in_progress',
        ], true)) {
            return null;
        }

        if (\in_array($normalized, [
            'annulation',
            'annule',
            'annulé',
            'cancel',
            'cancelled',
            'canceled',
            'abandoned',
            'abandonne',
            'abandonné',
        ], true)) {
            return null;
        }

        $reason = preg_replace('/\s+/', ' ', $reason) ?? $reason;

        if (preg_match('/^[A-Z0-9_-]{2,}$/', $reason) || preg_match('/^\d{2,}$/', $reason)) {
            return $this->translator->trans('shop.return.reason_code', ['%code%' => $reason]);
        }

        return $reason;
    }

    /**
     * @param array<string, mixed> $context
     */
    private function isCanceledReturn(Request $request, array $context = []): bool
    {
        $payload = array_replace($request->query->all(), $request->request->all(), $context);
        $keys = [
            'code-retour',
            'reason',
            'status',
            'Status',
            'vads_trans_status',
            'vads_trans_state',
            'event',
            'error',
            'message',
        ];

        foreach ($keys as $key) {
            if (!array_key_exists($key, $payload)) {
                continue;
            }

            $value = (string) $payload[$key];
            if (preg_match('/annul|cancel|aband/i', $value)) {
                return true;
            }
        }

        return false;
    }

    #[Route('/paiement/{id}/check', name: 'payment_check')]
    public function paymentCheck(Request $request, Cart $cart): Response
    {
        return $this->render('payment/check.html.twig', [
            'classMenu' => 'black',
            'cart' => $cart,
        ]);
    }

    #[Route('/paiement/{id}/banktransfert', name: 'payment_banktransfert')]
    public function paymentBankTransfert(Request $request, Cart $cart): Response
    {
        return $this->render('payment/banktransfert.html.twig', [
            'classMenu' => 'black',
            'cart' => $cart,
        ]);
    }

    #[Route('/paiement/{id}/etransactions', name: 'payment_cb_etransactions')]
    #[Route('/paiement/{id}/etransactions', name: 'payment_etransactions')]
    public function paymentEtransactions(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): ?Response {
        $data = $this->etransactionsPayment->createData($payment);

        $view = 'cb.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/cb/retour', name: 'payment_cb_return')]
    public function paymentCbReturn(Request $request): Response
    {
        $code = $request->query->get('Erreur');

        if ('00000' === $code) {
            return $this->respondToPaymentReturn('success');
        }

        $reason = $this->extractReturnReason($request, ['Erreur' => $code]);
        $status = $this->isCanceledReturn($request) ? 'canceled' : 'refused';

        return $this->respondToPaymentReturn($status, $reason);
    }

    #[Route('/paiement/{id}/cb_monetico', name: 'payment_cb_monetico')]
    #[Route('/paiement/{id}/cb_monetico', name: 'payment_monetico')]
    public function paymentCbMonetico(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $data = $this->moneticoPayment->createData($payment);

        $view = 'cb.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/cb/retour/erreur', name: 'payment_cb_return_error')]
    #[Route('/paiement/cb_monetico/retour/erreur', name: 'payment_cb_monetico_return_error')]
    #[Route('/paiement/cb_monetico/retour/erreur', name: 'payment_monetico_return_error')]
    #[Route('/paiement/sogecommerce/retour/erreur', name: 'payment_sogecommerce_return_error')]
    #[Route('/paiement/systempay/retour/erreur', name: 'payment_systempay_return_error')]
    #[Route('/paiement/axepta/retour/erreur', name: 'payment_axepta_return_error')]
    public function paymentCbReturnError(Request $request): Response
    {
        $reason = $this->extractReturnReason($request);
        $status = $this->isCanceledReturn($request) ? 'canceled' : 'refused';

        return $this->respondToPaymentReturn($status, $reason);
    }

    #[Route('/paiement/cb/retour/annule', name: 'payment_cb_return_canceled')]
    #[Route('/paiement/cb_monetico/retour/annule', name: 'payment_cb_monetico_return_canceled')]
    #[Route('/paiement/sogecommerce/retour/annule', name: 'payment_sogecommerce_return_canceled')]
    #[Route('/paiement/systempay/retour/annule', name: 'payment_systempay_return_canceled')]
    #[Route('/paiement/axepta/retour/annule', name: 'payment_axepta_return_canceled')]
    public function paymentCbReturnCanceled(Request $request): Response
    {
        $reason = $this->extractReturnReason($request);

        return $this->respondToPaymentReturn('canceled', $reason);
    }

    #[Route('/paiement/cb/retour/success', name: 'payment_cb_return_success')]
    #[Route('/paiement/cb_monetico/retour/success', name: 'payment_cb_monetico_return_success')]
    #[Route('/paiement/cb_monetico/retour/success', name: 'payment_monetico_return_success')]
    #[Route('/paiement/sogecommerce/retour/success', name: 'payment_sogecommerce_return_success')]
    #[Route('/paiement/systempay/retour/success', name: 'payment_systempay_return_success')]
    #[Route('/paiement/axepta/retour/success', name: 'payment_axepta_return_success')]
    public function paymentCbReturnSuccess(Request $request): Response
    {
        return $this->respondToPaymentReturn('success');
    }

    #[Route('/paiement/{id}/sherlock', name: 'payment_cb_sherlock')]
    #[Route('/paiement/{id}/sherlock', name: 'payment_sherlock')]
    public function paymentSherlock(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): ?Response {
        $data = [];
        $params = $this->sherlockPayment->createData($payment);
        $pathBin = $this->sherlockDir['sherlock_request_path'];

        $result = exec("$pathBin $params");

        $doc = new \DOMDocument();
        $doc->loadHTML($result);

        $form = $doc->getElementsByTagName('form');
        $data['URL'] = $form->item(0)->attributes->getNamedItem('action')->textContent;

        $inputs = $form->item(0)->getElementsByTagName('input');
        for ($i = 0; $i < $inputs->length; ++$i) {
            if ('DATA' === $inputs->item($i)->attributes->getNamedItem('name')->textContent) {
                $data['DATA'] = $inputs->item($i)->attributes->getNamedItem('value')->textContent;
            }
        }

        $view = 'cb.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/{id}/cb_systempay', name: 'payment_cb_systempay')]
    #[Route('/paiement/{id}/cb_systempay', name: 'payment_systempay')]
    public function paymentSystemPay(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $data = $this->systempay->createData($payment);

        $this->getSession()->set('payment', 'Cart');
        if ($payment instanceof Payment) {
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/cb.html.twig', [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/{id}/cb_sogecommerce', name: 'payment_cb_sogecommerce')]
    #[Route('/paiement/{id}/cb_sogecommerce', name: 'payment_sogecommerce')]
    public function paymentCbSogeCommerce(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $data = $this->sogeCommercePayment->createData($payment);

        $view = 'cb.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/{id}/cb_axepta', name: 'payment_cb_axepta')]
    #[Route('/paiement/{id}/cb_axepta', name: 'payment_axepta')]
    public function paymentCbAxepta(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $data = $this->axeptaPayment->createData($payment);

        $file = 'log_axepta.txt';
        $fullpath = $this->parameterBag->get('kernel.project_dir').$file;

        $log = fopen($fullpath, 'w');
        if (false !== $log) {
            $timestamp = date('Y-m-d H:i:s');
            fwrite($log, "[$timestamp] - $data".\PHP_EOL);
            fclose($log);
        }

        $view = 'cb.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/{id}/cb_sherlock2', name: 'payment_cb_sherlock2')]
    #[Route('/paiement/{id}/cb_sherlock2', name: 'payment_sherlock2')]
    public function paymentCbSherlock2(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $data = $this->sherlock2Payment->createData($payment);

        $view = 'cb.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/sherlock2/retour', name: 'payment_sherlock2_normal')]
    public function paymentSherlock2Return(Request $request): Response
    {
        $response = $request->request->all();
        $data = $this->sherlock2Payment->valid($response);

        if (($data['Valid'] ?? false) === true) {
            return $this->respondToPaymentReturn('success');
        }

        $reason = $this->extractReturnReason($request, $data);

        return $this->respondToPaymentReturn('refused', $reason);
    }

    #[Route('/paiement/{id}/payplug', name: 'payment_payplug')]
    public function paymentPayPlug(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $view = 'cb_payplug.html.twig';

        $form = $this->createFormBuilder()->getForm();

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'form' => $form->createView(),
            'cart' => $payment,
        ]);
    }

    #[Route('/paiement/payplug/{id}/process', name: 'payment_payplug_process')]
    public function paymentPayPlugProcess(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): RedirectResponse {
        $data = $this->payPlugPayment->createData($payment);

        return $this->redirect($data->hosted_payment->payment_url);
    }

    #[Route('/paiement/payplug/success', name: 'payment_payplug_success')]
    public function paymentPayPlugSuccess(Request $request): Response
    {
        return $this->respondToPaymentReturn('success');
    }

    #[Route('/paiement/payplug/error', name: 'payment_payplug_return_error')]
    public function paymentPayPlugError(Request $request): Response
    {
        $reason = $this->extractReturnReason($request);
        $status = $this->isCanceledReturn($request) ? 'canceled' : 'refused';

        return $this->respondToPaymentReturn($status, $reason);
    }

    #[Route('/paiement/{id}/cawl', name: 'payment_cawl')]
    public function paymentCawl(
        Request $request,
        #[ValueResolver('haspayment_resolver')]
        HasPayment $payment,
    ): Response {
        $data = $this->cawlPayment->createData($payment);

        $view = 'cb_cawl.html.twig';
        $this->getSession()->set('payment', 'Cart');

        if ($payment instanceof Payment) {
            $view = 'payment.html.twig';
            $this->getSession()->set('payment', 'Payment');
        }

        return $this->render('payment/'.$view, [
            'classMenu' => 'black',
            'cart' => $payment,
            'data' => $data,
        ]);
    }

    #[Route('/paiement/cawl/return', name: 'payment_cawl_return')]
    public function paymentCawlReturn(Request $request): Response
    {
        $returnData = $request->query->all();

        /** @var GetHostedCheckoutResponse $status */
        $status = $this->cawlPayment->getStatus($returnData['hostedCheckoutId']);

        if ('PAYMENT_CREATED' !== $status->status && 'IN_PROGRESS' !== $status->status) {
            $reason = $this->extractReturnReason($request, ['status' => $status->status]);
            $returnStatus = $this->isCanceledReturn($request, ['status' => $status->status]) ? 'canceled' : 'refused';

            return $this->respondToPaymentReturn($returnStatus, $reason);
        }

        return $this->respondToPaymentReturn('success');
    }
}
