<?php

namespace App\Controller;

use App\Entity\Advert;
use App\Entity\AdvertEvent;
use App\Entity\Revendeur;
use App\Service\MailerService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Twig\Environment;

class OrderPaymentController extends AbstractController
{
    private const NOTIFICATION_EMAIL = 'notification@e-cosplay.fr';
    private const TPL_CONFIRMED = 'order/payment_confirmed.html.twig';

    public function __construct(
        private EntityManagerInterface $em,
        private MailerService $mailer,
        private Environment $twig,
    ) {
    }

    #[Route('/order/{numOrder}', name: 'app_order_payment', requirements: ['numOrder' => '.+'])]
    public function index(
        string $numOrder,
        \Symfony\Component\HttpFoundation\Request $request,
        #[Autowire(env: 'STRIPE_PK')] string $stripePk = '',
    ): Response {
        $advert = $this->findAdvert($numOrder);
        $customer = $advert->getCustomer();
        $session = $request->getSession();
        $sessionKey = 'order_verified_'.$advert->getId();

        // Verifier si deja authentifie pour cet avis
        if (!$session->get($sessionKey, false)) {
            return $this->redirectToRoute('app_order_verify', ['numOrder' => $numOrder]);
        }

        // Tracker la visite
        $event = new AdvertEvent($advert, AdvertEvent::TYPE_VIEW, 'Page paiement consultee', $request->getClientIp());
        $this->em->persist($event);
        $this->em->flush();

        return $this->render('order/payment.html.twig', [
            'advert' => $advert,
            'customer' => $customer,
            'stripe_pk' => $stripePk,
        ]);
    }

    #[Route('/order-verify/{numOrder}', name: 'app_order_verify', requirements: ['numOrder' => '.+'])]
    public function verify(
        string $numOrder,
        \Symfony\Component\HttpFoundation\Request $request,
    ): Response {
        $advert = $this->findAdvert($numOrder);
        $customer = $advert->getCustomer();
        $session = $request->getSession();
        $sessionKey = 'order_verified_'.$advert->getId();

        // Deja verifie ou pas de client/email : acces direct
        if ($session->get($sessionKey, false) || null === $customer || null === $customer->getEmail()) {
            if (!$session->get($sessionKey, false)) {
                $session->set($sessionKey, true);
            }

            return $this->redirectToRoute('app_order_payment', ['numOrder' => $numOrder]);
        }

        $error = ('POST' === $request->getMethod())
            ? $this->handleVerifyPost($request, $advert, $session, $sessionKey)
            : null;

        // Redirect si POST a valide le code
        if (true === $error) {
            return $this->redirectToRoute('app_order_payment', ['numOrder' => $numOrder]);
        }

        $this->sendVerifyCodeIfNeeded($session, $advert, $customer);

        return $this->render('order/verify.html.twig', [
            'advert' => $advert,
            'customer' => $customer,
            'error' => \is_string($error) ? $error : null,
            'numOrder' => $numOrder,
        ]);
    }

    /**
     * @return string|true string=error message, true=code valid (redirect)
     */
    private function handleVerifyPost(
        \Symfony\Component\HttpFoundation\Request $request,
        Advert $advert,
        \Symfony\Component\HttpFoundation\Session\SessionInterface $session,
        string $sessionKey,
    ): string|true {
        $codeKey = 'order_code_'.$advert->getId();
        $codeExpiresKey = 'order_code_expires_'.$advert->getId();
        $code = trim($request->request->getString('code'));
        $storedCode = $session->get($codeKey);
        $expiresAt = $session->get($codeExpiresKey, 0);

        if (time() > $expiresAt) {
            $session->remove($codeKey);
            $session->remove($codeExpiresKey);

            return 'Code expire. Cliquez sur "Renvoyer le code" pour en recevoir un nouveau.';
        }

        if ($code === $storedCode) {
            $session->set($sessionKey, true);
            $session->remove($codeKey);
            $session->remove($codeExpiresKey);

            return true;
        }

        return 'Code incorrect. Veuillez reessayer.';
    }

    private function sendVerifyCodeIfNeeded(
        \Symfony\Component\HttpFoundation\Session\SessionInterface $session,
        Advert $advert,
        \App\Entity\Customer $customer,
    ): void {
        $codeKey = 'order_code_'.$advert->getId();
        $codeExpiresKey = 'order_code_expires_'.$advert->getId();

        if (null !== $session->get($codeKey) && time() <= $session->get($codeExpiresKey, 0)) {
            return;
        }

        $code = str_pad((string) random_int(0, 999999), 6, '0', \STR_PAD_LEFT);
        $session->set($codeKey, $code);
        $session->set($codeExpiresKey, time() + 900);

        $this->mailer->sendEmail(
            $customer->getEmail(),
            'Code de verification - Avis '.$advert->getOrderNumber()->getNumOrder(),
            $this->twig->render('emails/order_verify_code.html.twig', [
                'customer' => $customer,
                'advert' => $advert,
                'code' => $code,
            ]),
            null,
            null,
            false,
        );
    }

    #[Route('/order-verify/{numOrder}/resend', name: 'app_order_verify_resend', requirements: ['numOrder' => '.+'], methods: ['POST'])]
    public function verifyResend(
        string $numOrder,
        \Symfony\Component\HttpFoundation\Request $request,
    ): Response {
        $advert = $this->findAdvert($numOrder);
        $customer = $advert->getCustomer();
        $session = $request->getSession();

        if (null !== $customer && null !== $customer->getEmail()) {
            $code = str_pad((string) random_int(0, 999999), 6, '0', \STR_PAD_LEFT);
            $session->set('order_code_'.$advert->getId(), $code);
            $session->set('order_code_expires_'.$advert->getId(), time() + 900);

            $this->mailer->sendEmail(
                $customer->getEmail(),
                'Nouveau code de verification - Avis '.$advert->getOrderNumber()->getNumOrder(),
                $this->twig->render('emails/order_verify_code.html.twig', [
                    'customer' => $customer,
                    'advert' => $advert,
                    'code' => $code,
                ]),
                null,
                null,
                false,
            );
        }

        $this->addFlash('success', 'Un nouveau code a ete envoye.');

        return $this->redirectToRoute('app_order_verify', ['numOrder' => $numOrder]);
    }

    #[Route('/order-choose/virement/{numOrder}', name: 'app_order_payment_virement', requirements: ['numOrder' => '.+'], methods: ['POST'])]
    public function chooseVirement(string $numOrder): Response
    {
        return $this->handleOfflinePayment($numOrder, 'virement', 'Virement bancaire', 'emails/payment_virement_client.html.twig');
    }

    #[Route('/order-choose/cheque/{numOrder}', name: 'app_order_payment_cheque', requirements: ['numOrder' => '.+'], methods: ['POST'])]
    public function chooseCheque(string $numOrder): Response
    {
        return $this->handleOfflinePayment($numOrder, 'cheque', 'Cheque', 'emails/payment_cheque_client.html.twig');
    }

    private function handleOfflinePayment(string $numOrder, string $method, string $methodLabel, string $clientTemplate): Response
    {
        $advert = $this->findAdvert($numOrder);
        $customer = $advert->getCustomer();
        $num = $advert->getOrderNumber()->getNumOrder();

        if (null !== $customer && null !== $customer->getEmail()) {
            $this->mailer->sendEmail(
                $customer->getEmail(),
                'Paiement par '.$method.' - Avis de paiement '.$num,
                $this->twig->render($clientTemplate, [
                    'customer' => $customer,
                    'advert' => $advert,
                ]),
                null,
                null,
                false,
            );
        }

        $this->mailer->sendEmail(
            self::NOTIFICATION_EMAIL,
            'Paiement par '.$method.' - '.$num.' - '.($customer?->getFullName() ?? 'Inconnu'),
            $this->twig->render('emails/payment_notification_admin.html.twig', [
                'customer' => $customer,
                'advert' => $advert,
                'method' => $methodLabel,
            ]),
            null,
            null,
            false,
        );

        return $this->render(self::TPL_CONFIRMED, [
            'advert' => $advert,
            'customer' => $customer,
            'method' => $method,
        ]);
    }

    #[Route('/order-choose/stripe/{numOrder}', name: 'app_order_payment_stripe_intent', requirements: ['numOrder' => '.+'], methods: ['POST'])]
    public function createStripeIntent(
        string $numOrder,
        \Symfony\Component\HttpFoundation\Request $request,
        #[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
    ): JsonResponse {
        $advert = $this->findAdvert($numOrder);
        $customer = $advert->getCustomer();
        $amount = (int) round(((float) $advert->getTotalTtc()) * 100);

        if ('' === $stripeSk || $amount <= 0) {
            return $this->json(['error' => '' === $stripeSk ? 'Stripe non configure' : 'Montant invalide'], '' === $stripeSk ? 500 : 400);
        }

        // @codeCoverageIgnoreStart
        $body = json_decode($request->getContent(), true) ?? [];
        $paymentMethod = $body['method'] ?? 'card';
        try {
            \Stripe\Stripe::setApiKey($stripeSk);

            $params = [
                'amount' => $amount,
                'currency' => 'eur',
                'metadata' => [
                    'advert_id' => $advert->getId(),
                    'num_order' => $advert->getOrderNumber()->getNumOrder(),
                    'customer_name' => $customer?->getFullName() ?? 'Inconnu',
                    'customer_email' => $customer?->getEmail() ?? '',
                    'payment_method' => $paymentMethod,
                ],
                'description' => 'Avis de paiement '.$advert->getOrderNumber()->getNumOrder(),
            ];

            if ('sepa' === $paymentMethod) {
                // Prelevement bancaire : SEPA + virement bancaire Stripe
                $params['payment_method_types'] = ['sepa_debit', 'customer_balance'];
            } else {
                // Paiement en ligne : toutes les methodes sauf SEPA/virement (bouton dedie)
                $params['automatic_payment_methods'] = ['enabled' => true];
                $params['excluded_payment_method_types'] = ['sepa_debit', 'customer_balance', 'bancontact', 'eps'];
            }

            if (null !== $customer?->getStripeCustomerId()) {
                $params['customer'] = $customer->getStripeCustomerId();
            }

            if (null !== $customer?->getEmail()) {
                $params['receipt_email'] = $customer->getEmail();
            }

            // Stripe Connect : si le client a un revendeur avec Stripe Connect actif,
            // le paiement est lie au compte connecte (transfert automatique)
            $revendeur = $this->findRevendeur($customer);
            if (null !== $revendeur) {
                // > 1000€ : 75% E-Cosplay / 25% revendeur, sinon 50/50
                $feeRate = $amount > 100000 ? 0.75 : 0.50;
                $applicationFee = (int) round($amount * $feeRate);
                $params['application_fee_amount'] = $applicationFee;
                $params['transfer_data'] = [
                    'destination' => $revendeur->getStripeConnectId(),
                ];
                $params['metadata']['revendeur_code'] = $revendeur->getCodeRevendeur();
                $params['metadata']['revendeur_connect_id'] = $revendeur->getStripeConnectId();
                $params['metadata']['application_fee'] = (string) $applicationFee;
            }

            /** @phpstan-ignore-next-line */
            $intent = \Stripe\PaymentIntent::create($params);

            $advert->setStripePaymentId($intent->id);
            $this->em->flush();

            return $this->json([
                'clientSecret' => $intent->client_secret,
                'method' => $paymentMethod,
            ]);
        } catch (\Throwable $e) {
            return $this->json(['error' => $e->getMessage()], 500);
        }
        // @codeCoverageIgnoreEnd
    }

    #[Route('/order-choose/stripe/{numOrder}/success', name: 'app_order_payment_stripe_success', requirements: ['numOrder' => '.+'], methods: ['GET'])]
    public function stripeSuccess(
        string $numOrder,
        \Symfony\Component\HttpFoundation\Request $request,
        #[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
    ): Response {
        $advert = $this->findAdvert($numOrder);

        // Retirer la session de verification apres paiement
        $request->getSession()->remove('order_verified_'.$advert->getId());

        // Si le webhook a deja traite le paiement, afficher la confirmation directe
        if (Advert::STATE_ACCEPTED === $advert->getState()) {
            return $this->render(self::TPL_CONFIRMED, [
                'advert' => $advert,
                'customer' => $advert->getCustomer(),
                'method' => 'stripe',
            ]);
        }

        // Verifier le statut du PaymentIntent directement aupres de Stripe
        $piId = $advert->getStripePaymentId();
        // @codeCoverageIgnoreStart
        if (null !== $piId && '' !== $stripeSk) {
            try {
                \Stripe\Stripe::setApiKey($stripeSk);
                $pi = \Stripe\PaymentIntent::retrieve($piId);

                if ('succeeded' === $pi->status) {
                    return $this->render(self::TPL_CONFIRMED, [
                        'advert' => $advert,
                        'customer' => $advert->getCustomer(),
                        'method' => 'stripe',
                    ]);
                }
            } catch (\Throwable) {
                // Fallback sur le loader
            }
        }
        // @codeCoverageIgnoreEnd

        // Sinon afficher le loader de verification (le webhook va traiter en arriere-plan)
        $method = $request->query->getString('method', 'card');

        return $this->render('order/payment_processing.html.twig', [
            'advert' => $advert,
            'customer' => $advert->getCustomer(),
            'checkUrl' => $this->generateUrl('app_order_payment_stripe_check', ['numOrder' => $numOrder]),
            'method' => $method,
        ]);
    }

    #[Route('/order-choose/stripe/{numOrder}/check', name: 'app_order_payment_stripe_check', requirements: ['numOrder' => '.+'], methods: ['GET'])]
    public function stripeCheck(string $numOrder): JsonResponse
    {
        $advert = $this->findAdvert($numOrder);

        return $this->json([
            'status' => $advert->getState(),
            'accepted' => Advert::STATE_ACCEPTED === $advert->getState(),
        ]);
    }

    /**
     * Trouve le revendeur Stripe Connect associe au client (si eligible).
     *
     * @codeCoverageIgnore
     */
    private function findRevendeur(?\App\Entity\Customer $customer): ?Revendeur
    {
        $code = $customer?->getRevendeurCode();
        if (null === $code || '' === $code) {
            return null;
        }

        $revendeur = $this->em->getRepository(Revendeur::class)->findOneBy(['codeRevendeur' => $code]);

        if (null === $revendeur || !$revendeur->isUseStripe() || !$revendeur->getStripeConnectId()) {
            return null;
        }

        return $revendeur;
    }

    private function findAdvert(string $numOrder): Advert
    {
        $advert = $this->em->createQueryBuilder()
            ->select('a')
            ->from(Advert::class, 'a')
            ->join('a.orderNumber', 'o')
            ->where('o.numOrder = :num')
            ->andWhere('a.state != :cancel')
            ->setParameter('num', $numOrder)
            ->setParameter('cancel', Advert::STATE_CANCEL)
            ->orderBy('a.createdAt', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();

        if (null === $advert) {
            throw $this->createNotFoundException('Avis de paiement introuvable.');
        }

        return $advert;
    }
}
