<?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\Admin;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Filesystem\Filesystem;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils\Mailer;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils\Pdf;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils\PdfPuppeteer;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartCheckout;
use CpCreation\VitiCore\Cart\Model\DeliveryAddress;
use CpCreation\VitiCore\Cart\Model\InvoiceAddress;
use CpCreation\VitiCore\Cart\Repository\CartCheckoutRepository;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\Cart\Repository\PaymentMethodRepository;
use CpCreation\VitiCore\Contact\Repository\ContactRepository;
use CpCreation\VitiCore\Shop\Repository\ShopConfigRepository;
use CpCreation\VitiCore\User\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

#[Route('/order')]
#[IsGranted(new Expression("is_granted('ROLE_ADMIN') or is_granted('ROLE_ORDER')"))]
final class OrderController extends AbstractController
{
    /**
     * @var array<string, array{label: string, url_template: string}>
     */
    private const TRACKING_CARRIERS = [
        'laposte' => [
            'label' => 'La Poste / Colissimo / Chronopost',
            'url_template' => 'https://www.laposte.fr/outils/suivre-vos-envois?code=%s',
        ],
        'dhl' => [
            'label' => 'DHL Express',
            'url_template' => 'https://www.dhl.com/fr-fr/home/tracking/tracking-express.html?submit=1&tracking-id=%s',
        ],
        'ups' => [
            'label' => 'UPS',
            'url_template' => 'https://www.ups.com/track?loc=fr_FR&tracknum=%s',
        ],
        'fedex' => [
            'label' => 'FedEx',
            'url_template' => 'https://www.fedex.com/fedextrack/?trknbr=%s',
        ],
    ];

    /**
     * @var list<string>
     */
    private const STATS_GROUP_BY = ['day', 'week', 'month', 'year'];

    public function __construct(
        private readonly CartCheckoutRepository $cartCheckoutRepository,
        private readonly CartRepository $cartRepository,
        private readonly PaymentMethodRepository $paymentMethodRepository,
        private readonly ContactRepository $contactRepository,
        private readonly ShopConfigRepository $shopConfigRepository,
        private readonly UserRepository $userRepository,
        private readonly EntityManagerInterface $em,
        private readonly Filesystem $filesystem,
        private readonly Pdf $pdf,
        private readonly Mailer $mailer,
        private readonly PdfPuppeteer $pdfPuppeteer,
        private readonly TranslatorInterface $translator,
        private readonly ValidatorInterface $validator,
        private readonly mixed $giftFilesystemAdapter,
        private readonly array $paths,
    ) {
    }

    #[Route('', name: 'admin_order')]
    public function index(Request $request): Response
    {
        $filters = $this->buildOrderFilters($request);
        $carts = $this->cartRepository->findTrackingAdminFiltered(
            $filters['status'],
            $filters['payment_method_id'],
            $filters['created_from'],
            $filters['created_to'],
            'DESC'
        );

        return $this->render('@CpCreationVitiCore/admin/order/index.html.twig', [
            'items' => $carts,
            'payment_methods' => $this->paymentMethodRepository->findBy([], ['position' => 'ASC']),
            'filters' => $filters,
            'invoice_feature_enabled' => $this->isInvoiceFeatureEnabled(),
        ]);
    }

    #[Route('/stats', name: 'admin_order_stats')]
    public function stats(Request $request): Response
    {
        $filters = $this->buildOrderStatsFilters($request);
        $carts = $this->cartRepository->findPaidForStatsFiltered(
            $filters['payment_method_id'],
            $filters['paid_from'],
            $filters['paid_to']
        );

        return $this->render('@CpCreationVitiCore/admin/order/stats.html.twig', [
            'filters' => $filters,
            'payment_methods' => $this->paymentMethodRepository->findBy([], ['position' => 'ASC']),
            'stats' => $this->buildOrderStats(
                $carts,
                $filters['group_by'],
                $filters['paid_from'],
                $filters['paid_to']
            ),
        ]);
    }

    #[Route('/export/excel', name: 'admin_order_export_excel')]
    public function exportExcel(Request $request): Response
    {
        $filters = $this->buildOrderFilters($request);
        $carts = $this->cartRepository->findTrackingAdminFiltered(
            $filters['status'],
            $filters['payment_method_id'],
            $filters['created_from'],
            $filters['created_to'],
            'DESC'
        );

        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->setTitle('Commandes');
        $sheet->fromArray([
            'Date',
            'Reference',
            'Client',
            'Email',
            'Code partenaire',
            'Total',
            'Mode de paiement',
            'Statut',
            'Payee le',
            'Expédiée',
        ], null, 'A1');

        $row = 2;
        foreach ($carts as $cart) {
            $checkout = $cart->getCartCheckout();

            $sheet->fromArray([
                $cart->getCreatedAt()->format('d/m/Y H:i'),
                $cart->getToken() ?: '',
                mb_trim(($cart->getLastname() ?? '').' '.($cart->getFirstname() ?? '')),
                $cart->getEmail() ?: '',
                $cart->getGroupCode() ?: '',
                $this->formatAmountForExport($cart->getTotal()),
                $this->resolvePaymentMethodLabel($cart),
                $cart->isCheckoutPayed() ? 'Payee' : 'En cours',
                $checkout?->getPayedAt()?->format('d/m/Y H:i') ?: '',
                $cart->isDelivery() ? 'Oui' : 'Non',
            ], null, 'A'.$row);

            ++$row;
        }

        foreach (range('A', 'J') as $column) {
            $sheet->getColumnDimension($column)->setAutoSize(true);
        }

        $filePath = $this->createTempFilePath('commandes_', 'xlsx');
        $writer = new Xlsx($spreadsheet);
        $writer->save($filePath);

        return $this->createDownloadResponse(
            $filePath,
            'commandes_'.$this->buildExportSuffix($filters).'.xlsx',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        );
    }

    #[Route('/purchase-orders/download', name: 'admin_order_purchase_orders_download')]
    public function downloadPurchaseOrders(Request $request): Response
    {
        $filters = $this->buildOrderFilters($request);
        $carts = $this->cartRepository->findTrackingAdminFiltered(
            $filters['status'],
            $filters['payment_method_id'],
            $filters['created_from'],
            $filters['created_to'],
            'DESC'
        );

        if ([] === $carts) {
            $this->addFlash('alert-warning', 'Aucune commande ne correspond aux filtres sélectionnés.');

            return $this->redirectToRoute('admin_order', $filters['query_parameters']);
        }

        if (!class_exists(\ZipArchive::class)) {
            $this->addFlash('alert-danger', "L'extension ZIP n'est pas disponible sur ce serveur.");

            return $this->redirectToRoute('admin_order', $filters['query_parameters']);
        }

        $contact = $this->contactRepository->findOne();
        if (null === $contact) {
            throw $this->createNotFoundException('Aucun contact boutique configuré.');
        }

        $zipPath = $this->createTempFilePath('bons_de_commande_', 'zip');
        $zip = new \ZipArchive();
        $result = $zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);

        if (true !== $result) {
            throw new \RuntimeException($this->isInvoiceFeatureEnabled()
                ? "Impossible de creer l'archive des documents PDF."
                : "Impossible de creer l'archive des bons de commande.");
        }

        $addedFiles = 0;
        foreach ($carts as $index => $cart) {
            $filename = $this->pdf->createPdfCartDocumentAdmin($contact, $cart, 'fr');
            $filePath = $this->filesystem->absolutePath('order', $filename);

            if (!$this->filesystem->exists($filePath)) {
                continue;
            }

            $zip->addFile($filePath, $this->buildPurchaseOrderArchiveName($cart, $index + 1));
            ++$addedFiles;
        }

        $zip->close();

        if (0 === $addedFiles) {
            @unlink($zipPath);
            $this->addFlash('alert-danger', $this->isInvoiceFeatureEnabled()
                ? "Aucun document PDF n'a pu etre genere."
                : "Aucun bon de commande PDF n'a pu etre genere.");

            return $this->redirectToRoute('admin_order', $filters['query_parameters']);
        }

        return $this->createDownloadResponse(
            $zipPath,
            ($this->isInvoiceFeatureEnabled() ? 'documents_' : 'bons-de-commande_').$this->buildExportSuffix($filters).'.zip',
            'application/zip'
        );
    }

    #[Route('/{id}', name: 'admin_order_order')]
    public function html(Request $request, Cart $cart): Response
    {
        $siteUser = $cart->getUser();
        $cartEmail = $cart->getEmail();

        if (null === $siteUser && null !== $cartEmail && '' !== mb_trim($cartEmail)) {
            $siteUser = $this->userRepository->findOneBy([
                'email' => mb_trim($cartEmail),
            ]);
        }

        $documentLabel = $cart->getDocumentLabelKey();
        $checkoutCompletionIssue = $this->getCheckoutCompletionIssue($cart);

        return $this->render('@CpCreationVitiCore/admin/order/order.html.twig', [
            'cart' => $cart,
            'siteUser' => $siteUser,
            'payment_methods' => $this->paymentMethodRepository->findAvailableForAdminOrderQuery()->getQuery()->getResult(),
            'checkout_completion_issue' => $checkoutCompletionIssue,
            'tracking_carriers' => self::TRACKING_CARRIERS,
            'selected_tracking_carrier' => $this->resolveTrackingCarrierKey($cart->getUrlDeSuiviCarrier()),
            'document_label' => $documentLabel,
            'download_document_label' => $cart->getDownloadDocumentLabelKey(),
            'invoice_feature_enabled' => $this->isInvoiceFeatureEnabled(),
        ]);
    }

    #[Route('/{id}/document-type', name: 'admin_order_document_type', methods: ['POST'])]
    public function updateDocumentType(Request $request, Cart $cart): RedirectResponse
    {
        if ($cart->isCheckoutPayed()) {
            $this->addFlash('alert-warning', 'Le type de document ne peut plus être modifié après paiement.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        if (!$this->isCsrfTokenValid('document-type-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire de type de document est invalide.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $cart->setDocumentType(self::trimToNull($request->request->get('document_type')));
        $this->cartRepository->save($cart);

        $this->addFlash('alert-success', 'Le type de document a été mis à jour.');

        return $this->redirectToRoute('admin_order_order', [
            'id' => $cart->getId()->toString(),
        ]);
    }

    #[Route('/{id}/send-document-email', name: 'admin_order_send_document_email', methods: ['POST'])]
    public function sendDocumentEmail(Request $request, Cart $cart): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('send-document-email-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire d\'envoi du document est invalide.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $documentLabelKey = $cart->getDocumentLabelKey();
        $canSendProforma = !$cart->isCheckoutPayed() && 'checkout_design.document_proforma' === $documentLabelKey;
        $canSendInvoice = $cart->isCheckoutPayed() && 'checkout_design.document_invoice' === $documentLabelKey;

        if (!$canSendProforma && !$canSendInvoice) {
            $this->addFlash('alert-warning', 'Aucun envoi manuel n\'est disponible pour ce document.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $email = self::trimToNull($cart->getEmail());
        if (null === $email) {
            $this->addFlash('alert-warning', 'Aucun email client n\'est renseigné pour cette commande.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $contact = $this->contactRepository->findOne();
        if (null === $contact) {
            $this->addFlash('alert-warning', 'Aucun contact boutique n\'est configuré pour envoyer l\'email.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $this->mailer->sendOrderCustomer($contact, $cart);
        if ($canSendProforma) {
            $this->addFlash('alert-success', 'La facture pro forma a été envoyée au client.');
        } else {
            $this->addFlash('alert-success', 'La facture a été renvoyée au client.');
        }

        return $this->redirectToRoute('admin_order_order', [
            'id' => $cart->getId()->toString(),
        ]);
    }

    #[Route('/{id}/mark-paid', name: 'admin_order_mark_paid', methods: ['POST'])]
    public function markPaid(Request $request, Cart $cart): RedirectResponse
    {
        if ($cart->isCheckoutPayed()) {
            $this->addFlash('alert-warning', 'Cette commande est déjà payée.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $checkoutCompletionIssue = $this->getCheckoutCompletionIssue($cart);
        if (null !== $checkoutCompletionIssue) {
            $this->addFlash('alert-danger', $checkoutCompletionIssue);

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        if (!$this->isCsrfTokenValid('mark-paid-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire de mise en paiement est invalide.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $paymentMethodId = self::trimToNull($request->request->get('payment_method_id'));
        $paidAtInput = self::trimToNull($request->request->get('payed_at'));

        if (null === $paymentMethodId) {
            $this->addFlash('alert-danger', 'Veuillez sélectionner une méthode de paiement.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $paymentMethod = $this->paymentMethodRepository->findAvailableForAdminOrderById($paymentMethodId);
        if (null === $paymentMethod) {
            $this->addFlash('alert-danger', 'La méthode de paiement sélectionnée est invalide ou indisponible.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        if (null === $paidAtInput) {
            $this->addFlash('alert-danger', 'Veuillez renseigner une date de paiement.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $paidAt = \DateTimeImmutable::createFromFormat('Y-m-d\\TH:i', $paidAtInput)
            ?: \DateTimeImmutable::createFromFormat('Y-m-d H:i', $paidAtInput);

        if (!$paidAt instanceof \DateTimeImmutable) {
            $this->addFlash('alert-danger', 'La date de paiement est invalide.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $checkout = $cart->getCartCheckout();
        if (null === $checkout) {
            $checkout = new CartCheckout();
            $checkout->setCart($cart);
            $checkout->setPaymentMethod($paymentMethod);
            if (null === $checkout->getPayedToken()) {
                $checkout->setPayedToken($cart->getToken());
            }
            $cart->setCartCheckout($checkout);
            $this->cartRepository->save($cart);
        }

        $checkout->setPaymentMethod($paymentMethod);
        if (null === $checkout->getPayedToken()) {
            $checkout->setPayedToken($cart->getToken());
        }
        $checkout->setPayedAt($paidAt);

        $this->cartCheckoutRepository->save($checkout);
        $contact = $this->contactRepository->findOne();
        if (null !== $contact) {
            $this->mailer->sendPaidOrder($contact, $cart);
        }
        $this->addFlash('alert-success', 'La commande a été marquée comme payée.');

        return $this->redirectToRoute('admin_order_order', [
            'id' => $cart->getId()->toString(),
        ]);
    }

    #[Route('/{id}/delivery', name: 'admin_order_delivery', methods: ['POST'])]
    public function delivery(Request $request, Cart $cart): RedirectResponse
    {
        if (!$cart->isCheckoutPayed()) {
            $this->addFlash('alert-danger', 'Impossible de marquer comme expédiée une commande non payée.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        if (!$this->isCsrfTokenValid('mark-delivery-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire de mise en expédition est invalide.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $trackingCarrier = self::trimToNull($request->request->get('tracking_carrier'));
        $trackingCode = self::trimToNull($request->request->get('code_carrier'));

        $wasInDelivery = $cart->isDelivery();
        $previousTrackingUrl = $cart->getUrlDeSuiviCarrier();

        $cart->setDelivery(true);
        $cart->setCodeCarrier($trackingCode);
        $cart->setUrlDeSuiviCarrier($this->buildTrackingUrl($trackingCarrier, $trackingCode));

        $this->cartRepository->save($cart);

        $email = $cart->getEmail();
        $shouldSendEmail = !$wasInDelivery || (null === $previousTrackingUrl && null !== $cart->getUrlDeSuiviCarrier());

        if (null === $email || '' === mb_trim($email)) {
            $this->addFlash('alert-warning', 'La commande a bien été marquée comme expédiée, mais aucun email client n\'est renseigné.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        if ($shouldSendEmail) {
            $contact = $this->contactRepository->findOne();

            if (null !== $contact) {
                $this->mailer->sendDelivery($contact, $cart);
                $this->addFlash('alert-success', 'La commande a été marquée comme expédiée et l\'email client a été envoyé.');
            } else {
                $this->addFlash('alert-warning', 'La commande a bien été marquée comme expédiée, mais aucun contact site n\'a été trouvé pour envoyer l\'email.');
            }
        } else {
            $this->addFlash('alert-success', 'Les informations d\'expédition ont été mises à jour.');
        }

        return $this->redirectToRoute('admin_order_order', [
            'id' => $cart->getId()->toString(),
        ]);
    }

    #[Route('/{id}/purchase-order-pdf', name: 'admin_order_purchase_order_pdf')]
    public function purchaseOrderPdf(Cart $cart): Response
    {
        if ($this->em->getFilters()->isEnabled('soft_deletable')) {
            $this->em->getFilters()->disable('soft_deletable');
        }

        $contact = $this->contactRepository->findOne();
        if (null === $contact) {
            throw $this->createNotFoundException('Aucun contact boutique configuré.');
        }

        $filename = $this->pdf->createPdfCartDocumentAdmin($contact, $cart, 'fr');
        $filePath = $this->filesystem->absolutePath('order', $filename);

        return $this->file($filePath, $filename, ResponseHeaderBag::DISPOSITION_ATTACHMENT);
    }

    #[Route('/{id}/download', name: 'admin_order_gift_download')]
    public function download(Cart $cart): Response
    {
        $contact = $this->contactRepository->findOne();
        $path = $this->giftFilesystemAdapter->getAdapter()->getPathPrefix();
        $filename = $this->pdfPuppeteer->createGiftCardCombined($cart, $contact);

        return $this->file($path.$filename);
    }

    #[Route('/{id}/resend_gift', name: 'admin_order_gift_resend')]
    public function resendGiftCard(Cart $cart): RedirectResponse
    {
        $contact = $this->contactRepository->findOne();
        $this->mailer->sendGiftCard($contact, $cart);
        $this->addFlash('alert-success', 'Les cartes cadeaux ont été renvoyées !');

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

    #[Route('/{id}/payedAt', name: 'admin_order_payedAt')]
    public function payed(CartCheckout $cartCheckout): RedirectResponse
    {
        $cartCheckout->setPayedAt($cartCheckout->getPayedAt() ? null : new \DateTimeImmutable());
        $this->cartCheckoutRepository->save($cartCheckout);

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

    #[Route('/{id}/redirect/payment', name: 'admin_order_redirect_payment')]
    public function redirectPagePayment(Cart $cart): RedirectResponse
    {
        return $this->redirectToRoute('shop_checkout_cart', [
            'cartId' => $cart->getId()->toString(),
        ]);
    }

    #[Route('/{id}/disabled', name: 'admin_order_disabled')]
    public function delete(CartCheckout $cartCheckout): RedirectResponse
    {
        $this->cartCheckoutRepository->softDelete($cartCheckout);
        $this->addFlash('alert-success', 'La commande '.$cartCheckout->getPayedToken().' a été supprimée.');

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

    #[Route('/{id}/delete', name: 'admin_order_delete')]
    public function deleteCart(Cart $cart): RedirectResponse
    {
        if ($cart->isCheckoutPayed()) {
            $this->addFlash('alert-danger', 'Impossible de supprimer une commande déjà payée.');

            return $this->redirectToRoute('admin_order_order', [
                'id' => $cart->getId()->toString(),
            ]);
        }

        $reference = $cart->getToken() ?: $cart->getId()->toString();
        $cart->markDeleted();
        $this->cartRepository->save($cart);
        $this->addFlash('alert-success', 'La commande '.$reference.' a été supprimée.');

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

    public function showGiftOptions(Cart $cart): Response
    {
        if ($this->em->getFilters()->isEnabled('soft_deletable')) {
            $this->em->getFilters()->disable('soft_deletable');
        }

        $haveGift = false;
        foreach ($cart->getItems() as $item) {
            if ($item->getVariant()->getType()->isGift()) {
                $haveGift = true;
                break;
            }
        }

        return $this->render('@CpCreationVitiCore/admin/order/gift.html.twig', [
            'haveGift' => $haveGift,
            'item' => $cart,
        ]);
    }

    private function buildTrackingUrl(?string $carrierKey, ?string $trackingCode): ?string
    {
        if (null === $carrierKey || null === $trackingCode || !isset(self::TRACKING_CARRIERS[$carrierKey])) {
            return null;
        }

        return \sprintf(
            self::TRACKING_CARRIERS[$carrierKey]['url_template'],
            rawurlencode($trackingCode)
        );
    }

    private function resolveTrackingCarrierKey(?string $trackingUrl): ?string
    {
        if (null === $trackingUrl || '' === $trackingUrl) {
            return null;
        }

        foreach (self::TRACKING_CARRIERS as $key => $carrier) {
            $prefix = explode('%s', $carrier['url_template'])[0];

            if (str_starts_with($trackingUrl, $prefix)) {
                return $key;
            }
        }

        return null;
    }

    private function isInvoiceFeatureEnabled(): bool
    {
        return $this->shopConfigRepository->findOne()?->isInvoice() ?? false;
    }

    private static function trimToNull(mixed $value): ?string
    {
        if (!\is_scalar($value)) {
            return null;
        }

        $value = mb_trim((string) $value);

        return '' !== $value ? $value : null;
    }

    /**
     * @return array{
     *     status: string,
     *     payment_method_id: ?string,
     *     created_from: ?\DateTimeImmutable,
     *     created_to: ?\DateTimeImmutable,
     *     created_from_input: ?string,
     *     created_to_input: ?string,
     *     query_parameters: array<string, string>
     * }
     */
    private function buildOrderFilters(Request $request): array
    {
        $status = self::trimToNull($request->query->get('status')) ?? 'all';
        if (!\in_array($status, ['all', 'pending', 'paid'], true)) {
            $status = 'all';
        }

        $paymentMethodId = self::trimToNull($request->query->get('payment_method_id'));
        $createdFromInput = self::trimToNull($request->query->get('created_from'));
        $createdToInput = self::trimToNull($request->query->get('created_to'));

        $createdFrom = $this->parseDateFilter($createdFromInput);
        $createdTo = $this->parseDateFilter($createdToInput)?->modify('+1 day');

        return [
            'status' => $status,
            'payment_method_id' => $paymentMethodId,
            'created_from' => $createdFrom,
            'created_to' => $createdTo,
            'created_from_input' => $createdFrom?->format('Y-m-d'),
            'created_to_input' => null !== $createdTo ? $createdTo->modify('-1 day')->format('Y-m-d') : null,
            'query_parameters' => array_filter([
                'status' => $status,
                'payment_method_id' => $paymentMethodId,
                'created_from' => $createdFrom?->format('Y-m-d'),
                'created_to' => null !== $createdTo ? $createdTo->modify('-1 day')->format('Y-m-d') : null,
            ], static fn (?string $value): bool => null !== $value && '' !== $value),
        ];
    }

    private function parseDateFilter(?string $value): ?\DateTimeImmutable
    {
        if (null === $value) {
            return null;
        }

        $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $value);

        return $date instanceof \DateTimeImmutable ? $date : null;
    }

    /**
     * @return array{
     *     payment_method_id: ?string,
     *     paid_from: \DateTimeImmutable,
     *     paid_to: \DateTimeImmutable,
     *     paid_from_input: string,
     *     paid_to_input: string,
     *     group_by: string,
     *     query_parameters: array<string, string>
     * }
     */
    private function buildOrderStatsFilters(Request $request): array
    {
        $today = new \DateTimeImmutable('today');
        $defaultFrom = $today->modify('first day of this month')->modify('-11 months');
        $defaultTo = $today->modify('+1 day');

        $groupBy = self::trimToNull($request->query->get('group_by')) ?? 'month';
        if (!\in_array($groupBy, self::STATS_GROUP_BY, true)) {
            $groupBy = 'month';
        }

        $paymentMethodId = self::trimToNull($request->query->get('payment_method_id'));
        $paidFrom = $this->parseDateFilter(self::trimToNull($request->query->get('paid_from'))) ?? $defaultFrom;
        $paidTo = $this->parseDateFilter(self::trimToNull($request->query->get('paid_to')))?->modify('+1 day') ?? $defaultTo;

        if ($paidTo <= $paidFrom) {
            $paidTo = $paidFrom->modify('+1 day');
        }

        return [
            'payment_method_id' => $paymentMethodId,
            'paid_from' => $paidFrom,
            'paid_to' => $paidTo,
            'paid_from_input' => $paidFrom->format('Y-m-d'),
            'paid_to_input' => $paidTo->modify('-1 day')->format('Y-m-d'),
            'group_by' => $groupBy,
            'query_parameters' => array_filter([
                'payment_method_id' => $paymentMethodId,
                'paid_from' => $paidFrom->format('Y-m-d'),
                'paid_to' => $paidTo->modify('-1 day')->format('Y-m-d'),
                'group_by' => $groupBy,
            ], static fn (?string $value): bool => null !== $value && '' !== $value),
        ];
    }

    /**
     * @param list<Cart> $carts
     *
     * @return array{
     *     summary: array{
     *         order_count: int,
     *         revenue_ttc: int,
     *         average_basket_ttc: int,
     *         average_equivalence: float,
     *         discount_ttc: int,
     *         degressive_ttc: int,
     *         promo_items_ttc: int,
     *         promo_shipping_ttc: int
     *     },
     *     chart: array{
     *         categories: list<string>,
     *         orders: list<int>,
     *         revenue: list<float>,
     *         average_basket: list<float>
     *     },
     *     top_products: list<array{name: string, quantity: int, revenue_ttc: int}>
     * }
     */
    private function buildOrderStats(
        array $carts,
        string $groupBy,
        \DateTimeImmutable $paidFrom,
        \DateTimeImmutable $paidTo,
    ): array {
        $summary = [
            'order_count' => 0,
            'revenue_ttc' => 0,
            'average_basket_ttc' => 0,
            'average_equivalence' => 0.0,
            'discount_ttc' => 0,
            'degressive_ttc' => 0,
            'promo_items_ttc' => 0,
            'promo_shipping_ttc' => 0,
        ];
        $equivalenceTotal = 0.0;
        $topProducts = [];
        $chartBuckets = $this->createStatsBuckets($paidFrom, $paidTo, $groupBy);

        foreach ($carts as $cart) {
            $paidAt = $cart->getCartCheckout()?->getPayedAt();
            if (!$paidAt instanceof \DateTimeInterface) {
                continue;
            }

            $pricing = $cart->getPricing();
            $totalTTC = $pricing?->totalTTC ?? 0;

            ++$summary['order_count'];
            $summary['revenue_ttc'] += $totalTTC;
            $summary['discount_ttc'] += $pricing?->discountTTC ?? 0;
            $summary['degressive_ttc'] += $pricing?->degressiveTTC ?? 0;
            $equivalenceTotal += (float) $cart->getQuantityAllAbsolute();

            foreach ($cart->getPromos() as $promo) {
                $summary['promo_items_ttc'] += $promo->discountItemsTTC;
                $summary['promo_shipping_ttc'] += $promo->discountShippingTTC;
            }

            $bucketStart = $this->getStatsBucketStart(\DateTimeImmutable::createFromInterface($paidAt), $groupBy);
            $bucketKey = $bucketStart->format('c');
            if (isset($chartBuckets[$bucketKey])) {
                ++$chartBuckets[$bucketKey]['orders'];
                $chartBuckets[$bucketKey]['revenue_ttc'] += $totalTTC;
            }

            foreach ($pricing?->lines ?? [] as $line) {
                if ($line->isPromoGift) {
                    continue;
                }

                $productKey = $line->productId ?? $line->variantId ?? md5((string) ($line->productName ?? $line->variantFullName));
                $productName = $line->productName ?? $line->variantFullName ?? 'Produit inconnu';

                if (!isset($topProducts[$productKey])) {
                    $topProducts[$productKey] = [
                        'name' => $productName,
                        'quantity' => 0,
                        'revenue_ttc' => 0,
                    ];
                }

                $topProducts[$productKey]['quantity'] += $line->quantity;
                $topProducts[$productKey]['revenue_ttc'] += $line->netTTC;
            }
        }

        if ($summary['order_count'] > 0) {
            $summary['average_basket_ttc'] = (int) round($summary['revenue_ttc'] / $summary['order_count']);
            $summary['average_equivalence'] = round($equivalenceTotal / $summary['order_count'], 2);
        }

        usort($topProducts, static function (array $left, array $right): int {
            $quantityComparison = $right['quantity'] <=> $left['quantity'];
            if (0 !== $quantityComparison) {
                return $quantityComparison;
            }

            $revenueComparison = $right['revenue_ttc'] <=> $left['revenue_ttc'];
            if (0 !== $revenueComparison) {
                return $revenueComparison;
            }

            return strcmp((string) $left['name'], (string) $right['name']);
        });

        $chart = [
            'categories' => [],
            'orders' => [],
            'revenue' => [],
            'average_basket' => [],
        ];

        foreach ($chartBuckets as $bucket) {
            $chart['categories'][] = $bucket['label'];
            $chart['orders'][] = $bucket['orders'];
            $chart['revenue'][] = round($bucket['revenue_ttc'] / 100, 2);
            $chart['average_basket'][] = $bucket['orders'] > 0
                ? round(($bucket['revenue_ttc'] / $bucket['orders']) / 100, 2)
                : 0.0;
        }

        return [
            'summary' => $summary,
            'chart' => $chart,
            'top_products' => array_slice(array_values($topProducts), 0, 10),
        ];
    }

    /**
     * @return array<string, array{label: string, orders: int, revenue_ttc: int}>
     */
    private function createStatsBuckets(
        \DateTimeImmutable $paidFrom,
        \DateTimeImmutable $paidTo,
        string $groupBy,
    ): array {
        $buckets = [];
        $cursor = $this->getStatsBucketStart($paidFrom, $groupBy);
        $end = $this->getStatsBucketStart($paidTo->modify('-1 second'), $groupBy);

        while ($cursor <= $end) {
            $bucketKey = $cursor->format('c');
            $buckets[$bucketKey] = [
                'label' => $this->formatStatsBucketLabel($cursor, $groupBy),
                'orders' => 0,
                'revenue_ttc' => 0,
            ];
            $cursor = $this->getNextStatsBucketStart($cursor, $groupBy);
        }

        return $buckets;
    }

    private function getStatsBucketStart(\DateTimeImmutable $date, string $groupBy): \DateTimeImmutable
    {
        $date = $date->setTime(0, 0);

        return match ($groupBy) {
            'day' => $date,
            'week' => $date->modify('monday this week'),
            'year' => $date->setDate((int) $date->format('Y'), 1, 1),
            default => $date->modify('first day of this month'),
        };
    }

    private function getNextStatsBucketStart(\DateTimeImmutable $date, string $groupBy): \DateTimeImmutable
    {
        return match ($groupBy) {
            'day' => $date->modify('+1 day'),
            'week' => $date->modify('+1 week'),
            'year' => $date->modify('first day of january next year'),
            default => $date->modify('first day of next month'),
        };
    }

    private function formatStatsBucketLabel(\DateTimeImmutable $date, string $groupBy): string
    {
        return match ($groupBy) {
            'day' => $date->format('d/m/Y'),
            'week' => sprintf('S%s %s', $date->format('W'), $date->format('Y')),
            'year' => $date->format('Y'),
            default => $date->format('m/Y'),
        };
    }

    private function formatAmountForExport(?int $amount): string
    {
        if (null === $amount) {
            return '';
        }

        return number_format($amount / 100, 2, ',', ' ').' EUR';
    }

    private function resolvePaymentMethodLabel(Cart $cart): string
    {
        $method = $cart->getPaymentMethod()?->getMethod();

        if (null === $method || '' === $method) {
            return '';
        }

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

    private function getCheckoutCompletionIssue(Cart $cart): ?string
    {
        if ($cart->getItems()->isEmpty()) {
            return 'Le panier est vide.';
        }

        if (!$this->isAddressComplete($cart->getDeliveryAddress(), true)) {
            return 'Veuillez compléter les informations de livraison avant de marquer la commande comme payée.';
        }

        $invoiceAddress = $cart->getInvoiceAddress();
        if ($invoiceAddress instanceof InvoiceAddress && $invoiceAddress->isDifferentAddress() && !$this->isAddressComplete($invoiceAddress, false)) {
            return 'Veuillez compléter les informations de facturation avant de marquer la commande comme payée.';
        }

        $validationGroups = ['Default', 'delivery_address'];
        if ($invoiceAddress?->isDifferentAddress()) {
            $validationGroups[] = 'invoice_address';
        }

        $violations = $this->validator->validate($cart, null, $validationGroups);

        foreach ($violations as $violation) {
            if ('cgv' === (string) $violation->getPropertyPath()) {
                continue;
            }

            return $violation->getMessage();
        }

        return null;
    }

    private function isAddressComplete(DeliveryAddress|InvoiceAddress|null $address, bool $includeEmail): bool
    {
        if (null === $address) {
            return false;
        }

        $requiredFields = [
            $address->getFirstname(),
            $address->getLastname(),
            $address->getStreet(),
            $address->getPostal(),
            $address->getCity(),
            $address->getCountry(),
            $address->getPhone(),
        ];

        if ($includeEmail) {
            $requiredFields[] = method_exists($address, 'getEmail') ? $address->getEmail() : null;
        }

        foreach ($requiredFields as $value) {
            if (null === $value || '' === trim((string) $value)) {
                return false;
            }
        }

        return true;
    }

    private function buildExportSuffix(array $filters): string
    {
        $parts = ['du_'.$this->getCurrentDateLabel()];

        if ('paid' === $filters['status']) {
            $parts[] = 'payees';
        } elseif ('pending' === $filters['status']) {
            $parts[] = 'en-cours';
        } else {
            $parts[] = 'toutes';
        }

        if (null !== $filters['created_from_input']) {
            $parts[] = 'depuis_'.$filters['created_from_input'];
        }

        if (null !== $filters['created_to_input']) {
            $parts[] = 'jusquau_'.$filters['created_to_input'];
        }

        return implode('_', $parts);
    }

    private function getCurrentDateLabel(): string
    {
        return (new \DateTimeImmutable())->format('Y-m-d');
    }

    private function buildPurchaseOrderArchiveName(Cart $cart, int $index): string
    {
        $reference = $cart->getToken() ?: 'commande-'.$index;
        $reference = preg_replace('/[^A-Za-z0-9_-]+/', '-', $reference) ?: 'commande-'.$index;

        return sprintf('%02d_%s.pdf', $index, $reference);
    }

    private function createTempFilePath(string $prefix, string $extension): string
    {
        $temporaryPath = tempnam(sys_get_temp_dir(), $prefix);
        if (false === $temporaryPath) {
            throw new \RuntimeException('Impossible de créer un fichier temporaire.');
        }

        @unlink($temporaryPath);

        return $temporaryPath.'.'.$extension;
    }

    private function createDownloadResponse(string $filePath, string $downloadName, string $contentType): BinaryFileResponse
    {
        $response = $this->file($filePath, $downloadName, ResponseHeaderBag::DISPOSITION_ATTACHMENT);
        $response->headers->set('Content-Type', $contentType);
        $response->deleteFileAfterSend(true);

        return $response;
    }
}
