<?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\Payment\StripePayment;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils\Mailer;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Utils\Pdf;
use CpCreation\VitiCore\Cart\CartService;
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\InvoiceRepository;
use CpCreation\VitiCore\Cart\Repository\PaymentMethodRepository;
use CpCreation\VitiCore\Contact\Repository\ContactRepository;
use CpCreation\VitiCore\Mailer\Repository\EmailLogRepository;
use CpCreation\VitiCore\Product\Repository\ProductRepository;
use CpCreation\VitiCore\Product\Repository\ProductTypeRepository;
use CpCreation\VitiCore\Shop\Repository\DeliveryMethodRepository;
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 = [
        'db_schenker' => [
            'label' => 'DB-Schenker',
            'url_template' => null,
        ],
        'dhl' => [
            'label' => 'DHL Express',
            'url_template' => 'https://www.dhl.com/fr-fr/home/tracking/tracking-express.html?submit=1&tracking-id=%s',
        ],
        'fedex' => [
            'label' => 'FedEx',
            'url_template' => 'https://www.fedex.com/fedextrack/?trknbr=%s',
        ],
        'genin' => [
            'label' => 'Genin',
            'url_template' => null,
        ],
        'geodis' => [
            'label' => 'Geodis',
            'url_template' => null,
        ],
        'laposte' => [
            'label' => 'La Poste / Colissimo / Chronopost',
            'url_template' => 'https://www.laposte.fr/outils/suivre-vos-envois?code=%s',
        ],
        'logivin' => [
            'label' => 'Logivin',
            'url_template' => null,
        ],
        'ltd' => [
            'label' => 'LTD',
            'url_template' => null,
        ],
        'oenotrans' => [
            'label' => 'Oenotrans',
            'url_template' => null,
        ],
        'patrice_antoine' => [
            'label' => 'Patrice Antoine',
            'url_template' => null,
        ],
        'scellier' => [
            'label' => 'Scellier',
            'url_template' => null,
        ],
        'tcp' => [
            'label' => 'TCP',
            'url_template' => null,
        ],
        'transvins' => [
            'label' => 'Transvins',
            'url_template' => null,
        ],
        'ups' => [
            'label' => 'UPS',
            'url_template' => 'https://www.ups.com/track?loc=fr_FR&tracknum=%s',
        ],
    ];

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

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

    public function __construct(
        private readonly CartCheckoutRepository $cartCheckoutRepository,
        private readonly CartRepository $cartRepository,
        private readonly InvoiceRepository $invoiceRepository,
        private readonly PaymentMethodRepository $paymentMethodRepository,
        private readonly ContactRepository $contactRepository,
        private readonly EmailLogRepository $emailLogRepository,
        private readonly ProductRepository $productRepository,
        private readonly ProductTypeRepository $productTypeRepository,
        private readonly DeliveryMethodRepository $deliveryMethodRepository,
        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 StripePayment $stripePayment,
        private readonly TranslatorInterface $translator,
        private readonly ValidatorInterface $validator,
        private readonly CartService $cartService,
        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'],
            $filters['search'],
            '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'],
                $filters['top_clients_limit']
            ),
        ]);
    }

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

        $stats = $this->buildOrderStats(
            $carts,
            $filters['group_by'],
            $filters['paid_from'],
            $filters['paid_to'],
            $filters['top_clients_limit']
        );

        $html = $this->renderView('@CpCreationVitiCore/admin/order/stats_pdf.html.twig', [
            'filters' => $filters,
            'stats' => $stats,
        ]);

        $filePath = $this->createTempFilePath('stats_commandes_', 'pdf');
        $this->pdf->saveHtmlAsPdf($html, $filePath, true);

        return $this->createDownloadResponse(
            $filePath,
            'statistiques_commandes_'.$filters['paid_from']->format('Ymd').'_'.$filters['paid_to']->modify('-1 day')->format('Ymd').'.pdf',
            'application/pdf'
        );
    }

    #[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'],
            $filters['search'],
            '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),
                $this->resolveCheckoutStatusLabel($cart),
                $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'],
            $filters['search'],
            '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('/invoices', name: 'admin_order_invoices')]
    public function invoices(Request $request): Response
    {
        $invoiceFeatureDisabledResponse = $this->redirectIfInvoiceFeatureDisabled();
        if (null !== $invoiceFeatureDisabledResponse) {
            return $invoiceFeatureDisabledResponse;
        }

        $filters = $this->buildInvoiceFilters($request);
        $shopConfig = $this->shopConfigRepository->findOne();
        $items = $this->invoiceRepository->findAdminFiltered(
            $filters['search'],
            $filters['type'],
            $filters['date_from'],
            $filters['date_to']
        );

        return $this->render('@CpCreationVitiCore/admin/order/invoices.html.twig', [
            'items' => $items,
            'filters' => $filters,
            'invoice_prefix' => $shopConfig?->getInvoicePrefix(),
        ]);
    }

    #[Route('/invoices/download/pdf', name: 'admin_order_invoices_download_pdf')]
    public function invoicesDownloadPdf(Request $request): Response
    {
        $invoiceFeatureDisabledResponse = $this->redirectIfInvoiceFeatureDisabled();
        if (null !== $invoiceFeatureDisabledResponse) {
            return $invoiceFeatureDisabledResponse;
        }

        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é.');
        }

        $filters = $this->buildInvoiceFilters($request);
        $items = $this->invoiceRepository->findAdminFiltered(
            $filters['search'],
            $filters['type'],
            $filters['date_from'],
            $filters['date_to']
        );
        $documents = $this->buildInvoicePdfDocuments($items, $filters['type']);

        if ([] === $documents) {
            $this->addFlash('alert-warning', 'Aucune facture ou aucun avoir ne correspond aux filtres sélectionnés.');

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

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

        return $this->file(
            $filePath,
            'factures-avoirs_'.$this->buildInvoiceExportSuffix($filters).'.pdf',
            ResponseHeaderBag::DISPOSITION_ATTACHMENT
        );
    }

    #[Route('/{id}/invoice-documents-pdf', name: 'admin_order_invoice_documents_pdf')]
    public function invoiceDocumentsPdf(Cart $cart): Response
    {
        $invoiceFeatureDisabledResponse = $this->redirectIfInvoiceFeatureDisabled();
        if (null !== $invoiceFeatureDisabledResponse) {
            return $invoiceFeatureDisabledResponse;
        }

        $invoice = $cart->getInvoice();
        if (null === $invoice || null === $invoice->getNumberDebit()) {
            throw $this->createNotFoundException('Aucune facture disponible pour cette commande.');
        }

        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é.');
        }

        $documents = $this->buildInvoicePdfDocuments([$invoice], 'all');
        $filename = $this->pdf->createPdfInvoiceBatchAdmin($contact, $documents, 'fr');
        $filePath = $this->filesystem->absolutePath('order', $filename);

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

    #[Route('/{id}', name: 'admin_order_order')]
    public function html(Request $request, Cart $cart): Response
    {
        $this->cartService->hydratePricing($cart);

        $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,
            'email_logs' => $this->emailLogRepository->findRecentByCart($cart, 20),
            'payment_methods' => $this->paymentMethodRepository->findAvailableForAdminOrderQuery()->getQuery()->getResult(),
            'checkout_completion_issue' => $checkoutCompletionIssue,
            'tracking_carriers' => self::TRACKING_CARRIERS,
            'selected_tracking_carrier' => $cart->getTrackingCarrier()
                ?? $this->resolveTrackingCarrierKey($cart->getUrlDeSuiviCarrier()),
            'document_label' => $documentLabel,
            'download_document_label' => $cart->getDownloadDocumentLabelKey(),
            'invoice_feature_enabled' => $this->isInvoiceFeatureEnabled(),
            'credit_context' => $this->buildCreditContext($cart),
            'credit_summary' => $cart->getInvoice()?->getCreditDetails(),
            'can_cancel_credit' => null !== $cart->getInvoice()
                && $this->invoiceRepository->isLatestCredit($cart->getInvoice())
                && !$this->hasSuccessfulStripeRefund($cart->getInvoice()),
            'can_direct_credit_refund' => $this->stripePayment->isDirectCreditRefundEnabled($cart->getPaymentMethod()),
            'can_trigger_credit_refund' => $this->canTriggerStripeCreditRefund($cart),
            'invoice_prefix' => $this->shopConfigRepository->findOne()?->getInvoicePrefix(),
        ]);
    }

    #[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();
        $canSendOrder = 'checkout_design.document_order' === $documentLabelKey;
        $canSendProforma = !$cart->isCheckoutPayed() && 'checkout_design.document_proforma' === $documentLabelKey;
        $canSendInvoice = $cart->isCheckoutPayed() && 'checkout_design.document_invoice' === $documentLabelKey;

        if (!$canSendOrder && !$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(),
            ]);
        }

        if ($canSendOrder) {
            $this->mailer->dispatchResendOrderCustomer($cart);
            $this->addFlash('alert-success', 'Le bon de commande a été renvoyé au client.');
        } elseif ($canSendProforma) {
            $this->mailer->dispatchOrderCustomer($cart);
            $this->addFlash('alert-success', 'La facture pro forma a été envoyée au client.');
        } else {
            $this->mailer->dispatchOrderCustomer($cart);
            $this->addFlash('alert-success', 'La facture a été renvoyée au client.');
        }

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

    #[Route('/{id}/credit', name: 'admin_order_create_credit', methods: ['POST'])]
    public function createCredit(Request $request, Cart $cart): RedirectResponse
    {
        if (!$this->isInvoiceFeatureEnabled()) {
            $this->addFlash('alert-warning', 'La facturation n\'est pas active dans la configuration boutique.');

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

        if (!$cart->isCheckoutPayed()) {
            $this->addFlash('alert-danger', 'Un avoir ne peut être créé que sur une commande déjà payée.');

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

        $invoice = $cart->getInvoice();
        if (null === $invoice) {
            $this->addFlash('alert-danger', 'Aucune facture n\'est associée à cette commande.');

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

        if ($invoice->isCredit() || null !== $invoice->getNumberCredit()) {
            $this->addFlash('alert-warning', 'Un avoir a déjà été créé pour cette commande.');

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

        if (!$this->isCsrfTokenValid('create-credit-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire d\'avoir est invalide.');

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

        $creditContext = $this->buildCreditContext($cart);
        $creditAction = self::trimToNull($request->request->get('credit_action')) ?? 'partial';
        $creditDetails = 'full' === $creditAction
            ? $this->buildFullCreditDetails($cart, $creditContext)
            : $this->buildPartialCreditDetails($request, $cart, $creditContext);

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

        if (($creditDetails['totals']['total_ttc'] ?? 0) <= 0) {
            $this->addFlash('alert-warning', 'Aucun montant remboursable n\'a été calculé pour cet avoir.');

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

        $invoice->setCredit(true);
        $invoice->setCreditAt(new \DateTime());
        $invoice->setNumberCredit($this->invoiceRepository->getNextCreditNumber());

        $shouldRefundToCard = $this->stripePayment->isDirectCreditRefundEnabled($cart->getPaymentMethod())
            && (bool) $request->request->get('refund_to_card');

        if ($shouldRefundToCard) {
            $stripeRefund = $this->stripePayment->refundCreditNote($cart, $invoice, $creditDetails);
            if ([] !== $stripeRefund) {
                $creditDetails['stripe_refund'] = $stripeRefund;
                $this->addStripeRefundFlash($stripeRefund);
            }
        }

        $invoice->setCreditDetails($creditDetails);
        $this->invoiceRepository->save($invoice);
        $this->mailer->dispatchCreditCustomer($cart);

        $this->addFlash('alert-success', 'L\'avoir a été créé et envoyé au client.');

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

    #[Route('/{id}/credit/cancel', name: 'admin_order_cancel_credit', methods: ['POST'])]
    public function cancelCredit(Request $request, Cart $cart): RedirectResponse
    {
        $invoice = $cart->getInvoice();
        if (null === $invoice) {
            $this->addFlash('alert-danger', 'Aucune facture n\'est associée à cette commande.');

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

        if (!$invoice->isCredit() && null === $invoice->getNumberCredit()) {
            $this->addFlash('alert-warning', 'Aucun avoir n\'est actuellement enregistré pour cette commande.');

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

        if (!$this->invoiceRepository->isLatestCredit($invoice)) {
            $this->addFlash('alert-danger', 'Cet avoir ne peut pas être annulé car un autre avoir a été créé après lui.');

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

        if (!$this->isCsrfTokenValid('cancel-credit-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'La demande d\'annulation de l\'avoir est invalide.');

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

        if ($this->hasSuccessfulStripeRefund($invoice)) {
            $this->addFlash('alert-danger', 'Cet avoir a deja ete rembourse via Stripe et ne peut plus etre annule automatiquement.');

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

        $invoice->setCredit(false);
        $invoice->setCreditAt(null);
        $invoice->setNumberCredit(null);
        $invoice->setCreditDetails(null);
        $this->invoiceRepository->save($invoice);

        $this->addFlash('alert-success', 'L\'avoir a été annulé.');

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

    #[Route('/{id}/credit/refund-card', name: 'admin_order_credit_refund_card', methods: ['POST'])]
    public function refundCreditCard(Request $request, Cart $cart): RedirectResponse
    {
        $invoice = $cart->getInvoice();
        if (null === $invoice || !$invoice->isCredit() || null === $invoice->getNumberCredit()) {
            $this->addFlash('alert-danger', 'Aucun avoir n\'est disponible pour cette commande.');

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

        if (!$this->isCsrfTokenValid('refund-credit-card-'.$cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'La demande de remboursement carte est invalide.');

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

        if (!$this->canTriggerStripeCreditRefund($cart)) {
            $this->addFlash('alert-warning', 'Le remboursement carte n\'est pas disponible pour cet avoir.');

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

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

        $stripeRefund = $this->stripePayment->refundCreditNote($cart, $invoice, $creditDetails);
        if ([] === $stripeRefund) {
            $this->addFlash('alert-warning', 'Le remboursement carte n\'a pas pu être déclenché.');

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

        $creditDetails['stripe_refund'] = $stripeRefund;
        $invoice->setCreditDetails($creditDetails);
        $this->invoiceRepository->save($invoice);
        $this->addStripeRefundFlash($stripeRefund);

        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->dispatchPaidOrder($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'));
        if (null !== $trackingCarrier && !isset(self::TRACKING_CARRIERS[$trackingCarrier])) {
            $trackingCarrier = null;
        }

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

        $cart->markFulfilled();
        $cart->setTrackingCarrier($trackingCarrier);
        $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->dispatchDelivery($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}/withdrawal/processed', name: 'admin_order_withdrawal_processed', methods: ['POST'])]
    public function markWithdrawalProcessed(Request $request, Cart $cart): RedirectResponse
    {
        $withdrawalRequest = $cart->getWithdrawalRequest();

        if (null === $withdrawalRequest) {
            $this->addFlash('alert-warning', $this->translator->trans('withdrawal.admin.flash.not_found'));
        } elseif (!$this->isCsrfTokenValid('withdrawal-processed-' . $cart->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', $this->translator->trans('withdrawal.admin.flash.invalid_form'));
        } else {
            $withdrawalRequest->markProcessed();
            $this->em->flush();
            $this->addFlash('alert-success', $this->translator->trans('withdrawal.admin.flash.processed'));
        }

        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}/credit-pdf', name: 'admin_order_credit_pdf')]
    public function creditPdf(Cart $cart): Response
    {
        $invoice = $cart->getInvoice();
        if (null === $invoice || !$invoice->isCredit() || null === $invoice->getNumberCredit()) {
            throw $this->createNotFoundException('Aucun avoir disponible pour cette commande.');
        }

        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->createPdfCreditAdmin($contact, $cart, 'fr');
        $filePath = $this->filesystem->absolutePath('order', $filename);

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

    #[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(),
            'disableUserAutofill' => true,
        ]);
    }

    #[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');
    }

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

        $urlTemplate = self::TRACKING_CARRIERS[$carrierKey]['url_template'];
        if (null === $urlTemplate) {
            return null;
        }

        return \sprintf(
            $urlTemplate,
            rawurlencode($trackingCode)
        );
    }

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

        foreach (self::TRACKING_CARRIERS as $key => $carrier) {
            if (null === $carrier['url_template']) {
                continue;
            }

            $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 function redirectIfInvoiceFeatureDisabled(): ?RedirectResponse
    {
        if ($this->isInvoiceFeatureEnabled()) {
            return null;
        }

        $this->addFlash('alert-warning', 'La facturation n\'est pas active dans la configuration boutique.');

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

    /**
     * @return array{
     *     invoice_reference: ?string,
     *     order_total_ttc: int,
     *     shipping_paid_ttc: int,
     *     lines: list<array{
     *         item_id: string,
     *         product_name: string,
     *         type_name: ?string,
     *         unit_label: ?string,
     *         quantity: int,
     *         line_total_ht: int,
     *         line_tax: int,
     *         line_total_ttc: int
     *     }>
     * }
     */
    private function buildCreditContext(Cart $cart): array
    {
        $pricing = $cart->getPricing();
        $lines = [];

        foreach ($pricing?->lines ?? [] as $line) {
            if ($line->isPromoGift || null === $line->itemId || $line->quantity <= 0) {
                continue;
            }

            $lines[] = [
                'item_id' => $line->itemId,
                'product_name' => $line->productName ?: $line->variantFullName ?: 'Produit',
                'type_name' => $line->typeName,
                'unit_label' => $line->unitLabel,
                'quantity' => $line->quantity,
                'line_total_ht' => max(0, $line->netHT),
                'line_tax' => max(0, $line->tax),
                'line_total_ttc' => max(0, $line->netTTC),
            ];
        }

        return [
            'invoice_reference' => $this->formatDebitInvoiceReference($cart->getInvoice(), $this->shopConfigRepository->findOne()),
            'order_total_ttc' => max(0, $pricing?->totalTTC ?? 0),
            'shipping_paid_ttc' => $this->computeShippingPaidTTC($cart),
            'lines' => $lines,
        ];
    }

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

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

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

    /**
     * @return array{
     *     search: ?string,
     *     status: string,
     *     payment_method_id: ?string,
     *     created_from: ?\DateTimeImmutable,
     *     created_to: ?\DateTimeImmutable,
     *     search_input: ?string,
     *     created_from_input: ?string,
     *     created_to_input: ?string,
     *     query_parameters: array<string, string>
     * }
     */
    private function buildOrderFilters(Request $request): array
    {
        $search = self::trimToNull($request->query->get('search'));
        $status = self::trimToNull($request->query->get('status')) ?? 'all';
        if (!\in_array($status, ['all', 'pending', 'abandoned', 'failed', 'canceled', 'offline_pending', 'paid', 'delivered', 'paid_not_delivered'], 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 [
            'search' => $search,
            'status' => $status,
            'payment_method_id' => $paymentMethodId,
            'created_from' => $createdFrom,
            'created_to' => $createdTo,
            'search_input' => $search,
            '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([
                'search' => $search,
                '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),
        ];
    }

    /**
     * @return array{
     *     search: ?string,
     *     type: string,
     *     date_from: ?\DateTimeImmutable,
     *     date_to: ?\DateTimeImmutable,
     *     search_input: ?string,
     *     date_from_input: ?string,
     *     date_to_input: ?string,
     *     query_parameters: array<string, string>
     * }
     */
    private function buildInvoiceFilters(Request $request): array
    {
        $search = self::trimToNull($request->query->get('search'));
        $type = self::trimToNull($request->query->get('type')) ?? 'all';
        $dateFromInput = self::trimToNull($request->query->get('date_from'));
        $dateToInput = self::trimToNull($request->query->get('date_to'));

        if (!\in_array($type, ['all', 'invoice', 'credit'], true)) {
            $type = 'all';
        }

        $dateFrom = $this->parseDateFilter($dateFromInput);
        $dateTo = $this->parseDateFilter($dateToInput)?->modify('+1 day');

        return [
            'search' => $search,
            'type' => $type,
            'date_from' => $dateFrom,
            'date_to' => $dateTo,
            'search_input' => $search,
            'date_from_input' => $dateFrom?->format('Y-m-d'),
            'date_to_input' => null !== $dateTo ? $dateTo->modify('-1 day')->format('Y-m-d') : null,
            'query_parameters' => array_filter([
                'search' => $search,
                'type' => $type,
                'date_from' => $dateFrom?->format('Y-m-d'),
                'date_to' => null !== $dateTo ? $dateTo->modify('-1 day')->format('Y-m-d') : null,
            ], static fn (?string $value): bool => null !== $value && '' !== $value),
        ];
    }

    /**
     * @param array{
     *     invoice_reference: ?string,
     *     order_total_ttc: int,
     *     shipping_paid_ttc: int,
     *     lines: list<array{
     *         item_id: string,
     *         product_name: string,
     *         type_name: ?string,
     *         unit_label: ?string,
     *         quantity: int,
     *         line_total_ht: int,
     *         line_tax: int,
     *         line_total_ttc: int
     *     }>
     * } $creditContext
     *
     * @return array<string, mixed>
     */
    private function buildFullCreditDetails(Cart $cart, array $creditContext): array
    {
        $items = [];
        $itemsHT = 0;
        $itemsTax = 0;
        $itemsTotal = 0;

        foreach ($creditContext['lines'] as $line) {
            $amountHT = max(0, $line['line_total_ht']);
            $amountTax = max(0, $line['line_tax']);
            $amountTTC = max(0, $line['line_total_ttc']);
            $items[] = [
                'item_id' => $line['item_id'],
                'product_name' => $line['product_name'],
                'type_name' => $line['type_name'],
                'unit_label' => $line['unit_label'],
                'quantity_ordered' => $line['quantity'],
                'quantity_refunded' => $line['quantity'],
                'amount_ht' => $amountHT,
                'amount_tax' => $amountTax,
                'amount_ttc' => $amountTTC,
                'tax_rate_label' => $this->formatCreditTaxRateLabel($amountHT, $amountTax),
            ];
            $itemsHT += $amountHT;
            $itemsTax += $amountTax;
            $itemsTotal += $amountTTC;
        }

        $shipping = $this->buildCreditShippingPayload(
            'full',
            100,
            $creditContext['shipping_paid_ttc'],
            $cart
        );

        return $this->buildCreditDetailsPayload(
            $cart,
            'full',
            $creditContext['invoice_reference'],
            $items,
            $shipping,
            $itemsHT,
            $itemsTax,
            $itemsTotal
        );
    }

    /**
     * @param array{
     *     invoice_reference: ?string,
     *     order_total_ttc: int,
     *     shipping_paid_ttc: int,
     *     lines: list<array{
     *         item_id: string,
     *         product_name: string,
     *         type_name: ?string,
     *         unit_label: ?string,
     *         quantity: int,
     *         line_total_ht: int,
     *         line_tax: int,
     *         line_total_ttc: int
     *     }>
     * } $creditContext
     */
    private function buildPartialCreditDetails(Request $request, Cart $cart, array $creditContext): ?array
    {
        $requestData = $request->request->all();
        $rawQuantities = \is_array($requestData['credit_quantities'] ?? null) ? $requestData['credit_quantities'] : [];
        $items = [];
        $itemsHT = 0;
        $itemsTax = 0;
        $itemsTotal = 0;

        foreach ($creditContext['lines'] as $line) {
            $requestedQuantity = $rawQuantities[$line['item_id']] ?? 0;
            $quantityRefunded = max(0, min($line['quantity'], (int) $requestedQuantity));

            if ($quantityRefunded <= 0) {
                continue;
            }

            $amountHT = $this->computeProratedCreditAmount($line['line_total_ht'], $quantityRefunded, $line['quantity']);
            $amountTax = $this->computeProratedCreditAmount($line['line_tax'], $quantityRefunded, $line['quantity']);
            $amountTTC = (int) round(
                ($line['line_total_ttc'] * $quantityRefunded) / max(1, $line['quantity']),
                0,
                \PHP_ROUND_HALF_UP
            );

            if ($amountTTC <= 0) {
                continue;
            }

            $items[] = [
                'item_id' => $line['item_id'],
                'product_name' => $line['product_name'],
                'type_name' => $line['type_name'],
                'unit_label' => $line['unit_label'],
                'quantity_ordered' => $line['quantity'],
                'quantity_refunded' => $quantityRefunded,
                'amount_ht' => $amountHT,
                'amount_tax' => $amountTax,
                'amount_ttc' => $amountTTC,
                'tax_rate_label' => $this->formatCreditTaxRateLabel($amountHT, $amountTax),
            ];
            $itemsHT += $amountHT;
            $itemsTax += $amountTax;
            $itemsTotal += $amountTTC;
        }

        $shippingMode = self::trimToNull($request->request->get('shipping_refund_mode')) ?? 'none';
        if (!\in_array($shippingMode, ['none', 'percent', 'full'], true)) {
            $shippingMode = 'none';
        }

        $shippingPercent = null;
        $shippingAmountTTC = 0;

        if ('full' === $shippingMode) {
            $shippingPercent = 100;
            $shippingAmountTTC = $creditContext['shipping_paid_ttc'];
        } elseif ('percent' === $shippingMode) {
            $shippingPercent = max(0, min(100, (int) $request->request->get('shipping_refund_percent', 0)));
            $shippingAmountTTC = (int) round(
                ($creditContext['shipping_paid_ttc'] * $shippingPercent) / 100,
                0,
                \PHP_ROUND_HALF_UP
            );
        }

        $shipping = $this->buildCreditShippingPayload(
            $shippingMode,
            $shippingPercent,
            $shippingAmountTTC,
            $cart
        );

        if ([] === $items && $shippingAmountTTC <= 0) {
            $this->addFlash('alert-warning', 'Sélectionnez au moins un produit, une quantité ou un remboursement sur les frais de port.');

            return null;
        }

        return $this->buildCreditDetailsPayload(
            $cart,
            'partial',
            $creditContext['invoice_reference'],
            $items,
            $shipping,
            $itemsHT,
            $itemsTax,
            $itemsTotal
        );
    }

    /**
     * @param list<array{
     *     item_id: string,
     *     product_name: string,
     *     type_name: ?string,
     *     unit_label: ?string,
     *     quantity_ordered: int,
     *     quantity_refunded: int,
     *     amount_ht: int,
     *     amount_tax: int,
     *     amount_ttc: int
     * }> $items
     *
     * @return array<string, mixed>
     */
    private function buildCreditDetailsPayload(
        Cart $cart,
        string $mode,
        ?string $invoiceReference,
        array $items,
        array $shipping,
        int $itemsHT,
        int $itemsTax,
        int $itemsTotal,
    ): array {
        $shippingHT = (int) ($shipping['amount_ht'] ?? 0);
        $shippingTax = (int) ($shipping['amount_tax'] ?? 0);
        $totalTTC = $itemsTotal + (int) ($shipping['amount_ttc'] ?? 0);
        $totalHT = $itemsHT + $shippingHT;
        $totalTax = $itemsTax + $shippingTax;

        return [
            'mode' => $mode,
            'order_reference' => $cart->getToken(),
            'invoice_reference' => $invoiceReference,
            'created_at' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
            'items' => $items,
            'shipping' => $shipping,
            'totals' => [
                'items_ht' => $itemsHT,
                'items_tax' => $itemsTax,
                'items_ttc' => $itemsTotal,
                'shipping_ht' => $shippingHT,
                'shipping_tax' => $shippingTax,
                'shipping_ttc' => (int) ($shipping['amount_ttc'] ?? 0),
                'total_ht' => $totalHT,
                'total_tax' => $totalTax,
                'total_ttc' => $totalTTC,
            ],
        ];
    }

    private function computeProratedCreditAmount(int $amount, int $quantityRefunded, int $quantityOrdered): int
    {
        return (int) round(
            ($amount * $quantityRefunded) / max(1, $quantityOrdered),
            0,
            \PHP_ROUND_HALF_UP
        );
    }

    /**
     * @return array<string, mixed>
     */
    private function buildCreditShippingPayload(string $mode, ?int $percent, int $amountTTC, Cart $cart): array
    {
        $shippingTaxRate = max(0.0, (float) ($cart->getCartShipping()?->taxRate ?? $cart->getPricing()?->taxRate ?? 0.0));
        $amountHT = $this->computeHTFromTTC(max(0, $amountTTC), $shippingTaxRate);

        return [
            'mode' => $mode,
            'percent' => $percent,
            'base_ttc' => $this->computeShippingPaidTTC($cart),
            'amount_ht' => $amountHT,
            'amount_tax' => max(0, $amountTTC - $amountHT),
            'amount_ttc' => max(0, $amountTTC),
            'tax_rate_label' => $this->formatCreditTaxRateLabel(
                $amountHT,
                max(0, $amountTTC - $amountHT),
                $shippingTaxRate
            ),
        ];
    }

    private function computeHTFromTTC(int $amountTTC, float $taxRate): int
    {
        if ($amountTTC <= 0 || $taxRate <= 0.0) {
            return max(0, $amountTTC);
        }

        return (int) round($amountTTC / (1 + $taxRate), 0, \PHP_ROUND_HALF_UP);
    }

    private function formatCreditTaxRateLabel(int $amountHT, int $amountTax, ?float $taxRate = null): string
    {
        if (null !== $taxRate && $taxRate > 0.0) {
            return $this->formatTaxRatePercentLabel($taxRate);
        }

        if ($amountHT <= 0 || $amountTax <= 0) {
            return '0 %';
        }

        return number_format(($amountTax / $amountHT) * 100, 1, ',', ' ').' %';
    }

    private function formatTaxRatePercentLabel(float $rate): string
    {
        if ($rate <= 0.0) {
            return '0 %';
        }

        $percent = $rate * 100;
        $formatted = floor($percent) === $percent
            ? number_format($percent, 0, ',', ' ')
            : number_format($percent, 1, ',', ' ');

        return $formatted.' %';
    }

    private function computeShippingPaidTTC(Cart $cart): int
    {
        $shippingTTC = max(0, (int) ($cart->getCartShipping()?->amountTTC ?? 0));
        $shippingDiscountTTC = 0;

        foreach ($cart->getPromos() as $promo) {
            $shippingDiscountTTC += max(0, $promo->discountShippingTTC);
        }

        return max(0, $shippingTTC - $shippingDiscountTTC);
    }

    private function formatDebitInvoiceReference(?object $invoice, ?\CpCreation\VitiCore\Shop\Model\ShopConfig $shopConfig = null): ?string
    {
        if (!$invoice instanceof \CpCreation\VitiCore\Cart\Model\Invoice || null === $invoice->getNumberDebit() || null === $invoice->getYear()) {
            return null;
        }

        if (null !== $shopConfig) {
            return $shopConfig->formatInvoiceReference($invoice->getYear(), $invoice->getNumberDebit());
        }

        return sprintf('%s-%05d', (string) $invoice->getYear(), $invoice->getNumberDebit());
    }

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

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

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

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

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

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

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

        $date = \DateTimeImmutable::createFromFormat('!o-\WW-N', $value.'-1');

        return $date instanceof \DateTimeImmutable ? $date->setTime(0, 0) : null;
    }

    private function parseYearFilter(?string $value): ?int
    {
        if (null === $value || !preg_match('/^\d{4}$/', $value)) {
            return null;
        }

        $year = (int) $value;

        return $year >= 2000 && $year <= 2100 ? $year : null;
    }

    private function hasSuccessfulStripeRefund(?\CpCreation\VitiCore\Cart\Model\Invoice $invoice): bool
    {
        if (!$invoice instanceof \CpCreation\VitiCore\Cart\Model\Invoice) {
            return false;
        }

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

        $stripeRefund = $creditDetails['stripe_refund'] ?? null;
        if (!\is_array($stripeRefund)) {
            return false;
        }

        return \in_array($stripeRefund['status'] ?? null, ['succeeded', 'pending', 'requires_action'], true);
    }

    private function canTriggerStripeCreditRefund(Cart $cart): bool
    {
        $invoice = $cart->getInvoice();

        return null !== $invoice
            && $invoice->isCredit()
            && null !== $invoice->getNumberCredit()
            && $this->stripePayment->isDirectCreditRefundEnabled($cart->getPaymentMethod())
            && !$this->hasSuccessfulStripeRefund($invoice);
    }

    /**
     * @param array<string, mixed> $stripeRefund
     */
    private function addStripeRefundFlash(array $stripeRefund): void
    {
        $status = $stripeRefund['status'] ?? null;

        if ('succeeded' === $status) {
            $this->addFlash('alert-success', 'Le remboursement Stripe de l\'avoir a ete effectue automatiquement.');

            return;
        }

        if ('skipped' === $status && !($stripeRefund['attempted'] ?? false)) {
            return;
        }

        $message = $stripeRefund['message'] ?? 'Le remboursement Stripe n\'a pas pu etre termine automatiquement.';
        if ('pending' === $status) {
            $this->addFlash('alert-warning', 'Le remboursement Stripe est en attente: '.$message);

            return;
        }

        if ('failed' === $status) {
            $this->addFlash('alert-warning', 'L\'avoir a ete cree, mais le remboursement Stripe a echoue: '.$message);
        }
    }

    /**
     * @return array{
     *     payment_method_id: ?string,
     *     paid_from: \DateTimeImmutable,
     *     paid_to: \DateTimeImmutable,
     *     paid_from_input: string,
     *     paid_to_input: string,
     *     group_by: string,
     *     top_clients_limit: int,
     *     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_DETAIL_GROUP_BY, true)) {
            $groupBy = 'month';
        }

        $topClientsLimit = (int) ($request->query->get('top_clients_limit') ?? 10);
        if (!\in_array($topClientsLimit, [10, 20, 30, 50], true)) {
            $topClientsLimit = 10;
        }

        $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,
            'top_clients_limit' => $topClientsLimit,
            '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,
                'top_clients_limit' => (string) $topClientsLimit,
            ], static fn (?string $value): bool => null !== $value && '' !== $value),
        ];
    }

    /**
     * @param list<Cart> $carts
     *
     * @return array<string, mixed>
     */
    private function buildOrderStats(
        array $carts,
        string $groupBy,
        \DateTimeImmutable $paidFrom,
        \DateTimeImmutable $paidTo,
        int $topClientsLimit = 10,
    ): array {
        $summary = [
            'order_count' => 0,
            'revenue_ttc' => 0,
            'products_ttc' => 0,
            'shipping_ttc' => 0,
            'average_basket_ttc' => 0,
            'average_equivalence' => 0.0,
            'discount_ttc' => 0,
            'degressive_ttc' => 0,
            'promo_items_ttc' => 0,
            'promo_shipping_ttc' => 0,
            'credit_items_ttc' => 0,
            'credit_shipping_ttc' => 0,
            'credit_total_ttc' => 0,
            'credited_order_count' => 0,
            'transaction_fees_ttc' => 0,
            'transaction_fee_order_count' => 0,
            'transaction_fee_missing_order_count' => 0,
            'net_revenue_ttc' => 0,
            'average_shipping_ttc' => 0,
            'unique_customer_count' => 0,
            'repeat_customer_count' => 0,
            'shipping_rate_percent' => 0.0,
            'credit_rate_percent' => 0.0,
            'discount_rate_percent' => 0.0,
        ];
        $optionSummary = [
            'option_revenue_ttc' => 0,
            'option_quantity' => 0,
            'option_order_count' => 0,
            'products_with_options_count' => 0,
            'attach_rate_percent' => 0.0,
        ];
        $equivalenceTotal = 0.0;
        $totalSoldQuantity = 0;
        $catalogStats = $this->buildCatalogStatsReferences();
        $soldProducts = $catalogStats['sold_products'];
        $productTypes = $catalogStats['product_types'];
        $catalogProductNames = $catalogStats['product_names_by_id'];
        $catalogTypeNames = $catalogStats['type_names_by_id'];
        $catalogUnitsByPair = $catalogStats['unit_labels_by_pair'];
        $paymentMethods = [];
        $deliveryMethods = $this->buildDeliveryMethodStatsReferences();
        $customerOrders = [];
        $clientStats = [];
        $optionOrderReferences = [];
        $detailFrom = $paidFrom;
        $detailTo = $paidTo;
        $chartBuckets = $this->createStatsBuckets($detailFrom, $detailTo, $groupBy);

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

            $pricing = $cart->getPricing();
            $totalTTC = $pricing?->totalTTC ?? 0;
            $shippingPaidTTC = $this->computeShippingPaidTTC($cart);
            $productsPaidTTC = max(0, $totalTTC - $shippingPaidTTC);
            $transactionFee = $this->resolveTransactionFeeTTC($cart);
            $paymentMethodLabel = $this->resolvePaymentMethodLabel($cart);
            $paymentMethodKey = $this->resolvePaymentMethodStatsKey($cart);
            $deliveryMethodData = $this->resolveDeliveryMethodStatsData($cart);
            $deliveryMethodKey = $deliveryMethodData['key'];
            $creditStats = $this->extractCreditStats($cart);
            $customerEmail = mb_strtolower(mb_trim((string) ($cart->getEmail() ?? '')));
            $orderReference = $cart->getToken() ?: $cart->getId()->toString();

            ++$summary['order_count'];
            $summary['revenue_ttc'] += $totalTTC;
            $summary['products_ttc'] += $productsPaidTTC;
            $summary['shipping_ttc'] += $shippingPaidTTC;
            $summary['discount_ttc'] += $pricing?->discountTTC ?? 0;
            $summary['degressive_ttc'] += $pricing?->degressiveTTC ?? 0;
            $summary['credit_items_ttc'] += $creditStats['items_ttc'];
            $summary['credit_shipping_ttc'] += $creditStats['shipping_ttc'];
            $summary['credit_total_ttc'] += $creditStats['total_ttc'];
            $equivalenceTotal += (float) $cart->getQuantityAllAbsolute();

            if ($creditStats['active']) {
                ++$summary['credited_order_count'];
            }

            if ($transactionFee['computed']) {
                $summary['transaction_fees_ttc'] += $transactionFee['amount_ttc'];
                ++$summary['transaction_fee_order_count'];
            } else {
                ++$summary['transaction_fee_missing_order_count'];
            }

            if ('' !== $customerEmail) {
                $customerOrders[$customerEmail] = ($customerOrders[$customerEmail] ?? 0) + 1;
                if (!isset($clientStats[$customerEmail])) {
                    $clientStats[$customerEmail] = [
                        'email' => $customerEmail,
                        'firstname' => $this->normalizeStatsLabel($cart->getFirstname(), '-'),
                        'lastname' => $this->normalizeStatsLabel($cart->getLastname(), '-'),
                        'order_count' => 0,
                        'revenue_ttc' => 0,
                        'average_basket_ttc' => 0,
                        'last_order_at' => null,
                    ];
                }

                ++$clientStats[$customerEmail]['order_count'];
                $clientStats[$customerEmail]['revenue_ttc'] += $totalTTC;

                $paidAtTimestamp = $paidAt->getTimestamp();
                $currentLastOrderAt = $clientStats[$customerEmail]['last_order_at'];
                if (
                    null === $currentLastOrderAt
                    || $paidAtTimestamp >= $currentLastOrderAt->getTimestamp()
                ) {
                    $clientStats[$customerEmail]['last_order_at'] = \DateTimeImmutable::createFromInterface($paidAt);
                    $clientStats[$customerEmail]['firstname'] = $this->normalizeStatsLabel($cart->getFirstname(), '-');
                    $clientStats[$customerEmail]['lastname'] = $this->normalizeStatsLabel($cart->getLastname(), '-');
                }
            }

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

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

            if (!isset($paymentMethods[$paymentMethodKey])) {
                $paymentMethods[$paymentMethodKey] = [
                    'name' => $paymentMethodLabel,
                    'order_count' => 0,
                    'revenue_ttc' => 0,
                    'transaction_fees_ttc' => 0,
                    'transaction_fee_order_count' => 0,
                    'missing_transaction_fee_order_count' => 0,
                ];
            }

            ++$paymentMethods[$paymentMethodKey]['order_count'];
            $paymentMethods[$paymentMethodKey]['revenue_ttc'] += $totalTTC;

            if ($transactionFee['computed']) {
                $paymentMethods[$paymentMethodKey]['transaction_fees_ttc'] += $transactionFee['amount_ttc'];
                ++$paymentMethods[$paymentMethodKey]['transaction_fee_order_count'];
            } else {
                ++$paymentMethods[$paymentMethodKey]['missing_transaction_fee_order_count'];
            }

            if (!isset($deliveryMethods[$deliveryMethodKey])) {
                $deliveryMethods[$deliveryMethodKey] = [
                    'name' => $deliveryMethodData['name'],
                    'type' => $deliveryMethodData['type'],
                    'order_count' => 0,
                    'revenue_ttc' => 0,
                    'products_ttc' => 0,
                    'shipping_ttc' => 0,
                ];
            }

            ++$deliveryMethods[$deliveryMethodKey]['order_count'];
            $deliveryMethods[$deliveryMethodKey]['revenue_ttc'] += $totalTTC;
            $deliveryMethods[$deliveryMethodKey]['products_ttc'] += $productsPaidTTC;
            $deliveryMethods[$deliveryMethodKey]['shipping_ttc'] += $shippingPaidTTC;

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

                $productName = $catalogProductNames[$line->productId] ?? $line->productName ?? $line->variantFullName ?? 'Produit inconnu';
                $typeName = $catalogTypeNames[$line->typeId] ?? $this->normalizeStatsLabel($line->typeName, 'Sans type');
                $catalogUnitPairKey = $this->buildTypeUnitPairKey($line->productId, $line->typeId);
                $unitLabel = $catalogUnitsByPair[$catalogUnitPairKey]
                    ?? $this->normalizeStatsLabel($line->unitLabel, '-');
                $productKey = $this->buildStatsEntryKey(
                    $line->productId,
                    $line->typeId,
                    $productName,
                    $typeName,
                    $unitLabel
                );
                $productLabel = $productName;

                if ('-' !== $unitLabel) {
                    $productLabel .= ' ('.$unitLabel.')';
                }

                if (!isset($soldProducts[$productKey])) {
                    $soldProducts[$productKey] = [
                        'name' => $productLabel,
                        'product_name' => $productName,
                        'type_name' => $typeName,
                        'unit_label' => $unitLabel,
                        'quantity' => 0,
                        'revenue_ttc' => 0,
                        'order_count' => 0,
                        'average_price_ttc' => 0,
                        'share_revenue_percent' => 0.0,
                        'share_quantity_percent' => 0.0,
                        'is_sold' => false,
                        'option_quantity' => 0,
                        'option_revenue_ttc' => 0,
                        'option_order_count' => 0,
                        'option_attach_rate_percent' => 0.0,
                        'options' => [],
                        '_option_orders' => [],
                        '_orders' => [],
                    ];
                }

                $soldProducts[$productKey]['quantity'] += $line->quantity;
                $soldProducts[$productKey]['revenue_ttc'] += $line->netTTC;
                $soldProducts[$productKey]['is_sold'] = true;
                $soldProducts[$productKey]['_orders'][$orderReference] = true;
                $totalSoldQuantity += $line->quantity;

                if ($this->hasLineOption($line)) {
                    $optionLabel = $this->normalizeStatsLabel($line->optionName, 'Option');
                    $soldProducts[$productKey]['option_quantity'] += $line->quantity;
                    $soldProducts[$productKey]['option_revenue_ttc'] += max(0, $line->optionTotalTTC);
                    if (!isset($soldProducts[$productKey]['options'][$optionLabel])) {
                        $soldProducts[$productKey]['options'][$optionLabel] = [
                            'name' => $optionLabel,
                            'quantity' => 0,
                            'revenue_ttc' => 0,
                            '_orders' => [],
                        ];
                    }
                    $soldProducts[$productKey]['options'][$optionLabel]['quantity'] += $line->quantity;
                    $soldProducts[$productKey]['options'][$optionLabel]['revenue_ttc'] += max(0, $line->optionTotalTTC);
                    $soldProducts[$productKey]['options'][$optionLabel]['_orders'][$orderReference] = true;
                    $soldProducts[$productKey]['_option_orders'][$orderReference] = true;
                    $optionSummary['option_quantity'] += $line->quantity;
                    $optionSummary['option_revenue_ttc'] += max(0, $line->optionTotalTTC);
                    $optionOrderReferences[$orderReference] = true;
                }

                $typeKey = null !== $line->typeId && '' !== $line->typeId ? $line->typeId : 'type:'.mb_strtolower($typeName);
                if (!isset($productTypes[$typeKey])) {
                    $productTypes[$typeKey] = [
                        'name' => $typeName,
                        'quantity' => 0,
                        'revenue_ttc' => 0,
                        'order_count' => 0,
                        'average_price_ttc' => 0,
                        'share_revenue_percent' => 0.0,
                        'is_sold' => false,
                        '_orders' => [],
                    ];
                }

                $productTypes[$typeKey]['quantity'] += $line->quantity;
                $productTypes[$typeKey]['revenue_ttc'] += $line->netTTC;
                $productTypes[$typeKey]['is_sold'] = true;
                $productTypes[$typeKey]['_orders'][$orderReference] = true;
            }
        }

        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);
            $summary['average_shipping_ttc'] = (int) round($summary['shipping_ttc'] / $summary['order_count']);
        }

        $summary['net_revenue_ttc'] = $summary['revenue_ttc'] - $summary['credit_total_ttc'] - $summary['transaction_fees_ttc'];
        $summary['unique_customer_count'] = \count($customerOrders);
        $summary['repeat_customer_count'] = \count(array_filter(
            $customerOrders,
            static fn (int $count): bool => $count > 1
        ));
        $summary['shipping_rate_percent'] = $this->computeStatsPercent($summary['shipping_ttc'], $summary['revenue_ttc']);
        $summary['credit_rate_percent'] = $this->computeStatsPercent($summary['credit_total_ttc'], $summary['revenue_ttc']);
        $summary['discount_rate_percent'] = $this->computeStatsPercent(
            $summary['discount_ttc'] + $summary['promo_items_ttc'] + $summary['promo_shipping_ttc'],
            $summary['revenue_ttc']
        );
        $optionSummary['option_order_count'] = \count($optionOrderReferences);
        $optionSummary['attach_rate_percent'] = $this->computeStatsPercent($optionSummary['option_quantity'], $totalSoldQuantity);

        foreach ($soldProducts as &$soldProduct) {
            $soldProduct['order_count'] = \count($soldProduct['_orders']);
            $soldProduct['average_price_ttc'] = $soldProduct['quantity'] > 0
                ? (int) round($soldProduct['revenue_ttc'] / $soldProduct['quantity'])
                : 0;
            $soldProduct['share_revenue_percent'] = $this->computeStatsPercent(
                $soldProduct['revenue_ttc'],
                $summary['products_ttc']
            );
            $soldProduct['share_quantity_percent'] = $this->computeStatsPercent(
                $soldProduct['quantity'],
                $totalSoldQuantity
            );
            if ([] !== $soldProduct['options']) {
                foreach ($soldProduct['options'] as &$option) {
                    $option['order_count'] = \count($option['_orders']);
                    unset($option['_orders']);
                }
                unset($option);

                usort($soldProduct['options'], static function (array $left, array $right): int {
                    $revenueComparison = $right['revenue_ttc'] <=> $left['revenue_ttc'];
                    if (0 !== $revenueComparison) {
                        return $revenueComparison;
                    }

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

                    return strcmp((string) $left['name'], (string) $right['name']);
                });
            }
            $soldProduct['option_order_count'] = \count($soldProduct['_option_orders']);
            $soldProduct['option_attach_rate_percent'] = $this->computeStatsPercent(
                $soldProduct['option_quantity'],
                $soldProduct['quantity']
            );
            unset($soldProduct['_option_orders']);
            unset($soldProduct['_orders']);
        }
        unset($soldProduct);

        $optionSummary['products_with_options_count'] = \count(array_filter(
            $soldProducts,
            static fn (array $product): bool => (int) ($product['option_quantity'] ?? 0) > 0
        ));

        foreach ($productTypes as &$productType) {
            $productType['order_count'] = \count($productType['_orders']);
            $productType['average_price_ttc'] = $productType['quantity'] > 0
                ? (int) round($productType['revenue_ttc'] / $productType['quantity'])
                : 0;
            $productType['share_revenue_percent'] = $this->computeStatsPercent(
                $productType['revenue_ttc'],
                $summary['products_ttc']
            );
            unset($productType['_orders']);
        }
        unset($productType);

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

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

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

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

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

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

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

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

        usort($deliveryMethods, static function (array $left, array $right): int {
            $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;
        }

        $timelineRows = [];
        foreach ($chartBuckets as $bucket) {
            $timelineRows[] = [
                'label' => $bucket['label'],
                'sort_value' => $bucket['sort_value'],
                'orders' => $bucket['orders'],
                'revenue_ttc' => $bucket['revenue_ttc'],
                'average_basket_ttc' => $bucket['orders'] > 0
                    ? (int) round($bucket['revenue_ttc'] / $bucket['orders'])
                    : 0,
            ];
        }

        $revenueBreakdown = [
            [
                'label' => 'Produits TTC factures',
                'amount_ttc' => $summary['products_ttc'],
                'context' => 'Montant net historise sur les lignes produits.',
                'tone' => 'positive',
            ],
            [
                'label' => 'Frais de port TTC factures',
                'amount_ttc' => $summary['shipping_ttc'],
                'context' => 'Transport et retrait factures.',
                'tone' => 'positive',
            ],
            [
                'label' => 'Chiffre d\'affaire brut encaisse',
                'amount_ttc' => $summary['revenue_ttc'],
                'context' => 'Avant avoirs et frais de transaction.',
                'tone' => 'total',
            ],
            [
                'label' => 'Avoirs produits',
                'amount_ttc' => $summary['credit_items_ttc'],
                'context' => 'Montants produits rembourses sur les commandes filtrees.',
                'tone' => 'negative',
            ],
            [
                'label' => 'Avoirs frais de port',
                'amount_ttc' => $summary['credit_shipping_ttc'],
                'context' => 'Part transport remboursee.',
                'tone' => 'negative',
            ],
            [
                'label' => 'Total avoirs',
                'amount_ttc' => $summary['credit_total_ttc'],
                'context' => $summary['credited_order_count'].' commande(s) concernee(s).',
                'tone' => 'negative',
            ],
            [
                'label' => 'Frais de transaction',
                'amount_ttc' => $summary['transaction_fees_ttc'],
                'context' => $summary['transaction_fee_order_count'].' commande(s) calculee(s).',
                'tone' => 'negative',
            ],
            [
                'label' => 'Chiffre d\'affaire net',
                'amount_ttc' => $summary['net_revenue_ttc'],
                'context' => 'Brut encaisse - avoirs - frais de transaction.',
                'tone' => 'total',
            ],
        ];

        $optionProducts = array_values(array_filter(
            $soldProducts,
            static fn (array $product): bool => (int) ($product['quantity'] ?? 0) > 0
        ));

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

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

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

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

        foreach ($clientStats as &$clientStat) {
            $clientStat['average_basket_ttc'] = $clientStat['order_count'] > 0
                ? (int) round($clientStat['revenue_ttc'] / $clientStat['order_count'])
                : 0;
        }
        unset($clientStat);

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

            $orderCountComparison = ((int) $right['order_count']) <=> ((int) $left['order_count']);
            if (0 !== $orderCountComparison) {
                return $orderCountComparison;
            }

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

        $topClients = array_slice(array_values($clientStats), 0, $topClientsLimit);
        $topClientsTotalTTC = array_reduce(
            $topClients,
            static fn (int $carry, array $client): int => $carry + (int) ($client['revenue_ttc'] ?? 0),
            0
        );

        return [
            'summary' => $summary,
            'option_summary' => $optionSummary,
            'chart' => $chart,
            'timeline' => [
                'rows' => $timelineRows,
                'group_by' => $groupBy,
                'from' => $detailFrom,
                'to' => $detailTo,
            ],
            'revenue_breakdown' => $revenueBreakdown,
            'sold_products' => array_values($soldProducts),
            'option_products' => $optionProducts,
            'top_products' => array_slice(array_values($soldProducts), 0, 10),
            'top_clients' => $topClients,
            'top_clients_total_ttc' => $topClientsTotalTTC,
            'product_types' => array_values($productTypes),
            'radar' => $this->buildStatsRadarData($soldProducts),
            'payment_methods' => array_values($paymentMethods),
            'delivery_methods' => array_values($deliveryMethods),
        ];
    }

    /**
     * @return array{
     *     sold_products: array<string, array<string, mixed>>,
     *     product_types: array<string, array<string, mixed>>,
     *     product_names_by_id: array<string, string>,
     *     type_names_by_id: array<string, string>,
     *     unit_labels_by_pair: array<string, string>
     * }
     */
    private function buildCatalogStatsReferences(): array
    {
        $soldProducts = [];
        $productTypes = [];
        $productNamesById = [];
        $typeNamesById = [];
        $unitLabelsByPair = [];

        foreach ($this->productRepository->findByAdminFilters(['shop' => true]) as $product) {
            if (!$product instanceof \CpCreation\VitiCore\Product\Model\Product || $product->isDisabled()) {
                continue;
            }

            $productId = $product->getId()->toString();
            $productName = $this->resolveProductNameFr($product);
            $productNamesById[$productId] = $productName;

            foreach ($product->getVariants() as $variant) {
                if (
                    !$variant instanceof \CpCreation\VitiCore\Product\Model\ProductVariant
                    || $variant->isDeleted()
                    || $variant->isUnPublished()
                ) {
                    continue;
                }

                $type = $variant->getType();
                if (!$type instanceof \CpCreation\VitiCore\Product\Model\ProductType) {
                    continue;
                }

                $typeId = $type->getId()->toString();
                $typeName = $this->resolveProductTypeNameFr($type);
                $unitLabel = $this->buildProductTypeUnitLabel($type);
                $typePairKey = $this->buildTypeUnitPairKey($productId, $typeId);
                $productKey = $this->buildStatsEntryKey($productId, $typeId, $productName, $typeName, $unitLabel);

                $typeNamesById[$typeId] = $typeName;
                $unitLabelsByPair[$typePairKey] = $unitLabel;

                if (!isset($soldProducts[$productKey])) {
                    $productLabel = $productName;
                    if ('-' !== $unitLabel) {
                        $productLabel .= ' ('.$unitLabel.')';
                    }

                    $soldProducts[$productKey] = [
                        'name' => $productLabel,
                        'product_name' => $productName,
                        'type_name' => $typeName,
                        'unit_label' => $unitLabel,
                        'quantity' => 0,
                        'revenue_ttc' => 0,
                        'order_count' => 0,
                        'average_price_ttc' => 0,
                        'share_revenue_percent' => 0.0,
                        'share_quantity_percent' => 0.0,
                        'is_sold' => false,
                        'option_quantity' => 0,
                        'option_revenue_ttc' => 0,
                        'option_order_count' => 0,
                        'option_attach_rate_percent' => 0.0,
                        'options' => [],
                        '_option_orders' => [],
                        '_orders' => [],
                    ];
                }

                if (!isset($productTypes[$typeId])) {
                    $productTypes[$typeId] = [
                        'name' => $typeName,
                        'quantity' => 0,
                        'revenue_ttc' => 0,
                        'order_count' => 0,
                        'average_price_ttc' => 0,
                        'share_revenue_percent' => 0.0,
                        'is_sold' => false,
                        '_orders' => [],
                    ];
                }
            }
        }

        foreach ($this->productTypeRepository->findBy([], ['position' => 'ASC']) as $type) {
            if (!$type instanceof \CpCreation\VitiCore\Product\Model\ProductType) {
                continue;
            }

            $typeId = $type->getId()->toString();
            $typeName = $this->resolveProductTypeNameFr($type);
            $typeNamesById[$typeId] = $typeName;

            if (!isset($productTypes[$typeId])) {
                $productTypes[$typeId] = [
                    'name' => $typeName,
                    'quantity' => 0,
                    'revenue_ttc' => 0,
                    'order_count' => 0,
                    'average_price_ttc' => 0,
                    'share_revenue_percent' => 0.0,
                    'is_sold' => false,
                    '_orders' => [],
                ];
            }
        }

        return [
            'sold_products' => $soldProducts,
            'product_types' => $productTypes,
            'product_names_by_id' => $productNamesById,
            'type_names_by_id' => $typeNamesById,
            'unit_labels_by_pair' => $unitLabelsByPair,
        ];
    }

    /**
     * @return array{active: bool, items_ttc: int, shipping_ttc: int, total_ttc: int}
     */
    private function extractCreditStats(Cart $cart): array
    {
        $invoice = $cart->getInvoice();
        if (null === $invoice || !$invoice->isCredit()) {
            return [
                'active' => false,
                'items_ttc' => 0,
                'shipping_ttc' => 0,
                'total_ttc' => 0,
            ];
        }

        $creditDetails = $invoice->getCreditDetails();
        if (!\is_array($creditDetails)) {
            return [
                'active' => true,
                'items_ttc' => 0,
                'shipping_ttc' => 0,
                'total_ttc' => 0,
            ];
        }

        $totals = \is_array($creditDetails['totals'] ?? null) ? $creditDetails['totals'] : [];

        return [
            'active' => true,
            'items_ttc' => max(0, (int) ($totals['items_ttc'] ?? 0)),
            'shipping_ttc' => max(0, (int) ($totals['shipping_ttc'] ?? 0)),
            'total_ttc' => max(0, (int) ($totals['total_ttc'] ?? 0)),
        ];
    }

    private function normalizeStatsLabel(?string $value, string $fallback): string
    {
        $value = mb_trim((string) $value);

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

    private function buildStatsEntryKey(
        ?string $productId,
        ?string $typeId,
        ?string $productName,
        ?string $typeName,
        ?string $unitLabel,
    ): string
    {
        $parts = [$productId, $typeId, $productName, $typeName, $unitLabel];

        return md5(implode('|', array_map(
            static fn (mixed $part): string => (string) $part,
            $parts
        )));
    }

    private function buildTypeUnitPairKey(?string $productId, ?string $typeId): string
    {
        return ($productId ?? '').'|'.($typeId ?? '');
    }

    private function hasLineOption(\CpCreation\VitiCore\Cart\CartPricingLine $line): bool
    {
        return '' !== mb_trim((string) $line->optionName)
            || $line->optionTotalTTC > 0
            || null !== $line->option;
    }

    private function resolveProductNameFr(\CpCreation\VitiCore\Product\Model\Product $product): string
    {
        $translatedName = mb_trim((string) $product->translate('fr')->getName());

        if ('' !== $translatedName) {
            return $translatedName;
        }

        return $this->normalizeStatsLabel($product->getName(), 'Produit inconnu');
    }

    private function resolveProductTypeNameFr(\CpCreation\VitiCore\Product\Model\ProductType $type): string
    {
        $translatedName = mb_trim((string) $type->translate('fr')->getName());

        if ('' !== $translatedName) {
            return $translatedName;
        }

        return $this->normalizeStatsLabel($type->getName(), 'Sans type');
    }

    private function buildProductTypeUnitLabel(\CpCreation\VitiCore\Product\Model\ProductType $type): string
    {
        $unity = mb_trim((string) $type->getUnity());
        if ('' === $unity) {
            return '-';
        }

        $volume = $type->getVolume();
        $label = '' !== (string) $volume ? mb_trim((string) $volume).' '.$unity : $unity;

        return $this->normalizeStatsLabel($label, '-');
    }

    private function computeStatsPercent(int|float $value, int|float $total): float
    {
        if ($total <= 0) {
            return 0.0;
        }

        return round((((float) $value) / ((float) $total)) * 100, 2);
    }

    /**
     * @param list<array<string, mixed>> $soldProducts
     *
     * @return array{
     *     indicators: list<array{name: string, max: int}>,
     *     revenue_share: list<float>,
     *     quantity_share: list<float>
     * }
     */
    private function buildStatsRadarData(array $soldProducts): array
    {
        $radarProducts = array_slice($soldProducts, 0, 6);
        $indicators = [];
        $revenueShare = [];
        $quantityShare = [];

        foreach ($radarProducts as $product) {
            $indicators[] = [
                'name' => mb_strimwidth((string) ($product['name'] ?? 'Produit'), 0, 26, '...'),
                'max' => 100,
            ];
            $revenueShare[] = (float) ($product['share_revenue_percent'] ?? 0.0);
            $quantityShare[] = (float) ($product['share_quantity_percent'] ?? 0.0);
        }

        return [
            'indicators' => $indicators,
            'revenue_share' => $revenueShare,
            'quantity_share' => $quantityShare,
        ];
    }

    /**
     * @return array<string, array{label: string, sort_value: int, 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),
                'sort_value' => $cursor->getTimestamp(),
                '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 resolvePaymentMethodStatsKey(Cart $cart): string
    {
        $paymentMethod = $cart->getPaymentMethod();
        $methodId = $paymentMethod?->getId()?->toString();

        if (null !== $methodId && '' !== $methodId) {
            return $methodId;
        }

        $method = $paymentMethod?->getMethod();

        return null !== $method && '' !== $method
            ? 'method:'.$method
            : 'method:unknown';
    }

    /**
     * @return array{computed: bool, amount_ttc: int}
     */
    private function resolveTransactionFeeTTC(Cart $cart): array
    {
        $actualTransactionFee = $this->extractStoredTransactionFeeTTC($cart);

        if (null !== $actualTransactionFee) {
            return [
                'computed' => true,
                'amount_ttc' => $actualTransactionFee,
            ];
        }

        $estimatedTransactionFee = $this->estimateTransactionFeeTTC($cart);

        return [
            'computed' => $estimatedTransactionFee['estimated'],
            'amount_ttc' => $estimatedTransactionFee['amount_ttc'],
        ];
    }

    /**
     * @return array{estimated: bool, amount_ttc: int}
     */
    private function estimateTransactionFeeTTC(Cart $cart): array
    {
        $paymentMethod = $cart->getPaymentMethod();
        if (!$paymentMethod instanceof \CpCreation\VitiCore\Cart\Model\PaymentMethod) {
            return [
                'estimated' => false,
                'amount_ttc' => 0,
            ];
        }

        if (!$paymentMethod->isPaymentOnline()) {
            return [
                'estimated' => true,
                'amount_ttc' => 0,
            ];
        }

        $configuration = $paymentMethod->getConfiguration();
        if (!\is_array($configuration)) {
            return [
                'estimated' => false,
                'amount_ttc' => 0,
            ];
        }

        $percent = $this->parsePositiveFloatConfigValue($configuration['estimatedTransactionFeePercent'] ?? null);
        $fixed = $this->parsePositiveFloatConfigValue($configuration['estimatedTransactionFeeFixed'] ?? null);

        if (null === $percent && null === $fixed) {
            return [
                'estimated' => false,
                'amount_ttc' => 0,
            ];
        }

        $totalTTC = max(0, (int) ($cart->getPricing()?->totalTTC ?? 0));
        $amountTTC = (int) round(
            ($totalTTC * (($percent ?? 0.0) / 100))
            + (($fixed ?? 0.0) * 100)
        );

        return [
            'estimated' => true,
            'amount_ttc' => max(0, $amountTTC),
        ];
    }

    private function extractStoredTransactionFeeTTC(Cart $cart): ?int
    {
        $paymentMethod = $cart->getPaymentMethod();
        if (!$paymentMethod instanceof \CpCreation\VitiCore\Cart\Model\PaymentMethod) {
            return null;
        }

        if (StripePayment::METHOD !== $paymentMethod->getMethod()) {
            return null;
        }

        $response = $cart->getCartCheckout()?->getResponse();
        if (!\is_array($response)) {
            return null;
        }

        $candidates = [
            ['transaction_fee_amount'],
            ['stripe_checkout_sync', 'transaction_fee_amount'],
            ['object', 'payment_intent', 'latest_charge', 'balance_transaction', 'fee'],
            ['session', 'payment_intent', 'latest_charge', 'balance_transaction', 'fee'],
            ['raw', 'data', 'object', 'payment_intent', 'latest_charge', 'balance_transaction', 'fee'],
            ['raw', 'data', 'object', 'latest_charge', 'balance_transaction', 'fee'],
            ['raw', 'data', 'object', 'balance_transaction', 'fee'],
        ];

        foreach ($candidates as $path) {
            $value = $this->getNestedArrayValue($response, $path);
            $normalizedAmount = $this->normalizeStoredAmount($value);

            if (null !== $normalizedAmount) {
                return max(0, $normalizedAmount);
            }
        }

        return null;
    }

    /**
     * @param list<string> $path
     */
    private function getNestedArrayValue(array $payload, array $path): mixed
    {
        $value = $payload;

        foreach ($path as $segment) {
            if (!\is_array($value) || !array_key_exists($segment, $value)) {
                return null;
            }

            $value = $value[$segment];
        }

        return $value;
    }

    private function normalizeStoredAmount(mixed $value): ?int
    {
        if (\is_int($value)) {
            return $value;
        }

        if (\is_string($value) && '' !== $value && is_numeric($value)) {
            return (int) round((float) $value);
        }

        return null;
    }

    /**
     * @return array{key: string, name: string, type: string}
     */
    private function resolveDeliveryMethodStatsData(Cart $cart): array
    {
        $deliveryMethod = $cart->getDeliveryMethod();

        if (null !== $deliveryMethod) {
            $name = mb_trim($this->resolveDeliveryMethodNameFr($deliveryMethod));
            $type = $deliveryMethod->isDepot() ? 'Retrait' : 'Expedition';

            return [
                'key' => 'delivery:'.$deliveryMethod->getId()->toString(),
                'name' => '' !== $name ? $name : 'Mode non renseigne',
                'type' => $type,
            ];
        }

        $shippingName = mb_trim((string) ($cart->getShippingName() ?? ''));

        return [
            'key' => '' !== $shippingName ? 'shipping:'.md5($shippingName) : 'delivery:unknown',
            'name' => '' !== $shippingName ? $shippingName : 'Livraison standard',
            'type' => $this->computeShippingPaidTTC($cart) > 0 ? 'Expedition' : 'Retrait / autre',
        ];
    }

    /**
     * @return array<string, array{
     *     name: string,
     *     type: string,
     *     order_count: int,
     *     revenue_ttc: int,
     *     products_ttc: int,
     *     shipping_ttc: int
     * }>
     */
    private function buildDeliveryMethodStatsReferences(): array
    {
        $deliveryMethods = [];

        foreach ($this->deliveryMethodRepository->findBy([], ['position' => 'ASC']) as $deliveryMethod) {
            if (
                !$deliveryMethod instanceof \CpCreation\VitiCore\Shop\Model\DeliveryMethod
                || $deliveryMethod->isDeleted()
            ) {
                continue;
            }

            $deliveryMethods['delivery:'.$deliveryMethod->getId()->toString()] = [
                'name' => $this->resolveDeliveryMethodNameFr($deliveryMethod),
                'type' => $deliveryMethod->isDepot() ? 'Retrait' : 'Expedition',
                'order_count' => 0,
                'revenue_ttc' => 0,
                'products_ttc' => 0,
                'shipping_ttc' => 0,
            ];
        }

        return $deliveryMethods;
    }

    private function resolveDeliveryMethodNameFr(\CpCreation\VitiCore\Shop\Model\DeliveryMethod $deliveryMethod): string
    {
        $translatedName = mb_trim((string) $deliveryMethod->translate('fr')->getName());

        if ('' !== $translatedName) {
            return $translatedName;
        }

        return $this->normalizeStatsLabel($deliveryMethod->getName(), 'Mode non renseigne');
    }

    private function parsePositiveFloatConfigValue(mixed $value): ?float
    {
        if (\is_int($value) || \is_float($value)) {
            return $value >= 0 ? (float) $value : null;
        }

        if (!\is_string($value)) {
            return null;
        }

        $normalized = str_replace(',', '.', mb_trim($value));
        if ('' === $normalized || !is_numeric($normalized)) {
            return null;
        }

        $floatValue = (float) $normalized;

        return $floatValue >= 0 ? $floatValue : null;
    }

    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 ('delivered' === $filters['status']) {
            $parts[] = 'livrees';
        } elseif ('paid_not_delivered' === $filters['status']) {
            $parts[] = 'payees-non-livrees';
        } elseif ('offline_pending' === $filters['status']) {
            $parts[] = 'paiements-hors-ligne-a-traiter';
        } elseif ('failed' === $filters['status']) {
            $parts[] = 'paiement-echoue';
        } elseif ('canceled' === $filters['status']) {
            $parts[] = 'paiement-annule';
        } elseif ('abandoned' === $filters['status']) {
            $parts[] = 'abandonnes';
        } 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 buildInvoiceExportSuffix(array $filters): string
    {
        $parts = ['du_'.$this->getCurrentDateLabel()];

        $parts[] = match ($filters['type']) {
            'invoice' => 'factures',
            'credit' => 'avoirs',
            default => 'factures-et-avoirs',
        };

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

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

        if (null !== $filters['search_input']) {
            $searchLabel = preg_replace('/[^A-Za-z0-9_-]+/', '-', mb_strtolower($filters['search_input'])) ?: null;
            if (null !== $searchLabel && '' !== trim($searchLabel, '-')) {
                $parts[] = 'recherche_'.trim($searchLabel, '-');
            }
        }

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

    private function resolveCheckoutStatusLabel(Cart $cart): string
    {
        return match ($cart->getCheckoutStatus()) {
            'paid' => 'Payee',
            'failed' => 'Paiement echoue',
            'canceled' => 'Paiement annule',
            'abandoned' => 'Panier abandonne',
            'initiated' => 'Paiement initie',
            'ordered' => 'Paiement en attente de validation',
            default => 'En cours',
        };
    }

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

    /**
     * @param list<\CpCreation\VitiCore\Cart\Model\Invoice> $items
     *
     * @return list<array{cart: Cart, credit_only: bool}>
     */
    private function buildInvoicePdfDocuments(array $items, string $type): array
    {
        $documents = [];
        $includeInvoices = 'credit' !== $type;
        $includeCredits = 'invoice' !== $type;

        foreach ($items as $item) {
            $cart = $item->getCart();
            if (null === $cart) {
                continue;
            }

            if ($includeInvoices && null !== $item->getNumberDebit()) {
                $documents[] = [
                    'cart' => $cart,
                    'credit_only' => false,
                ];
            }

            if ($includeCredits && $item->isCredit() && null !== $item->getNumberCredit()) {
                $documents[] = [
                    'cart' => $cart,
                    'credit_only' => true,
                ];
            }
        }

        return $documents;
    }

    private function buildInvoiceCartExportName(Cart $cart): string
    {
        $invoice = $cart->getInvoice();
        $shopConfig = $this->shopConfigRepository->findOne();
        $reference = $this->formatDebitInvoiceReference($invoice, $shopConfig) ?? ($cart->getToken() ?: 'facture');
        $reference = preg_replace('/[^A-Za-z0-9_-]+/', '-', $reference) ?: 'facture';

        return sprintf(
            '%s_%s.pdf',
            trim($reference, '-'),
            $invoice?->isCredit() ? 'facture-et-avoir' : 'facture'
        );
    }

    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;
    }
}
