<?php

namespace App\Controller;

use App\Entity\Billet;
use App\Entity\BilletBuyer;
use App\Entity\BilletBuyerItem;
use App\Entity\BilletOrder;
use App\Entity\Event;
use App\Entity\User;
use App\Service\AuditService;
use App\Service\BilletOrderService;
use App\Service\InvoiceService;
use App\Service\StripeService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class OrderController extends AbstractController
{
    private const REF_PATTERN = 'ETICKET-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}';

    #[Route('/evenement/{id}/commander', name: 'app_order_create', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function create(int $id, Request $request, EntityManagerInterface $em, AuditService $audit): Response
    {
        $event = $em->getRepository(Event::class)->find($id);
        if (!$event || !$event->isOnline()) {
            throw $this->createNotFoundException();
        }

        $cart = json_decode($request->getContent(), true);
        $eventUrl = $this->generateUrl('app_event_detail', [
            'orgaSlug' => $event->getAccount()->getSlug(),
            'id' => $event->getId(),
            'eventSlug' => $event->getSlug(),
        ]);

        $isExpired = $event->getEndAt() && $event->getEndAt() < new \DateTimeImmutable();
        if ($isExpired || !$this->isValidCart($cart)) {
            return $this->json($isExpired ? ['error' => 'Cet evenement est termine.'] : ['redirect' => $eventUrl], 400);
        }

        /** @var User|null $user */
        $user = $this->getUser();

        $order = new BilletBuyer();
        $order->setEvent($event);

        if ($user) {
            $order->setUser($user);
            $order->setFirstName($user->getFirstName());
            $order->setLastName($user->getLastName());
            $order->setEmail($user->getEmail());
        }

        $order->setTotalHT($this->buildOrderItems($order, $cart, $event, $em));

        if ($order->getItems()->isEmpty()) {
            return $this->json(['redirect' => $eventUrl]);
        }

        $count = $em->getRepository(BilletBuyer::class)->count([]) + 1;
        $order->setOrderNumber(date('Y-m-d').'-'.$count);

        $em->persist($order);
        $em->flush();

        $audit->log('order_created', 'BilletBuyer', $order->getId(), [
            'orderNumber' => $order->getOrderNumber(),
            'event' => $event->getTitle(),
            'totalHT' => $order->getTotalHTDecimal(),
        ]);

        $redirect = $user
            ? $this->generateUrl('app_order_payment', ['id' => $order->getId()])
            : $this->generateUrl('app_order_guest', ['id' => $order->getId()]);

        return $this->json(['redirect' => $redirect]);
    }

    #[Route('/commande/{id}/informations', name: 'app_order_guest', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
    public function guest(int $id, Request $request, EntityManagerInterface $em): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->find($id);
        if (!$order || BilletBuyer::STATUS_PENDING !== $order->getStatus()) {
            throw $this->createNotFoundException();
        }

        $canSkip = $order->getUser();

        if ($request->isMethod('POST') && !$canSkip) {
            $firstName = trim($request->request->getString('first_name'));
            $lastName = trim($request->request->getString('last_name'));
            $email = trim($request->request->getString('email'));

            if ('' === $firstName || '' === $lastName || '' === $email || !filter_var($email, \FILTER_VALIDATE_EMAIL)) {
                $this->addFlash('error', '' === $firstName || '' === $lastName || '' === $email ? 'Tous les champs sont requis.' : 'L\'adresse email n\'est pas valide.');

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

            $order->setFirstName($firstName);
            $order->setLastName($lastName);
            $order->setEmail($email);
            $em->flush();
            $canSkip = true;
        }

        if ($canSkip) {
            return $this->redirectToRoute('app_order_payment', ['id' => $order->getId()]);
        }

        return $this->render('order/guest.html.twig', [
            'order' => $order,
            'breadcrumbs' => [
                ['name' => 'Accueil', 'url' => '/'],
                ['name' => $order->getEvent()->getTitle()],
                ['name' => 'Informations'],
            ],
        ]);
    }

    #[Route('/commande/{id}/paiement', name: 'app_order_payment', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function payment(int $id, EntityManagerInterface $em): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->find($id);
        if (!$order || BilletBuyer::STATUS_PENDING !== $order->getStatus()) {
            throw $this->createNotFoundException();
        }

        if (!$order->getFirstName() || !$order->getEmail()) {
            return $this->redirectToRoute('app_order_guest', ['id' => $order->getId()]);
        }

        $organizer = $order->getEvent()->getAccount();
        if (!$organizer->getStripeAccountId()) {
            throw $this->createNotFoundException();
        }

        $stripePublicKey = $this->getParameter('stripe_pk');

        return $this->render('order/payment.html.twig', [
            'order' => $order,
            'stripe_public_key' => $stripePublicKey,
            'stripe_account' => $organizer->getStripeAccountId(),
            'breadcrumbs' => [
                ['name' => 'Accueil', 'url' => '/'],
                ['name' => $order->getEvent()->getTitle()],
                ['name' => 'Paiement'],
            ],
        ]);
    }

    /**
     * @codeCoverageIgnore Requires live Stripe API
     */
    #[Route('/commande/{id}/create-payment-intent', name: 'app_order_create_intent', requirements: ['id' => '\d+'], methods: ['POST'])]
    public function createPaymentIntent(int $id, EntityManagerInterface $em, StripeService $stripeService): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->find($id);
        if (!$order || BilletBuyer::STATUS_PENDING !== $order->getStatus() || !$order->getEmail()) {
            throw $this->createNotFoundException();
        }

        $organizer = $order->getEvent()->getAccount();
        if (!$organizer->getStripeAccountId()) {
            throw $this->createNotFoundException();
        }

        if ($order->getStripeSessionId()) {
            $paymentIntent = $stripeService->getClient()->paymentIntents->retrieve(
                $order->getStripeSessionId(),
                [],
                ['stripe_account' => $organizer->getStripeAccountId()],
            );

            return $this->json(['clientSecret' => $paymentIntent->client_secret]);
        }

        $hasDebt = ($organizer->getDebt() ?? 0) > 0;

        if ($hasDebt) {
            $paymentIntent = $stripeService->getClient()->paymentIntents->create([
                'amount' => $order->getTotalHT(),
                'currency' => 'eur',
                'automatic_payment_methods' => ['enabled' => true],
                'metadata' => [
                    'order_id' => $order->getId(),
                    'reference' => $order->getReference(),
                    'debt_organizer_id' => $organizer->getId(),
                ],
                'receipt_email' => $order->getEmail(),
            ], [
                'idempotency_key' => 'pi_'.$order->getReference(),
            ]);
        } else {
            $commissionRate = $organizer->getCommissionRate() ?? 0;
            $applicationFee = (int) round($order->getTotalHT() * ($commissionRate / 100));

            $paymentIntent = $stripeService->getClient()->paymentIntents->create([
                'amount' => $order->getTotalHT(),
                'currency' => 'eur',
                'automatic_payment_methods' => ['enabled' => true],
                'application_fee_amount' => $applicationFee,
                'metadata' => [
                    'order_id' => $order->getId(),
                    'reference' => $order->getReference(),
                ],
                'receipt_email' => $order->getEmail(),
            ], [
                'stripe_account' => $organizer->getStripeAccountId(),
                'idempotency_key' => 'pi_'.$order->getReference(),
            ]);
        }

        $order->setStripeSessionId($paymentIntent->id);
        $em->flush();

        return $this->json(['clientSecret' => $paymentIntent->client_secret]);
    }

    /**
     * @codeCoverageIgnore Requires live Stripe API
     */
    #[Route('/commande/{id}/paiement-stripe', name: 'app_order_checkout_fallback', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function checkoutFallback(int $id, EntityManagerInterface $em, StripeService $stripeService): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->find($id);
        if (!$order || BilletBuyer::STATUS_PENDING !== $order->getStatus() || !$order->getEmail()) {
            throw $this->createNotFoundException();
        }

        $organizer = $order->getEvent()->getAccount();
        if (!$organizer->getStripeAccountId()) {
            throw $this->createNotFoundException();
        }

        $lineItems = [];
        foreach ($order->getItems() as $item) {
            $lineItems[] = [
                'price_data' => [
                    'currency' => 'eur',
                    'unit_amount' => $item->getUnitPriceHT(),
                    'product_data' => [
                        'name' => $item->getBilletName(),
                    ],
                ],
                'quantity' => $item->getQuantity(),
            ];
        }

        $hasDebt = ($organizer->getDebt() ?? 0) > 0;
        $successUrl = $this->generateUrl('app_order_success', ['id' => $order->getId()], \Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL).'?redirect_status=succeeded&payment_intent={CHECKOUT_SESSION_ID}';
        $cancelUrl = $this->generateUrl('app_order_payment', ['id' => $order->getId()], \Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL);

        if ($hasDebt) {
            $session = $stripeService->getClient()->checkout->sessions->create([
                'mode' => 'payment',
                'line_items' => $lineItems,
                'customer_email' => $order->getEmail(),
                'payment_intent_data' => [
                    'metadata' => [
                        'order_id' => $order->getId(),
                        'reference' => $order->getReference(),
                        'debt_organizer_id' => $organizer->getId(),
                    ],
                ],
                'success_url' => $successUrl,
                'cancel_url' => $cancelUrl,
            ]);
        } else {
            $commissionRate = $organizer->getCommissionRate() ?? 0;
            $applicationFee = (int) round($order->getTotalHT() * ($commissionRate / 100));

            $session = $stripeService->getClient()->checkout->sessions->create([
                'mode' => 'payment',
                'line_items' => $lineItems,
                'customer_email' => $order->getEmail(),
                'payment_intent_data' => [
                    'application_fee_amount' => $applicationFee,
                    'metadata' => [
                        'order_id' => $order->getId(),
                        'reference' => $order->getReference(),
                    ],
                ],
                'success_url' => $successUrl,
                'cancel_url' => $cancelUrl,
            ], ['stripe_account' => $organizer->getStripeAccountId()]);
        }

        $order->setStripeSessionId($session->payment_intent);
        $em->flush();

        return $this->redirect($session->url);
    }

    #[Route('/commande/{id}/confirmation', name: 'app_order_success', requirements: ['id' => '\d+'], methods: ['GET'])]
    public function success(int $id, Request $request, EntityManagerInterface $em, BilletOrderService $billetOrderService, StripeService $stripeService, AuditService $audit): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->find($id);
        if (!$order) {
            throw $this->createNotFoundException();
        }

        $redirectStatus = $request->query->getString('redirect_status');
        $paymentIntentId = $request->query->getString('payment_intent');

        if ('succeeded' === $redirectStatus && BilletBuyer::STATUS_PENDING === $order->getStatus()) {
            $this->savePaymentDetails($order, $paymentIntentId, $stripeService, $em);
            $billetOrderService->generateOrderTickets($order);
            $audit->log('order_paid', 'BilletBuyer', $order->getId(), [
                'orderNumber' => $order->getOrderNumber(),
                'totalHT' => $order->getTotalHTDecimal(),
            ]);
            $billetOrderService->generateAndSendTickets($order);
            $billetOrderService->notifyOrganizer($order);
        }

        $failed = 'failed' === $redirectStatus;

        return $this->render('order/success.html.twig', [
            'order' => $order,
            'failed' => $failed,
            'breadcrumbs' => [
                ['name' => 'Accueil', 'url' => '/'],
                ['name' => $failed ? 'Echec' : 'Confirmation'],
            ],
        ]);
    }

    #[Route('/ma-commande/{orderNumber}/{token}', name: 'app_order_public', methods: ['GET'])]
    public function publicOrder(string $orderNumber, string $token, EntityManagerInterface $em): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->findOneBy(['orderNumber' => $orderNumber, 'accessToken' => $token]);
        if (!$order) {
            throw $this->createNotFoundException();
        }

        $tickets = $em->getRepository(BilletOrder::class)->findBy(['billetBuyer' => $order]);

        return $this->render('order/public.html.twig', [
            'order' => $order,
            'tickets' => $tickets,
            'breadcrumbs' => [
                ['name' => 'Accueil', 'url' => '/'],
                ['name' => 'Commande '.$order->getOrderNumber()],
            ],
        ]);
    }

    #[Route('/ma-commande/{orderNumber}/{token}/billet/{ticketReference}', name: 'app_order_download_ticket', requirements: ['ticketReference' => self::REF_PATTERN], methods: ['GET'])]
    public function downloadTicket(string $orderNumber, string $token, string $ticketReference, EntityManagerInterface $em, BilletOrderService $billetOrderService): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->findOneBy(['orderNumber' => $orderNumber, 'accessToken' => $token]);
        if (!$order) {
            throw $this->createNotFoundException();
        }

        $ticket = $em->getRepository(BilletOrder::class)->findOneBy([
            'billetBuyer' => $order,
            'reference' => $ticketReference,
        ]);
        if (!$ticket) {
            throw $this->createNotFoundException();
        }

        $pdf = $billetOrderService->generatePdf($ticket);

        return new Response($pdf, 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="'.$ticket->getReference().'.pdf"',
        ]);
    }

    #[Route('/ma-commande/{orderNumber}/{token}/facture', name: 'app_order_invoice', methods: ['GET'])]
    public function downloadInvoice(string $orderNumber, string $token, EntityManagerInterface $em, InvoiceService $invoiceService): Response
    {
        $order = $em->getRepository(BilletBuyer::class)->findOneBy(['orderNumber' => $orderNumber, 'accessToken' => $token]);
        if (!$order || BilletBuyer::STATUS_PAID !== $order->getStatus()) {
            throw $this->createNotFoundException();
        }

        $pdf = $invoiceService->generatePdf($order);

        return new Response($pdf, 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="facture_'.$order->getOrderNumber().'.pdf"',
        ]);
    }

    /**
     * @param list<array<string, mixed>> $cart
     */
    private function buildOrderItems(BilletBuyer $order, array $cart, Event $event, EntityManagerInterface $em): int
    {
        $totalHT = 0;

        foreach ($cart as $item) {
            $billetId = (int) ($item['billetId'] ?? 0);
            $qty = (int) ($item['qty'] ?? 0);
            if ($qty <= 0) {
                continue;
            }

            $billet = $em->getRepository(Billet::class)->find($billetId);
            if (!$this->isBilletAvailable($billet, $event)) {
                continue;
            }

            $qty = $this->clampQuantity($billet, $qty);
            if (0 === $qty) {
                continue;
            }

            $orderItem = new BilletBuyerItem();
            $orderItem->setBillet($billet);
            $orderItem->setBilletName($billet->getName());
            $orderItem->setQuantity($qty);
            $orderItem->setUnitPriceHT($billet->getPriceHT());

            $order->addItem($orderItem);
            $totalHT += $orderItem->getLineTotalHT();
        }

        return $totalHT;
    }

    private function isBilletAvailable(?Billet $billet, Event $event): bool
    {
        if (!$billet || $billet->getCategory()->getEvent()->getId() !== $event->getId()) {
            return false;
        }

        return !$billet->isNotBuyable();
    }

    private function clampQuantity(Billet $billet, int $qty): int
    {
        if ($billet->isUnlimited()) {
            return $qty;
        }

        if ($billet->getQuantity() <= 0) {
            return 0;
        }

        return min($qty, $billet->getQuantity());
    }

    private function isValidCart(mixed $cart): bool
    {
        if (!\is_array($cart) || 0 === \count($cart)) {
            return false;
        }

        foreach ($cart as $item) {
            if (!\is_array($item) || !isset($item['billetId'], $item['qty']) || !is_numeric($item['billetId']) || !is_numeric($item['qty'])) {
                return false;
            }
        }

        return true;
    }

    /**
     * @codeCoverageIgnore Requires live Stripe API
     */
    private function savePaymentDetails(BilletBuyer $order, string $paymentIntentId, StripeService $stripeService, EntityManagerInterface $em): void
    {
        if (!$paymentIntentId) {
            return;
        }

        $organizer = $order->getEvent()->getAccount();
        if (!$organizer->getStripeAccountId()) {
            return;
        }

        try {
            $pi = $stripeService->getClient()->paymentIntents->retrieve(
                $paymentIntentId,
                ['expand' => ['payment_method']],
                ['stripe_account' => $organizer->getStripeAccountId()]
            );

            $pm = $pi->payment_method;
            if ($pm) {
                $order->setPaymentMethod($pm->type ?? null);
                if (isset($pm->card)) {
                    $order->setCardBrand($pm->card->brand ?? null);
                    $order->setCardLast4($pm->card->last4 ?? null);
                }
            }

            $order->setStripeSessionId($paymentIntentId);
            $em->flush();
        } catch (\Exception) {
            // Stripe failure is non-blocking
        }
    }
}
