<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (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\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\PayPlugPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\Sherlock2Payment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\SogeCommercePayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\StripePayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Payment\SystemPayPayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils\Mailer;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\Payment\Model\Payment;
use CpCreation\VitiCore\Payment\Repository\PaymentRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Attribute\Route;

final class CartController extends AbstractController
{
    public function __construct(
        private CartRepository $cartRepository,
        private PaymentRepository $paymentRepository,
        private Mailer $mailer,
        private EtransactionsPayment $etransactionsPayment,
        private SystemPayPayment $systempayPayment,
        private SogeCommercePayment $sogeCommercePayment,
        private AxeptaPayment $axeptaPayment,
        private Sherlock2Payment $sherlock2Payment,
        private PayPlugPayment $payplugPayment,
        private StripePayment $stripePayment,
        private EntityManagerInterface $em,
        private RequestStack $requestStack,
        private CawlPayment $cawlPayment,
    ) {
    }

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

    #[Route('/request/etransactions/responseAt', name: 'etransactions_responseAt', methods: ['GET', 'POST'])]
    public function etransactionsResponseAt(Request $request): Response
    {
        $code = $request->request->get('Erreur') ?? $request->query->get('Erreur');
        $ref = $request->request->get('Ref') ?? $request->query->get('Ref');

        $response = $request->request->all() ?: $request->query->all();

        if (!$ref || !$code) {
            throw new BadRequestHttpException();
        }

        if ($this->etransactionsPayment->canVerifyReturnSignature() && !$this->etransactionsPayment->validReturn($request)) {
            return new Response('Invalid signature', Response::HTTP_BAD_REQUEST);
        }

        $this->em->getFilters()->disable('soft_deletable');

        /** @var Cart|null $cart */
        $cart = $this->cartRepository->findOneBy(['token' => $ref]);

        if ($cart) {
            $checkout = $cart->getCartCheckout();
            $checkout->setResponse($response);

            if ('00000' === $code) {
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);
            } else {
                $this->em->persist($checkout);
                $this->em->flush();
            }

            return new Response('', 200);
        }

        /** @var Payment|null $payment */
        $payment = $this->paymentRepository->findOneBy(['slug' => $ref]);

        if ($payment) {
            $payment->setResponse($response);

            if ('00000' === $code) {
                $payment->setPayedAt(new \DateTime('now'));
                $this->mailer->sendPayment($payment);
            }

            $this->em->persist($payment);
            $this->em->flush();
        }

        return new Response('', 200);
    }

    #[Route('/request/monetico/responseAt', name: 'monetico_responseAt', methods: ['GET', 'POST'])]
    public function moneticoResponseAt(Request $request): Response
    {
        $code = $request->request->get('code-retour') ?? $request->query->get('code-retour');
        $ref = $request->request->get('reference') ?? $request->query->get('reference');

        $response = $request->request->all() ?: $request->query->all();

        if (!$ref || !$code) {
            throw new BadRequestHttpException();
        }

        $this->em->getFilters()->disable('soft_deletable');

        /** @var Cart|null $cart */
        $cart = $this->cartRepository->findOneBy(['token' => $ref]);

        if ($cart) {
            $checkout = $cart->getCartCheckout();
            $checkout->setResponse($response);

            if ('payetest' === $code || 'paiement' === $code) {
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);
            } else {
                $this->em->persist($checkout);
                $this->em->flush();
            }

            return new Response("version=2\ncdr=0", 200);
        }

        /** @var Payment|null $payment */
        $payment = $this->paymentRepository->findOneBy(['slug' => $ref]);
        if ($payment) {
            if ('payetest' === $code || 'paiement' === $code) {
                $payment->setPayedAt(new \DateTime('now'));
                $payment->setResponse($response);
                $this->mailer->sendPayment($payment);
            } else {
                $payment->setResponse($response);
            }

            $this->em->persist($payment);
            $this->em->flush();

            return new Response("version=2\ncdr=0", 200);
        }

        return new Response("version=2\ncdr=1\n", 200);
    }

    #[Route('/request/systempay/responseAt', name: 'systempay_responseAt', methods: ['POST'])]
    public function systemPayResponseAt(Request $request): Response
    {
        $response = $request->request->all();
        if (!$response) {
            return new Response('POST is empty');
        }

        $token = $response['vads_trans_id'] ?? null;
        if (!$token) {
            return new Response('Data received but no cart associated');
        }

        $cart = $this->cartRepository->findOneBy(['token' => $token]);

        if ($this->systempayPayment->valid($response)) {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($response);
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);

                return new Response('Order successfully updated');
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse($response);
                $payment->setPayedAt(new \DateTime('now'));
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Order successfully updated');
            }

            return new Response('Data received but no cart associated');
        }

        if ($cart) {
            $checkout = $cart->getCartCheckout();
            $checkout->setResponse($response);
            $this->em->persist($checkout);
            $this->em->flush();
        } else {
            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse($response);
                $this->em->persist($payment);
                $this->em->flush();
            }
        }

        return new Response('Payment refused');
    }

    #[Route('/request/sogecommerce/responseAt', name: 'sogecommerce_responseAt', methods: ['POST'])]
    public function sogeCommerceResponseAt(Request $request): Response
    {
        $response = $request->request->all();
        if (!$response) {
            return new Response('POST is empty');
        }

        $token = $response['vads_trans_id'] ?? null;
        if (!$token) {
            return new Response('Data received but no cart associated');
        }

        $cart = $this->cartRepository->findOneBy(['token' => $token]);

        if ($this->sogeCommercePayment->valid($response)) {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($response);
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);

                return new Response('Order successfully updated');
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse($response);
                $payment->setPayedAt(new \DateTime('now'));
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Order successfully updated');
            }

            return new Response('Data received but no cart associated');
        }

        if ($cart) {
            $checkout = $cart->getCartCheckout();
            $checkout->setResponse($response);
            $this->em->persist($checkout);
            $this->em->flush();
        } else {
            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse($response);
                $this->em->persist($payment);
                $this->em->flush();
            }
        }

        return new Response('Payment refused');
    }

    #[Route('/request/axepta/responseAt', name: 'axepta_responseAt', methods: ['POST'])]
    public function axeptaResponseAt(Request $request): Response
    {
        $response = $request->request->all();
        if (!$response) {
            return new Response('No response');
        }

        $data = $this->axeptaPayment->valid($response);
        if (empty($data)) {
            return new Response('No response');
        }

        $token = $data['TransID'] ?? null;
        if (!$token) {
            return new Response('No response');
        }

        $cart = $this->cartRepository->findOneBy(['token' => $token]);

        if (($data['Valid'] ?? false) === true) {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse(array_merge($data, $response));
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);

                return new Response('Order successfully updated');
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse(array_merge($data, $response));
                $payment->setPayedAt(new \DateTime('now'));
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Order successfully updated');
            }

            return new Response('Data received but no cart associated');
        }

        // invalid
        if ($cart) {
            $checkout = $cart->getCartCheckout();
            $checkout->setResponse(array_merge($data, $response));
            $this->em->persist($checkout);
            $this->em->flush();
        } else {
            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse(array_merge($data, $response));
                $this->em->persist($payment);
                $this->em->flush();
            }
        }

        return new Response('Payment refused');
    }

    #[Route('/request/sherlock2/responseAt', name: 'sherlock2_responseAt', methods: ['POST'])]
    public function sherlock2ResponseAt(Request $request): Response
    {
        $response = $request->request->all();
        if (!$response) {
            return new Response('No response');
        }

        $data = $this->sherlock2Payment->valid($response);
        if (empty($data)) {
            return new Response('No response');
        }

        $token = $data['orderId'] ?? null;
        if (!$token) {
            return new Response('No response');
        }

        $cart = $this->cartRepository->findOneBy(['token' => $token]);

        if (($data['Valid'] ?? false) === true) {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($response);
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);

                return new Response('Order successfully updated');
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
            if ($payment) {
                $payment->setResponse($response);
                $payment->setPayedAt(new \DateTime('now'));
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Order successfully updated');
            }

            return new Response('Order successfully updated');
        }

        // invalid
        if ($cart) {
            $checkout = $cart->getCartCheckout();
            $checkout->setResponse($response);
            $this->em->persist($checkout);
            $this->em->flush();

            return new Response('Data invalid');
        }

        $payment = $this->paymentRepository->findOneBy(['slug' => $token]);
        if ($payment) {
            $payment->setResponse($response);
            $this->em->persist($payment);
            $this->em->flush();

            return new Response('Data invalid');
        }

        return new Response('No response');
    }

    #[Route('/request/payplug/responseAt', name: 'payplug_responseAt', methods: ['POST'])]
    public function payPlugResponseAt(Request $request): Response
    {
        $data = $request->toArray();

        if (!isset($data['id'])) {
            return new Response('No response');
        }

        $paymentArray = $this->payplugPayment->valid($data);
        $reference = $paymentArray['metadata']['transaction_id'] ?? null;

        if (!$reference) {
            return new Response('No response');
        }

        $cart = $this->cartRepository->findOneBy(['token' => $reference]);

        if (!empty($paymentArray['is_paid'])) {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($paymentArray);
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);

                return new Response('Order successfully updated');
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $reference]);
            if ($payment) {
                $payment->setResponse($paymentArray);
                $payment->setPayedAt(new \DateTime('now'));
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Order successfully updated');
            }
        } else {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($paymentArray);
                $this->em->persist($checkout);
                $this->em->flush();

                return new Response('Data invalid');
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $reference]);
            if ($payment) {
                $payment->setResponse($paymentArray);
                $this->em->persist($payment);
                $this->em->flush();

                return new Response('Data invalid');
            }
        }

        return new Response('No response');
    }

    #[Route('/request/stripe/responseAt', name: 'stripe_responseAt', methods: ['POST'])]
    public function stripeResponseAt(Request $request): Response
    {
        $payload = $request->getContent();
        $signature = $request->headers->get('Stripe-Signature');

        try {
            $event = $this->stripePayment->constructWebhookEvent($payload, $signature);
        } catch (\Throwable $exception) {
            return new Response($exception->getMessage(), 400);
        }

        $normalizedResponse = $this->stripePayment->normalizeWebhookEvent($event);
        $eventType = $normalizedResponse['event_type'] ?? null;
        $isPaidEvent = \in_array($eventType, [
            'checkout.session.completed',
            'checkout.session.async_payment_succeeded',
        ], true);
        $isRefundEvent = \in_array($eventType, [
            'charge.refunded',
            'refund.updated',
            'refund.failed',
        ], true);

        $reference = $normalizedResponse['transaction_reference'] ?? null;

        if ($isRefundEvent) {
            $reference = $normalizedResponse['order_reference'] ?? $reference;
        }

        if (!\is_string($reference) || '' === $reference) {
            return new Response('No transaction reference', 200);
        }

        $cart = $this->cartRepository->findOneBy(['token' => $reference]);
        if ($cart) {
            $checkout = $cart->getCartCheckout();
            if (null !== $checkout) {
                if ($isPaidEvent) {
                    $normalizedResponse = $this->enrichStripePaidWebhookResponse(
                        $normalizedResponse,
                        $cart->getPaymentMethod()
                    );
                }

                $checkout->setResponse($normalizedResponse);

                if ($isPaidEvent && null === $checkout->getPayedAt()) {
                    $checkout->setPayedAt(new \DateTime('now'));
                    $this->mailer->dispatchPaidOrder($cart);
                }

                $this->em->persist($checkout);
            }

            if ($isRefundEvent) {
                $this->synchronizeStripeRefundOnInvoice($cart, $normalizedResponse);
            }

            $this->em->flush();

            return new Response('Received', 200);
        }

        $payment = $this->paymentRepository->findOneBy(['slug' => $reference]);
        if ($payment) {
            if ($isPaidEvent) {
                $normalizedResponse = $this->enrichStripePaidWebhookResponse(
                    $normalizedResponse,
                    $payment->getPaymentMethod()
                );
            }

            $payment->setResponse($normalizedResponse);

            if ($isPaidEvent && null === $payment->getPayedAt()) {
                $payment->setPayedAt(new \DateTime('now'));
                $this->mailer->sendPayment($payment);
            }

            $this->em->persist($payment);
            $this->em->flush();
        }

        return new Response('Received', 200);
    }

    /**
        * @param array<string, mixed> $normalizedResponse
        *
        * @return array<string, mixed>
        */
    private function enrichStripePaidWebhookResponse(array $normalizedResponse, ?\CpCreation\VitiCore\Cart\Model\PaymentMethod $paymentMethod): array
    {
        $sessionId = $normalizedResponse['checkout_session_id'] ?? null;

        if (!\is_string($sessionId) || '' === $sessionId) {
            return $normalizedResponse;
        }

        try {
            $session = $this->stripePayment->retrieveCheckoutSession($sessionId, $paymentMethod);
            $sessionResponse = $this->stripePayment->normalizeCheckoutSession($session);

            return array_replace_recursive($sessionResponse, [
                'event_id' => $normalizedResponse['event_id'] ?? null,
                'event_type' => $normalizedResponse['event_type'] ?? ($sessionResponse['event_type'] ?? null),
                'raw' => $normalizedResponse['raw'] ?? ($sessionResponse['raw'] ?? []),
            ]);
        } catch (\Throwable) {
            return $normalizedResponse;
        }
    }

    /**
     * @param array<string, mixed> $normalizedResponse
     */
    private function synchronizeStripeRefundOnInvoice(Cart $cart, array $normalizedResponse): void
    {
        $invoice = $cart->getInvoice();
        if (null === $invoice) {
            return;
        }

        $creditDetails = $invoice->getCreditDetails();
        if (!\is_array($creditDetails)) {
            $creditDetails = [];
        }

        $existingStripeRefund = $creditDetails['stripe_refund'] ?? [];
        if (!\is_array($existingStripeRefund)) {
            $existingStripeRefund = [];
        }

        $message = $normalizedResponse['refund_failure_reason'] ?? ($existingStripeRefund['message'] ?? null);
        $refundStatus = $normalizedResponse['refund_status'] ?? null;
        if ('charge.refunded' === ($normalizedResponse['event_type'] ?? null) && null === $refundStatus) {
            $refundStatus = 'succeeded';
        }

        $creditDetails['stripe_refund'] = array_filter([
            ...$existingStripeRefund,
            'attempted' => true,
            'status' => $refundStatus ?? ($existingStripeRefund['status'] ?? 'pending'),
            'refund_id' => $normalizedResponse['refund_id'] ?? ($existingStripeRefund['refund_id'] ?? null),
            'payment_intent' => $normalizedResponse['payment_intent'] ?? ($existingStripeRefund['payment_intent'] ?? null),
            'charge_id' => $normalizedResponse['charge_id'] ?? ($existingStripeRefund['charge_id'] ?? null),
            'amount_ttc' => (int) ($normalizedResponse['refund_amount'] ?? ($existingStripeRefund['amount_ttc'] ?? 0)),
            'message' => \is_string($message) && '' !== $message ? $message : null,
            'updated_at' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
            'webhook_event_id' => $normalizedResponse['event_id'] ?? null,
            'webhook_event_type' => $normalizedResponse['event_type'] ?? null,
        ], static fn (mixed $value): bool => null !== $value);

        $invoice->setCreditDetails($creditDetails);
        $this->em->persist($invoice);
    }

    #[Route('/request/cawl/responseAt', name: 'cawl_responseAt', methods: ['POST'])]
    public function cawlResponseAt(Request $request): Response
    {
        $signature = $request->headers->get('X-GCS-Signature');
        $data = json_decode($request->getContent(), true);

        $jsonMinifie = json_encode($data, \JSON_UNESCAPED_SLASHES);
        $result = json_decode((string) $jsonMinifie, true);

        $valid = $this->cawlPayment->valid((string) $jsonMinifie, (string) $signature);
        $reference = $result['payment']['paymentOutput']['references']['merchantReference'] ?? null;

        if (!$reference) {
            return new Response('No response', 200);
        }

        $cart = $this->cartRepository->findOneBy(['token' => $reference]);

        if (true === $valid) {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($result);
                $checkout->setPayedAt(new \DateTime('now'));
                $this->em->persist($checkout);
                $this->em->flush();

                $this->mailer->dispatchPaidOrder($cart);

                return new Response('Order successfully updated', 200);
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $reference]);
            if ($payment) {
                $payment->setResponse($result);
                $payment->setPayedAt(new \DateTime('now'));
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Order successfully updated', 200);
            }
        } else {
            if ($cart) {
                $checkout = $cart->getCartCheckout();
                $checkout->setResponse($result);
                $this->em->persist($checkout);
                $this->em->flush();

                return new Response('Received', 200);
            }

            $payment = $this->paymentRepository->findOneBy(['slug' => $reference]);
            if ($payment) {
                $payment->setResponse($result);
                $this->em->persist($payment);
                $this->em->flush();

                $this->mailer->sendPayment($payment);

                return new Response('Received', 200);
            }
        }

        return new Response('No response', 200);
    }
}
