<?php

namespace App\Controller;

use App\Entity\Contrat;
use App\Entity\CustomerPaymentMethod;
use App\Service\DocuSealService;
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\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;

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

    #[Route('/process/contrat/{id}/verify', name: 'app_contrat_verify', requirements: ['id' => '\d+'])]
    public function verify(int $id, Request $request): Response
    {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat) {
            throw $this->createNotFoundException('Contrat introuvable.');
        }

        $session = $request->getSession();
        $error = null;

        if ($request->isMethod('POST')) {
            $code = $request->request->getString('code');
            $storedCode = $session->get('contrat_code_'.$id);
            $expires = $session->get('contrat_code_expires_'.$id, 0);

            if (time() > $expires) {
                $error = 'Code expire. Veuillez en demander un nouveau.';
            } elseif ($code !== $storedCode) {
                $error = 'Code incorrect.';
            } else {
                $session->set('contrat_verified_'.$id, true);

                return $this->redirectToRoute('app_contrat_process', ['id' => $id]);
            }
        }

        // Envoyer un code si pas encore fait
        if (null === $session->get('contrat_code_'.$id)) {
            $this->sendVerificationCode($contrat, $session, $id);
        }

        return $this->render('contrat/verify.html.twig', [
            'contrat' => $contrat,
            'error' => $error,
        ]);
    }

    #[Route('/process/contrat/{id}/verify/resend', name: 'app_contrat_resend_code', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function resendCode(int $id, Request $request): Response
    {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat) {
            throw $this->createNotFoundException('Contrat introuvable.');
        }

        $this->sendVerificationCode($contrat, $request->getSession(), $id);

        return $this->redirectToRoute('app_contrat_verify', ['id' => $id]);
    }

    #[Route('/process/contrat/{id}', name: 'app_contrat_process', requirements: ['id' => '\d+'])]
    public function process(int $id, Request $request): Response
    {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat) {
            throw $this->createNotFoundException('Contrat introuvable.');
        }

        $session = $request->getSession();
        if (!$session->get('contrat_verified_'.$contrat->getId(), false)) {
            return $this->redirectToRoute('app_contrat_verify', ['id' => $id]);
        }

        return $this->render('contrat/process.html.twig', [
            'contrat' => $contrat,
        ]);
    }

    #[Route('/process/contrat/{id}/sign', name: 'app_contrat_sign', requirements: ['id' => '\d+'])]
    public function sign(
        int $id,
        Request $request,
        DocuSealService $docuSeal,
        #[Autowire(env: 'DOCUSEAL_URL')] string $docuSealUrl = '',
    ): Response {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat || null === $contrat->getSubmissionId()) {
            throw $this->createNotFoundException('Contrat introuvable.');
        }

        $session = $request->getSession();
        if (!$session->get('contrat_verified_'.$contrat->getId(), false)) {
            return $this->redirectToRoute('app_contrat_verify', ['id' => $id]);
        }

        // @codeCoverageIgnoreStart
        $slug = $docuSeal->getSubmitterSlug((int) $contrat->getSubmissionId());
        if (null !== $slug) {
            return $this->redirect(rtrim($docuSealUrl, '/').'/s/'.$slug);
        }
        // @codeCoverageIgnoreEnd

        throw $this->createNotFoundException('Lien de signature introuvable.');
    }

    /**
     * Page de configuration du paiement (CB ou SEPA).
     */
    #[Route('/process/contrat/{id}/setup-payment', name: 'app_contrat_setup_payment', requirements: ['id' => '\d+'])]
    public function setupPayment(
        int $id,
        Request $request,
        #[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
        #[Autowire(env: 'STRIPE_PK')] string $stripePk = '',
    ): Response {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat || Contrat::STATE_SIGNED !== $contrat->getState()) {
            throw $this->createNotFoundException('Contrat introuvable.');
        }

        $session = $request->getSession();
        if (!$session->get('contrat_verified_'.$contrat->getId(), false)) {
            return $this->redirectToRoute('app_contrat_verify', ['id' => $id]);
        }

        $customer = $contrat->getCustomer();
        if (null === $customer) {
            throw $this->createNotFoundException('Client introuvable.');
        }

        // @codeCoverageIgnoreStart
        \Stripe\Stripe::setApiKey($stripeSk);

        $stripeCustomerId = $customer->getStripeCustomerId();
        if (null === $stripeCustomerId) {
            $stripeCustomer = \Stripe\Customer::create([
                'email' => $customer->getEmail() ?? $contrat->getEmail(),
                'name' => $contrat->getRaisonSociale(),
            ]);
            $stripeCustomerId = $stripeCustomer->id;
            $customer->setStripeCustomerId($stripeCustomerId);
            $this->em->flush();
        }

        // Creer un SetupIntent pour SEPA
        $setupIntent = \Stripe\SetupIntent::create([
            'customer' => $stripeCustomerId,
            'payment_method_types' => ['sepa_debit'],
            'metadata' => ['contrat_id' => (string) $contrat->getId()],
        ]);

        // Creer aussi un Checkout Session pour CB (premier paiement)
        $totalCents = (int) round($contrat->getTotalHt() * 100);

        $cbCheckoutUrl = null;
        if ($totalCents > 0) {
            $checkoutSession = \Stripe\Checkout\Session::create([
                'mode' => 'payment',
                'payment_method_types' => ['card'],
                'customer' => $stripeCustomerId,
                'line_items' => [[
                    'price_data' => [
                        'currency' => 'eur',
                        'unit_amount' => $totalCents,
                        'product_data' => [
                            'name' => 'Premier paiement - '.$contrat->getReference(),
                        ],
                    ],
                    'quantity' => 1,
                ]],
                'payment_intent_data' => [
                    'setup_future_usage' => 'off_session',
                    'metadata' => [
                        'contrat_id' => (string) $contrat->getId(),
                        'first_payment' => '1',
                    ],
                ],
                'success_url' => $this->generateUrl('app_contrat_payment_success', ['id' => $id], UrlGeneratorInterface::ABSOLUTE_URL),
                'cancel_url' => $this->generateUrl('app_contrat_setup_payment', ['id' => $id], UrlGeneratorInterface::ABSOLUTE_URL),
            ]);
            $cbCheckoutUrl = $checkoutSession->url;
        }
        // @codeCoverageIgnoreEnd

        return $this->render('contrat/setup_payment.html.twig', [
            'contrat' => $contrat,
            'customer' => $customer,
            'clientSecret' => $setupIntent->client_secret,
            'stripePk' => $stripePk,
            'cbCheckoutUrl' => $cbCheckoutUrl,
            'totalHt' => $contrat->getTotalHt(),
        ]);
    }

    /**
     * Confirmation SEPA pour contrat.
     */
    #[Route('/process/contrat/{id}/setup-payment/confirm', name: 'app_contrat_setup_payment_confirm', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function setupPaymentConfirm(
        int $id,
        Request $request,
        #[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
    ): Response {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat) {
            return new JsonResponse(['error' => 'Contrat introuvable'], Response::HTTP_NOT_FOUND);
        }

        $session = $request->getSession();
        if (!$session->get('contrat_verified_'.$contrat->getId(), false)) {
            return new JsonResponse(['error' => 'Non autorise'], Response::HTTP_FORBIDDEN);
        }

        $data = json_decode($request->getContent(), true);
        $paymentMethodId = $data['payment_method'] ?? null;

        if (null === $paymentMethodId) {
            return new JsonResponse(['error' => 'payment_method manquant'], Response::HTTP_BAD_REQUEST);
        }

        $customer = $contrat->getCustomer();
        if (null === $customer) {
            return new JsonResponse(['error' => 'Client introuvable'], Response::HTTP_NOT_FOUND);
        }

        // @codeCoverageIgnoreStart
        try {
            \Stripe\Stripe::setApiKey($stripeSk);

            $stripeCustomerId = $customer->getStripeCustomerId();
            $pm = \Stripe\PaymentMethod::retrieve($paymentMethodId);
            $pm->attach(['customer' => $stripeCustomerId]);
            \Stripe\Customer::update($stripeCustomerId, [
                'invoice_settings' => ['default_payment_method' => $paymentMethodId],
            ]);

            // Sauvegarder le moyen de paiement
            $sepa = $pm->sepa_debit ?? null;
            $cpm = new CustomerPaymentMethod($customer, $paymentMethodId, CustomerPaymentMethod::TYPE_SEPA);
            $cpm->setIsDefault(true);
            if (null !== $sepa) {
                $cpm->setLast4($sepa->last4 ?? null);
                $cpm->setBrand($sepa->bank_code ?? null);
                $cpm->setCountry($sepa->country ?? null);
            }

            // Retirer le default des autres
            $existingMethods = $this->em->getRepository(CustomerPaymentMethod::class)->findBy(['customer' => $customer]);
            foreach ($existingMethods as $m) {
                $m->setIsDefault(false);
            }

            $this->em->persist($cpm);
        } catch (\Throwable $e) {
            return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
        // @codeCoverageIgnoreEnd

        $this->em->flush();

        return new JsonResponse(['status' => 'ok']);
    }

    /**
     * Page succes apres premier paiement CB.
     */
    #[Route('/process/contrat/{id}/payment-success', name: 'app_contrat_payment_success', requirements: ['id' => '\d+'])]
    public function paymentSuccess(int $id): Response
    {
        $contrat = $this->em->getRepository(Contrat::class)->find($id);
        if (null === $contrat) {
            throw $this->createNotFoundException('Contrat introuvable.');
        }

        return $this->render('contrat/payment_success.html.twig', [
            'contrat' => $contrat,
        ]);
    }

    private function sendVerificationCode(Contrat $contrat, object $session, int $id): void
    {
        $code = str_pad((string) random_int(0, 999999), 6, '0', \STR_PAD_LEFT);
        $session->set('contrat_code_'.$id, $code);
        $session->set('contrat_code_expires_'.$id, time() + 900);

        $this->mailer->sendEmail(
            $contrat->getEmail(),
            'Code de verification - Contrat '.$contrat->getReference(),
            $this->twig->render('emails/contrat_verify_code.html.twig', [
                'contrat' => $contrat,
                'code' => $code,
            ]),
            null,
            null,
            false,
        );
    }
}
