<?php

namespace App\Tests\Controller\Admin;

use App\Controller\Admin\DevisController;
use App\Entity\Advert;
use App\Entity\Customer;
use App\Entity\Devis;
use App\Entity\DevisLine;
use App\Entity\OrderNumber;
use App\Service\AdvertService;
use App\Service\DevisService;
use App\Service\DocuSealService;
use App\Service\MailerService;
use App\Service\MeilisearchService;
use App\Service\OrderNumberService;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Twig\Environment;

class DevisControllerTest extends TestCase
{
    // ---------------------------------------------------------------
    // Helper: build controller with container/session
    // ---------------------------------------------------------------

    private function buildController(
        ?EntityManagerInterface $em = null,
        ?DevisService $devisService = null,
        ?MeilisearchService $meilisearch = null,
        ?OrderNumberService $orderNumberService = null,
    ): DevisController {
        $em ??= $this->createStub(EntityManagerInterface::class);
        $meilisearch ??= $this->createStub(MeilisearchService::class);
        $orderNumberService ??= $this->createStub(OrderNumberService::class);

        if (null === $devisService) {
            $devisService = $this->createStub(DevisService::class);
            $devisService->method('computeTotals')->willReturn([
                'totalHt' => '100.00',
                'totalTva' => '20.00',
                'totalTtc' => '120.00',
            ]);
        }

        $controller = new DevisController($em, $orderNumberService, $devisService, $meilisearch);

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);

        $twig = $this->createStub(Environment::class);
        $twig->method('render')->willReturn('<html></html>');

        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true],
            ['router', true],
            ['security.authorization_checker', true],
            ['security.token_storage', true],
            ['request_stack', true],
            ['parameter_bag', true],
            ['serializer', false],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $twig],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        return $controller;
    }

    // ---------------------------------------------------------------
    // Helper: build a real Devis entity
    // ---------------------------------------------------------------

    private function buildDevis(string $numOrder = 'DV/2026-001', string $state = Devis::STATE_CREATED): Devis
    {
        $orderNumber = new OrderNumber($numOrder);
        $devis = new Devis($orderNumber, 'test_secret');
        $devis->setState($state);

        return $devis;
    }

    private function buildCustomer(int $id = 1, ?string $email = 'client@example.com'): Customer
    {
        $customer = $this->createStub(Customer::class);
        $customer->method('getId')->willReturn($id);
        $customer->method('getEmail')->willReturn($email);

        return $customer;
    }

    // ---------------------------------------------------------------
    // create – GET: not found
    // ---------------------------------------------------------------

    public function testCreateThrows404WhenCustomerNotFound(): void
    {
        $customerRepo = $this->createStub(EntityRepository::class);
        $customerRepo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->create(999, new Request());
    }

    // ---------------------------------------------------------------
    // create – GET: renders form
    // ---------------------------------------------------------------

    public function testCreateGetRendersForm(): void
    {
        $customer = $this->buildCustomer();

        $customerRepo = $this->createStub(EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);

        $orderNumberService = $this->createStub(OrderNumberService::class);
        $orderNumberService->method('preview')->willReturn('DV/2026-001');

        $controller = $this->buildController($em, orderNumberService: $orderNumberService);

        $response = $controller->create(1, Request::create('/create/1', 'GET'));

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(200, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // create – POST: empty lines redirects
    // ---------------------------------------------------------------

    public function testCreatePostWithNoLinesRedirects(): void
    {
        $customer = $this->buildCustomer(1);

        $customerRepo = $this->createStub(EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);

        $controller = $this->buildController($em);

        $request = Request::create('/create/1', 'POST', ['lines' => []]);

        $response = $controller->create(1, $request);

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // create – POST: lines with all-empty titles redirects (no valid line)
    // ---------------------------------------------------------------

    public function testCreatePostWithOnlyEmptyTitlesRedirects(): void
    {
        $customer = $this->buildCustomer(1);

        $customerRepo = $this->createStub(EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);

        $devis = $this->buildDevis();

        $devisService = $this->createStub(DevisService::class);
        $devisService->method('create')->willReturn($devis);
        $devisService->method('computeTotals')->willReturn([
            'totalHt' => '0.00',
            'totalTva' => '0.00',
            'totalTtc' => '0.00',
        ]);

        $controller = $this->buildController($em, $devisService);

        // lines array with a line that has empty title
        $request = Request::create('/create/1', 'POST', [
            'lines' => [
                0 => ['title' => '   ', 'priceHt' => '100', 'pos' => '0'],
            ],
        ]);

        $response = $controller->create(1, $request);

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // create – POST: success
    // ---------------------------------------------------------------

    public function testCreatePostSuccessRedirects(): void
    {
        $customer = $this->buildCustomer(1);

        $customerRepo = $this->createStub(EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $devis = $this->buildDevis();

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);
        $em->expects($this->atLeastOnce())->method('persist');
        $em->expects($this->once())->method('flush');

        $devisService = $this->createStub(DevisService::class);
        $devisService->method('create')->willReturn($devis);
        $devisService->method('computeTotals')->willReturn([
            'totalHt' => '100.00',
            'totalTva' => '20.00',
            'totalTtc' => '120.00',
        ]);

        $controller = $this->buildController($em, $devisService);

        $request = Request::create('/create/1', 'POST', [
            'lines' => [
                0 => ['title' => 'Prestation SEO', 'priceHt' => '100', 'pos' => '0'],
            ],
        ]);

        $response = $controller->create(1, $request);

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // create – POST: line with description, type, serviceId (createDevisLine branches)
    // ---------------------------------------------------------------

    public function testCreatePostWithFullLineDataCovarsCreateDevisLine(): void
    {
        $customer = $this->buildCustomer(1);

        $customerRepo = $this->createStub(EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $devis = $this->buildDevis();

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);

        $devisService = $this->createStub(DevisService::class);
        $devisService->method('create')->willReturn($devis);
        $devisService->method('computeTotals')->willReturn([
            'totalHt' => '150.00',
            'totalTva' => '30.00',
            'totalTtc' => '180.00',
        ]);

        $controller = $this->buildController($em, $devisService);

        $request = Request::create('/create/1', 'POST', [
            'lines' => [
                0 => [
                    'title' => 'Hébergement',
                    'description' => 'Desc hébergement',
                    'priceHt' => '150,00',  // comma decimal
                    'pos' => '0',
                    'type' => 'website',
                    'serviceId' => '42',
                ],
            ],
        ]);

        $response = $controller->create(1, $request);

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // edit – not found
    // ---------------------------------------------------------------

    public function testEditThrows404WhenDevisNotFound(): void
    {
        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->edit(999, new Request());
    }

    // ---------------------------------------------------------------
    // edit – cancelled devis redirects
    // ---------------------------------------------------------------

    public function testEditRedirectsWhenDevisIsCancelled(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis('DV/2026-002', Devis::STATE_CANCEL);
        $devis->setCustomer($customer);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->edit(1, new Request());

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // edit – cancelled devis without customer (getId returns null)
    // ---------------------------------------------------------------

    public function testEditRedirectsWhenCancelledDevisHasNoCustomer(): void
    {
        $devis = $this->buildDevis('DV/2026-007', Devis::STATE_CANCEL);
        // no customer set → getCustomer() = null

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->edit(1, new Request());

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // edit – no customer on non-cancelled devis throws 404
    // ---------------------------------------------------------------

    public function testEditThrows404WhenDevisHasNoCustomer(): void
    {
        $devis = $this->buildDevis('DV/2026-003', Devis::STATE_CREATED);
        // getCustomer() returns null (no setCustomer call)

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->edit(1, new Request());
    }

    // ---------------------------------------------------------------
    // edit – GET: renders form
    // ---------------------------------------------------------------

    public function testEditGetRendersForm(): void
    {
        $customer = $this->buildCustomer(3);
        $devis = $this->buildDevis('DV/2026-004');
        $devis->setCustomer($customer);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->edit(1, Request::create('/1/edit', 'GET'));

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(200, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // edit – POST: empty lines redirects to edit route
    // ---------------------------------------------------------------

    public function testEditPostWithNoLinesRedirectsToEditRoute(): void
    {
        $customer = $this->buildCustomer(3);
        $devis = $this->buildDevis('DV/2026-005');
        $devis->setCustomer($customer);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $request = Request::create('/1/edit', 'POST', ['lines' => []]);

        $response = $controller->edit(1, $request);

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // edit – POST: all-empty titles redirects (no valid line)
    // ---------------------------------------------------------------

    public function testEditPostWithOnlyEmptyTitlesRedirects(): void
    {
        $customer = $this->buildCustomer(3);
        $devis = $this->buildDevis('DV/2026-005b');
        $devis->setCustomer($customer);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $devisService = $this->createStub(DevisService::class);
        $devisService->method('computeTotals')->willReturn([
            'totalHt' => '0.00',
            'totalTva' => '0.00',
            'totalTtc' => '0.00',
        ]);

        $controller = $this->buildController($em, $devisService);

        $request = Request::create('/1/edit', 'POST', [
            'lines' => [
                0 => ['title' => '', 'priceHt' => '50', 'pos' => '0'],
            ],
        ]);

        $response = $controller->edit(1, $request);

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // edit – POST: success removes old lines and saves new ones
    // ---------------------------------------------------------------

    public function testEditPostSuccessReplacesLinesAndRedirects(): void
    {
        $customer = $this->buildCustomer(3);
        $devis = $this->buildDevis('DV/2026-006');
        $devis->setCustomer($customer);

        // Add an existing line to be removed during edit
        $oldLine = new DevisLine($devis, 'Old line', '50.00', 0);
        $devis->addLine($oldLine);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);
        $em->expects($this->atLeastOnce())->method('persist');
        $em->expects($this->once())->method('flush');

        $devisService = $this->createStub(DevisService::class);
        $devisService->method('computeTotals')->willReturn([
            'totalHt' => '200.00',
            'totalTva' => '40.00',
            'totalTtc' => '240.00',
        ]);

        $controller = $this->buildController($em, $devisService);

        $request = Request::create('/1/edit', 'POST', [
            'lines' => [
                0 => ['title' => 'New line', 'priceHt' => '200', 'pos' => '0'],
            ],
        ]);

        $response = $controller->edit(1, $request);

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // generatePdf – not found
    // ---------------------------------------------------------------

    public function testGeneratePdfThrows404WhenDevisNotFound(): void
    {
        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->generatePdf(
            999,
            $this->createStub(KernelInterface::class),
            $this->createStub(Environment::class),
        );
    }

    // ---------------------------------------------------------------
    // send – not found
    // ---------------------------------------------------------------

    public function testSendThrows404WhenDevisNotFound(): void
    {
        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->send(
            999,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );
    }

    // ---------------------------------------------------------------
    // send – no PDF redirects
    // ---------------------------------------------------------------

    public function testSendRedirectsWhenNoPdf(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        // unsignedPdf is null → no PDF

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->send(
            1,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // send – no customer redirects
    // ---------------------------------------------------------------

    public function testSendRedirectsWhenNoCustomer(): void
    {
        $devis = $this->buildDevis();
        $devis->setUnsignedPdf('devis-file.pdf');
        // customer is null

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->send(
            1,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // send – no customer email redirects
    // ---------------------------------------------------------------

    public function testSendRedirectsWhenCustomerHasNoEmail(): void
    {
        $customer = $this->buildCustomer(5, null);  // email = null
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        $devis->setUnsignedPdf('devis-file.pdf');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->send(
            1,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // send – DocuSeal returns null (failure) redirects
    // ---------------------------------------------------------------

    public function testSendRedirectsWhenDocuSealFails(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        $devis->setUnsignedPdf('devis-file.pdf');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $docuSeal = $this->createStub(DocuSealService::class);
        $docuSeal->method('sendDevisForSignature')->willReturn(null);

        $urlGenerator = $this->createStub(UrlGeneratorInterface::class);
        $urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');

        $controller = $this->buildController($em);

        $response = $controller->send(
            1,
            $docuSeal,
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $urlGenerator,
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // send – success
    // ---------------------------------------------------------------

    public function testSendSuccessFlushesAndRedirects(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        $devis->setUnsignedPdf('devis-file.pdf');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);
        $em->expects($this->once())->method('flush');

        $docuSeal = $this->createStub(DocuSealService::class);
        $docuSeal->method('sendDevisForSignature')->willReturn(42);

        $urlGenerator = $this->createStub(UrlGeneratorInterface::class);
        $urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');

        $twig = $this->createStub(Environment::class);
        $twig->method('render')->willReturn('<html>email</html>');

        $mailer = $this->createStub(MailerService::class);

        $meilisearch = $this->createStub(MeilisearchService::class);

        $controller = $this->buildController($em, meilisearch: $meilisearch);

        // Replace twig stub in container
        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);

        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true],
            ['router', true],
            ['security.authorization_checker', true],
            ['security.token_storage', true],
            ['request_stack', true],
            ['parameter_bag', true],
            ['serializer', false],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $twig],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        $response = $controller->send(
            1,
            $docuSeal,
            $mailer,
            $twig,
            $urlGenerator,
        );

        $this->assertSame(302, $response->getStatusCode());
        $this->assertSame(Devis::STATE_SEND, $devis->getState());
        $this->assertSame('42', $devis->getSubmissionId());
    }

    // ---------------------------------------------------------------
    // resend – not found
    // ---------------------------------------------------------------

    public function testResendThrows404WhenDevisNotFound(): void
    {
        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->resend(
            999,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );
    }

    // ---------------------------------------------------------------
    // resend – no customer redirects
    // ---------------------------------------------------------------

    public function testResendRedirectsWhenNoCustomer(): void
    {
        $devis = $this->buildDevis();
        $devis->setUnsignedPdf('devis-file.pdf');
        // customer is null

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->resend(
            1,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // resend – no customer email redirects
    // ---------------------------------------------------------------

    public function testResendRedirectsWhenCustomerHasNoEmail(): void
    {
        $customer = $this->buildCustomer(5, null);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        $devis->setUnsignedPdf('devis-file.pdf');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->resend(
            1,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // resend – no PDF redirects
    // ---------------------------------------------------------------

    public function testResendRedirectsWhenNoPdf(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        // unsignedPdf is null

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->resend(
            1,
            $this->createStub(DocuSealService::class),
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $this->createStub(UrlGeneratorInterface::class),
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // resend – DocuSeal returns null (failure) redirects
    // ---------------------------------------------------------------

    public function testResendRedirectsWhenDocuSealFails(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        $devis->setUnsignedPdf('devis-file.pdf');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $docuSeal = $this->createStub(DocuSealService::class);
        $docuSeal->method('resendDevisSignature')->willReturn(null);

        $urlGenerator = $this->createStub(UrlGeneratorInterface::class);
        $urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');

        $controller = $this->buildController($em);

        $response = $controller->resend(
            1,
            $docuSeal,
            $this->createStub(MailerService::class),
            $this->createStub(Environment::class),
            $urlGenerator,
        );

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // resend – success
    // ---------------------------------------------------------------

    public function testResendSuccessFlushesAndRedirects(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis();
        $devis->setCustomer($customer);
        $devis->setUnsignedPdf('devis-file.pdf');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);
        $em->expects($this->once())->method('flush');

        $docuSeal = $this->createStub(DocuSealService::class);
        $docuSeal->method('resendDevisSignature')->willReturn(99);

        $urlGenerator = $this->createStub(UrlGeneratorInterface::class);
        $urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');

        $twig = $this->createStub(Environment::class);
        $twig->method('render')->willReturn('<html>email</html>');

        $mailer = $this->createStub(MailerService::class);

        $meilisearch = $this->createStub(MeilisearchService::class);

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);

        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true],
            ['router', true],
            ['security.authorization_checker', true],
            ['security.token_storage', true],
            ['request_stack', true],
            ['parameter_bag', true],
            ['serializer', false],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $twig],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);

        $controller = new DevisController($em, $this->createStub(OrderNumberService::class), $this->createStub(DevisService::class), $meilisearch);
        $controller->setContainer($container);

        $response = $controller->resend(
            1,
            $docuSeal,
            $mailer,
            $twig,
            $urlGenerator,
        );

        $this->assertSame(302, $response->getStatusCode());
        $this->assertSame(Devis::STATE_SEND, $devis->getState());
        $this->assertSame('99', $devis->getSubmissionId());
    }

    // ---------------------------------------------------------------
    // createAdvert – not found
    // ---------------------------------------------------------------

    public function testCreateAdvertThrows404WhenDevisNotFound(): void
    {
        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $this->expectException(NotFoundHttpException::class);
        $controller->createAdvert(999, $this->createStub(AdvertService::class));
    }

    // ---------------------------------------------------------------
    // createAdvert – wrong state (not accepted) redirects
    // ---------------------------------------------------------------

    public function testCreateAdvertRedirectsWhenDevisNotAccepted(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis('DV/2026-010', Devis::STATE_SEND);
        $devis->setCustomer($customer);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->createAdvert(1, $this->createStub(AdvertService::class));

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // createAdvert – wrong state without customer (getId = null path)
    // ---------------------------------------------------------------

    public function testCreateAdvertRedirectsWhenNotAcceptedAndNoCustomer(): void
    {
        $devis = $this->buildDevis('DV/2026-010b', Devis::STATE_CREATED);
        // no customer

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->createAdvert(1, $this->createStub(AdvertService::class));

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // createAdvert – already has advert redirects
    // ---------------------------------------------------------------

    public function testCreateAdvertRedirectsWhenAdvertAlreadyExists(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis('DV/2026-011', Devis::STATE_ACCEPTED);
        $devis->setCustomer($customer);

        // Set an existing advert on the devis
        $existingAdvert = new Advert(new OrderNumber('AP/2026-001'), 'secret');
        $devis->setAdvert($existingAdvert);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->createAdvert(1, $this->createStub(AdvertService::class));

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // createAdvert – already has advert without customer (null getId path)
    // ---------------------------------------------------------------

    public function testCreateAdvertRedirectsWhenAdvertExistsAndNoCustomer(): void
    {
        $devis = $this->buildDevis('DV/2026-011b', Devis::STATE_ACCEPTED);
        // no customer
        $existingAdvert = new Advert(new OrderNumber('AP/2026-001b'), 'secret');
        $devis->setAdvert($existingAdvert);

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->createAdvert(1, $this->createStub(AdvertService::class));

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // createAdvert – success: creates advert with lines (all line fields)
    // ---------------------------------------------------------------

    public function testCreateAdvertSuccessCopiesLinesAndRedirects(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis('DV/2026-012', Devis::STATE_ACCEPTED);
        $devis->setCustomer($customer);
        $devis->setTotalHt('200.00');
        $devis->setTotalTva('40.00');
        $devis->setTotalTtc('240.00');

        // Add a devis line with all optional fields set
        $line = new DevisLine($devis, 'Prestation web', '200.00', 0);
        $line->setDescription('Description de la prestation');
        $line->setType('website');
        $line->setServiceId(7);
        $devis->addLine($line);

        // devis->getAdvert() must be null so the guard passes
        // (do NOT call $devis->setAdvert() here)

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        // createFromDevis returns an advert that also links back via setDevis
        $advert = new Advert(new OrderNumber('AP/2026-012'), 'secret');
        $advert->setDevis($devis);   // mirrors what AdvertService::create does

        $advertService = $this->createStub(AdvertService::class);
        $advertService->method('createFromDevis')->willReturn($advert);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);
        $em->expects($this->atLeastOnce())->method('persist');
        $em->expects($this->once())->method('flush');

        $meilisearch = $this->createMock(MeilisearchService::class);
        $meilisearch->expects($this->once())->method('indexAdvert');
        $meilisearch->expects($this->once())->method('indexDevis');

        $controller = $this->buildController($em, meilisearch: $meilisearch);

        $response = $controller->createAdvert(1, $advertService);

        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // createAdvert – success: line without optional fields (null branches)
    // ---------------------------------------------------------------

    public function testCreateAdvertSuccessWithLineHavingNoOptionalFields(): void
    {
        $customer = $this->buildCustomer(5);
        $devis = $this->buildDevis('DV/2026-013', Devis::STATE_ACCEPTED);
        $devis->setCustomer($customer);
        $devis->setTotalHt('50.00');
        $devis->setTotalTva('0.00');
        $devis->setTotalTtc('50.00');

        // Line with no description, no type, no serviceId
        $line = new DevisLine($devis, 'Simple line', '50.00', 0);
        $devis->addLine($line);

        // devis->getAdvert() must be null so the guard passes
        $advert = new Advert(new OrderNumber('AP/2026-013'), 'secret');

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $advertService = $this->createStub(AdvertService::class);
        $advertService->method('createFromDevis')->willReturn($advert);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->createAdvert(1, $advertService);

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // createAdvert – success with no customer on devis (null getId path on redirect)
    // ---------------------------------------------------------------

    public function testCreateAdvertSuccessWhenDevisHasNoCustomerAtEnd(): void
    {
        // Devis is accepted, no advert, getCustomer() returns null → redirect uses 0
        $devis = $this->buildDevis('DV/2026-014', Devis::STATE_ACCEPTED);
        // no customer set on devis itself; getAdvert() is null so guard passes

        $advert = new Advert(new OrderNumber('AP/2026-014'), 'secret');
        // do NOT call $devis->setAdvert() here

        $devisRepo = $this->createStub(EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $advertService = $this->createStub(AdvertService::class);
        $advertService->method('createFromDevis')->willReturn($advert);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($devisRepo);

        $controller = $this->buildController($em);

        $response = $controller->createAdvert(1, $advertService);

        $this->assertInstanceOf(Response::class, $response);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ── services ──

    private function buildControllerWithEm(EntityManagerInterface $em): DevisController
    {
        $orderNumberService = $this->createStub(\App\Service\OrderNumberService::class);
        $devisService = $this->createStub(\App\Service\DevisService::class);
        $meilisearch = $this->createStub(\App\Service\MeilisearchService::class);

        $controller = new DevisController($em, $orderNumberService, $devisService, $meilisearch);

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);

        $twig = $this->createStub(Environment::class);
        $twig->method('render')->willReturn('<html></html>');

        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true],
            ['router', true],
            ['security.authorization_checker', true],
            ['security.token_storage', true],
            ['request_stack', true],
            ['parameter_bag', true],
            ['serializer', false],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $twig],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        return $controller;
    }

    public function testServicesCustomerNotFound(): void
    {
        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);

        $response = $controller->services(999, 'ndd');
        $this->assertSame(200, $response->getStatusCode());
        $this->assertSame('[]', $response->getContent());
    }

    public function testServicesNdd(): void
    {
        $customer = $this->createStub(\App\Entity\Customer::class);

        $domain = $this->createStub(\App\Entity\Domain::class);
        $domain->method('getId')->willReturn(1);
        $domain->method('getFqdn')->willReturn('example.com');

        $customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $domainRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $domainRepo->method('findBy')->willReturn([$domain]);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
            \App\Entity\Customer::class => $customerRepo,
            \App\Entity\Domain::class => $domainRepo,
            default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
        });

        $controller = $this->buildControllerWithEm($em);

        $response = $controller->services(1, 'ndd');
        $data = json_decode($response->getContent(), true);
        $this->assertSame(1, $data[0]['id']);
        $this->assertSame('example.com', $data[0]['label']);
    }

    public function testServicesWebsite(): void
    {
        $customer = $this->createStub(\App\Entity\Customer::class);

        $website = $this->createStub(\App\Entity\Website::class);
        $website->method('getId')->willReturn(5);
        $website->method('getName')->willReturn('Mon Site');
        $website->method('getType')->willReturn('vitrine');

        $customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $websiteRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $websiteRepo->method('findBy')->willReturn([$website]);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
            \App\Entity\Customer::class => $customerRepo,
            \App\Entity\Website::class => $websiteRepo,
            default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
        });

        $controller = $this->buildControllerWithEm($em);

        $response = $controller->services(1, 'website');
        $data = json_decode($response->getContent(), true);
        $this->assertSame(5, $data[0]['id']);
        $this->assertStringContainsString('Mon Site', $data[0]['label']);
    }

    public function testServicesEsymail(): void
    {
        $customer = $this->createStub(\App\Entity\Customer::class);

        $domain = $this->createStub(\App\Entity\Domain::class);

        $email = $this->createStub(\App\Entity\DomainEmail::class);
        $email->method('getId')->willReturn(10);
        $email->method('getFullEmail')->willReturn('user@example.com');

        $customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $domainRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $domainRepo->method('findBy')->willReturn([$domain]);

        $emailRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $emailRepo->method('findBy')->willReturn([$email]);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
            \App\Entity\Customer::class => $customerRepo,
            \App\Entity\Domain::class => $domainRepo,
            \App\Entity\DomainEmail::class => $emailRepo,
            default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
        });

        $controller = $this->buildControllerWithEm($em);

        $response = $controller->services(1, 'esymail');
        $data = json_decode($response->getContent(), true);
        $this->assertSame(10, $data[0]['id']);
        $this->assertSame('user@example.com', $data[0]['label']);
    }

    public function testServicesDefaultType(): void
    {
        $customer = $this->createStub(\App\Entity\Customer::class);

        $customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $customerRepo->method('find')->willReturn($customer);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($customerRepo);

        $controller = $this->buildControllerWithEm($em);

        $response = $controller->services(1, 'unknown');
        $this->assertSame('[]', $response->getContent());
    }

    // ── search ──

    // ── events ──

    public function testEventsNotFound(): void
    {
        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn(null);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);

        $this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
        $controller->events(999);
    }

    public function testEventsNoSubmitterId(): void
    {
        $devis = $this->createStub(\App\Entity\Devis::class);
        $devis->method('getSubmissionId')->willReturn(null);

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);
        $response = $controller->events(1);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testEventsWithSubmitterId(): void
    {
        $devis = $this->createStub(\App\Entity\Devis::class);
        $devis->method('getSubmissionId')->willReturn('42');

        $devisRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $devisRepo->method('find')->willReturn($devis);

        $query = $this->createStub(\Doctrine\ORM\Query::class);
        $query->method('getResult')->willReturn([]);

        $qb = $this->createStub(\Doctrine\ORM\QueryBuilder::class);
        $qb->method('where')->willReturn($qb);
        $qb->method('andWhere')->willReturn($qb);
        $qb->method('setParameter')->willReturn($qb);
        $qb->method('orderBy')->willReturn($qb);
        $qb->method('getQuery')->willReturn($query);

        $eventRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $eventRepo->method('createQueryBuilder')->willReturn($qb);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
            \App\Entity\Devis::class => $devisRepo,
            \App\Entity\DocusealEvent::class => $eventRepo,
            default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
        });

        $controller = $this->buildControllerWithEm($em);
        $response = $controller->events(1);
        $this->assertSame(200, $response->getStatusCode());
    }

    // ── cancel ──

    public function testCancelNotFound(): void
    {
        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn(null);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);

        $this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
        $controller->cancel(999);
    }

    public function testCancelAlreadyCancelled(): void
    {
        $on = new \App\Entity\OrderNumber('04/2026-00099');
        $devis = new \App\Entity\Devis($on, 'secret');
        $devis->setState(\App\Entity\Devis::STATE_CANCEL);

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);
        $response = $controller->cancel(1);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testCancelSuccess(): void
    {
        $on = new \App\Entity\OrderNumber('04/2026-00098');
        $devis = new \App\Entity\Devis($on, 'secret');
        $devis->setState(\App\Entity\Devis::STATE_SEND);

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);
        $em->expects($this->once())->method('flush');

        $meilisearch = $this->createMock(\App\Service\MeilisearchService::class);
        $meilisearch->expects($this->once())->method('indexDevis');

        $controller = new DevisController(
            $em,
            $this->createStub(\App\Service\OrderNumberService::class),
            $this->createStub(\App\Service\DevisService::class),
            $meilisearch,
        );

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);
        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');
        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true], ['router', true], ['security.authorization_checker', true],
            ['security.token_storage', true], ['request_stack', true], ['parameter_bag', true],
            ['serializer', false],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $this->createStub(Environment::class)],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        $response = $controller->cancel(1);
        $this->assertSame(302, $response->getStatusCode());
        $this->assertSame(\App\Entity\Devis::STATE_CANCEL, $devis->getState());
    }

    public function testCancelNoCustomer(): void
    {
        $on = new \App\Entity\OrderNumber('04/2026-00097');
        $devis = new \App\Entity\Devis($on, 'secret');

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);
        $response = $controller->cancel(1);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ── generatePdf ──

    public function testGeneratePdfNotFound(): void
    {
        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn(null);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $controller = $this->buildControllerWithEm($em);

        $this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
        $controller->generatePdf(999, $this->createStub(\Symfony\Component\HttpKernel\KernelInterface::class), $this->createStub(Environment::class));
    }

    public function testGeneratePdfSuccess(): void
    {
        $on = new \App\Entity\OrderNumber('04/2026-00096');
        $devis = new \App\Entity\Devis($on, 'secret');
        $devis->setTotalHt('100.00');
        $devis->setTotalTva('0.00');
        $devis->setTotalTtc('100.00');

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);
        $em->expects($this->once())->method('flush');

        $tmpDir = sys_get_temp_dir().'/devis_pdf_ctrl_'.bin2hex(random_bytes(4));
        mkdir($tmpDir.'/public/uploads/devis', 0777, true);

        $kernel = $this->createStub(\Symfony\Component\HttpKernel\KernelInterface::class);
        $kernel->method('getProjectDir')->willReturn($tmpDir);

        $twig = $this->createStub(Environment::class);

        $controller = new DevisController(
            $em,
            $this->createStub(\App\Service\OrderNumberService::class),
            $this->createStub(\App\Service\DevisService::class),
            $this->createStub(\App\Service\MeilisearchService::class),
        );

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);
        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');
        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true], ['router', true], ['security.authorization_checker', true],
            ['security.token_storage', true], ['request_stack', true], ['parameter_bag', true],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $this->createStub(Environment::class)],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        $response = $controller->generatePdf(1, $kernel, $twig);
        $this->assertSame(302, $response->getStatusCode());

        @array_map('unlink', glob($tmpDir.'/public/uploads/devis/*'));
        @rmdir($tmpDir.'/public/uploads/devis');
        @rmdir($tmpDir.'/public/uploads');
        @rmdir($tmpDir.'/public');
        @rmdir($tmpDir);
    }

    public function testGeneratePdfWithExistingPdf(): void
    {
        $on = new \App\Entity\OrderNumber('04/2026-00095');
        $devis = new \App\Entity\Devis($on, 'secret');
        $devis->setTotalHt('50.00');
        $devis->setTotalTva('0.00');
        $devis->setTotalTtc('50.00');

        $tmpDir = sys_get_temp_dir().'/devis_pdf_old_'.bin2hex(random_bytes(4));
        mkdir($tmpDir.'/public/uploads/devis', 0777, true);

        // Simulate existing PDF file
        $oldPdfName = 'old-devis.pdf';
        file_put_contents($tmpDir.'/public/uploads/devis/'.$oldPdfName, '%PDF-old');
        $devis->setUnsignedPdf($oldPdfName);

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);
        $em->expects($this->once())->method('flush');

        $kernel = $this->createStub(\Symfony\Component\HttpKernel\KernelInterface::class);
        $kernel->method('getProjectDir')->willReturn($tmpDir);

        $twig = $this->createStub(Environment::class);

        $controller = new DevisController(
            $em,
            $this->createStub(\App\Service\OrderNumberService::class),
            $this->createStub(\App\Service\DevisService::class),
            $this->createStub(\App\Service\MeilisearchService::class),
        );

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);
        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');
        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true], ['router', true], ['security.authorization_checker', true],
            ['security.token_storage', true], ['request_stack', true], ['parameter_bag', true],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $this->createStub(Environment::class)],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        $response = $controller->generatePdf(1, $kernel, $twig);
        $this->assertSame(302, $response->getStatusCode());

        // Old PDF should have been deleted
        $this->assertFileDoesNotExist($tmpDir.'/public/uploads/devis/'.$oldPdfName);

        @array_map('unlink', glob($tmpDir.'/public/uploads/devis/*'));
        @rmdir($tmpDir.'/public/uploads/devis');
        @rmdir($tmpDir.'/public/uploads');
        @rmdir($tmpDir.'/public');
        @rmdir($tmpDir);
    }

    public function testGeneratePdfWithExistingPdfNotOnDisk(): void
    {
        $on = new \App\Entity\OrderNumber('04/2026-00094');
        $devis = new \App\Entity\Devis($on, 'secret');
        $devis->setTotalHt('30.00');
        $devis->setTotalTva('0.00');
        $devis->setTotalTtc('30.00');
        $devis->setUnsignedPdf('ghost-file.pdf'); // File doesn't exist on disk

        $tmpDir = sys_get_temp_dir().'/devis_pdf_ghost_'.bin2hex(random_bytes(4));
        mkdir($tmpDir.'/public/uploads/devis', 0777, true);

        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('find')->willReturn($devis);
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $kernel = $this->createStub(\Symfony\Component\HttpKernel\KernelInterface::class);
        $kernel->method('getProjectDir')->willReturn($tmpDir);

        $controller = new DevisController(
            $em,
            $this->createStub(\App\Service\OrderNumberService::class),
            $this->createStub(\App\Service\DevisService::class),
            $this->createStub(\App\Service\MeilisearchService::class),
        );

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);
        $router = $this->createStub(RouterInterface::class);
        $router->method('generate')->willReturn('/redirect');
        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true], ['router', true], ['security.authorization_checker', true],
            ['security.token_storage', true], ['request_stack', true], ['parameter_bag', true],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $this->createStub(Environment::class)],
            ['router', $router],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        $response = $controller->generatePdf(1, $kernel, $this->createStub(Environment::class));
        $this->assertSame(302, $response->getStatusCode());

        @array_map('unlink', glob($tmpDir.'/public/uploads/devis/*'));
        @rmdir($tmpDir.'/public/uploads/devis');
        @rmdir($tmpDir.'/public/uploads');
        @rmdir($tmpDir.'/public');
        @rmdir($tmpDir);
    }

    // ── search ──

    public function testSearchEmptyQuery(): void
    {
        $em = $this->createStub(EntityManagerInterface::class);
        $controller = $this->buildControllerWithEm($em);

        $request = new Request(['q' => '']);
        $response = $controller->search(1, $request);
        $this->assertSame('[]', $response->getContent());
    }

    public function testSearchWhitespaceQuery(): void
    {
        $em = $this->createStub(EntityManagerInterface::class);
        $controller = $this->buildControllerWithEm($em);

        $request = new Request(['q' => '   ']);
        $response = $controller->search(1, $request);
        $this->assertSame('[]', $response->getContent());
    }

    public function testSearchWithResults(): void
    {
        $meilisearch = $this->createMock(\App\Service\MeilisearchService::class);
        $meilisearch->method('searchDevis')->with('test', 20, 42)->willReturn([
            ['numOrder' => '04/2026-00001', 'customerName' => 'Client A'],
        ]);

        $em = $this->createStub(EntityManagerInterface::class);
        $controller = new DevisController(
            $em,
            $this->createStub(\App\Service\OrderNumberService::class),
            $this->createStub(\App\Service\DevisService::class),
            $meilisearch,
        );

        $session = new Session(new MockArraySessionStorage());
        $stack = $this->createStub(RequestStack::class);
        $stack->method('getSession')->willReturn($session);

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturnMap([
            ['twig', true], ['router', true], ['security.authorization_checker', true],
            ['security.token_storage', true], ['request_stack', true], ['parameter_bag', true],
            ['serializer', false],
        ]);
        $container->method('get')->willReturnMap([
            ['twig', $this->createStub(Environment::class)],
            ['router', $this->createStub(RouterInterface::class)],
            ['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
            ['security.token_storage', $this->createStub(TokenStorageInterface::class)],
            ['request_stack', $stack],
            ['parameter_bag', $this->createStub(ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);

        $request = new Request(['q' => 'test']);
        $response = $controller->search(42, $request);

        $data = json_decode($response->getContent(), true);
        $this->assertCount(1, $data);
        $this->assertSame('04/2026-00001', $data[0]['numOrder']);
    }
}
