<?php

namespace App\Tests\Controller\Admin;

use App\Controller\Admin\PrestatairesController;
use App\Entity\FacturePrestataire;
use App\Entity\Prestataire;
use App\Repository\PrestataireRepository;
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\JsonResponse;
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\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponseInterface;
use Twig\Environment;

class PrestatairesControllerTest extends TestCase
{
    private function buildController(?EntityManagerInterface $em = null): PrestatairesController
    {
        $em ??= $this->createStub(EntityManagerInterface::class);

        $controller = new PrestatairesController($em);

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

    private function buildPrestataire(int $id = 1, string $raisonSociale = 'ACME SA'): Prestataire
    {
        $prestataire = new Prestataire($raisonSociale);
        // Force a non-null id via Reflection
        $ref = new \ReflectionProperty(Prestataire::class, 'id');
        $ref->setAccessible(true);
        $ref->setValue($prestataire, $id);

        return $prestataire;
    }

    // ---------------------------------------------------------------
    // index
    // ---------------------------------------------------------------

    public function testIndexReturns200(): void
    {
        $repo = $this->createStub(PrestataireRepository::class);
        $repo->method('findBy')->willReturn([]);

        $controller = $this->buildController();
        $response = $controller->index($repo);

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

    public function testIndexWithPrestataires(): void
    {
        $repo = $this->createStub(PrestataireRepository::class);
        $repo->method('findBy')->willReturn([
            $this->buildPrestataire(1, 'ACME SA'),
            $this->buildPrestataire(2, 'Example SAS'),
        ]);

        $controller = $this->buildController();
        $response = $controller->index($repo);

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

    // ---------------------------------------------------------------
    // create
    // ---------------------------------------------------------------

    public function testCreateRedirectsOnSuccess(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('persist');
        $em->expects($this->once())->method('flush');

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

        $request = new Request([], ['raisonSociale' => 'Nouveau Prestataire', 'email' => 'test@example.com']);
        $response = $controller->create($request);

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

    public function testCreateRedirectsWhenRaisonSocialeEmpty(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->never())->method('persist');

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

        $request = new Request([], ['raisonSociale' => '   ']);
        $response = $controller->create($request);

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

    // ---------------------------------------------------------------
    // show
    // ---------------------------------------------------------------

    public function testShowReturns200(): void
    {
        $prestataire = $this->buildPrestataire();
        $controller = $this->buildController();
        $response = $controller->show($prestataire);

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

    // ---------------------------------------------------------------
    // edit
    // ---------------------------------------------------------------

    public function testEditFlushesAndRedirects(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('flush');

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

        $request = new Request([], [
            'raisonSociale' => 'ACME Modifie',
            'email' => 'contact@acme.fr',
            'phone' => '0600000000',
            'siret' => '12345678901234',
            'address' => '1 rue de la Paix',
            'zipCode' => '75001',
            'city' => 'Paris',
        ]);
        $response = $controller->edit($prestataire, $request);

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

    public function testEditKeepsRaisonSocialeWhenEmpty(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('flush');

        $prestataire = $this->buildPrestataire(1, 'Nom Original');
        $controller = $this->buildController($em);

        // Empty raisonSociale should keep the original
        $request = new Request([], ['raisonSociale' => '']);
        $controller->edit($prestataire, $request);

        $this->assertSame('Nom Original', $prestataire->getRaisonSociale());
    }

    // ---------------------------------------------------------------
    // delete
    // ---------------------------------------------------------------

    public function testDeleteRemovesAndRedirects(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('remove');
        $em->expects($this->once())->method('flush');

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

        $response = $controller->delete($prestataire);

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

    // ---------------------------------------------------------------
    // addFacture
    // ---------------------------------------------------------------

    public function testAddFactureRedirectsOnInvalidData(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->never())->method('persist');

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

        // Missing numFacture
        $request = new Request([], ['numFacture' => '', 'year' => 2026, 'month' => 3]);
        $response = $controller->addFacture($prestataire, $request);

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

    public function testAddFactureRedirectsOnInvalidYear(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->never())->method('persist');

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

        $request = new Request([], ['numFacture' => 'FAC001', 'year' => 2010, 'month' => 3, 'montantHt' => '100', 'montantTtc' => '120']);
        $response = $controller->addFacture($prestataire, $request);

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

    public function testAddFacturePersistsAndRedirects(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('persist');
        $em->expects($this->once())->method('flush');

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

        $request = new Request([], [
            'numFacture' => 'FAC-2026-001',
            'year' => 2026,
            'month' => 3,
            'montantHt' => '500.00',
            'montantTtc' => '600.00',
        ]);
        $response = $controller->addFacture($prestataire, $request);

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

    // ---------------------------------------------------------------
    // markPaid
    // ---------------------------------------------------------------

    public function testMarkPaidFlushesAndRedirects(): void
    {
        $prestataire = $this->buildPrestataire(1);

        $facture = $this->createMock(FacturePrestataire::class);
        $facture->method('getPrestataire')->willReturn($prestataire);
        $facture->method('getNumFacture')->willReturn('FAC-001');

        $factureRepo = $this->createMock(EntityRepository::class);
        $factureRepo->method('find')->with(42)->willReturn($facture);

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

        $controller = $this->buildController($em);
        $response = $controller->markPaid($prestataire, 42);

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

    public function testMarkPaidIgnoresMismatchedPrestataire(): void
    {
        $prestataire = $this->buildPrestataire(1);
        $otherPrestataire = $this->buildPrestataire(2, 'Other');

        $facture = $this->createMock(FacturePrestataire::class);
        $facture->method('getPrestataire')->willReturn($otherPrestataire);

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

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

        $controller = $this->buildController($em);
        $response = $controller->markPaid($prestataire, 99);

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

    public function testMarkPaidWhenFactureNotFound(): void
    {
        $prestataire = $this->buildPrestataire(1);

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

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

        $controller = $this->buildController($em);
        $response = $controller->markPaid($prestataire, 999);

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

    // ---------------------------------------------------------------
    // deleteFacture
    // ---------------------------------------------------------------

    public function testDeleteFactureRemovesAndRedirects(): void
    {
        $prestataire = $this->buildPrestataire(1);

        $facture = $this->createMock(FacturePrestataire::class);
        $facture->method('getPrestataire')->willReturn($prestataire);

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

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

        $controller = $this->buildController($em);
        $response = $controller->deleteFacture($prestataire, 42);

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

    public function testDeleteFactureWhenNotFound(): void
    {
        $prestataire = $this->buildPrestataire(1);

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

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($factureRepo);
        $em->expects($this->never())->method('remove');

        $controller = $this->buildController($em);
        $response = $controller->deleteFacture($prestataire, 999);

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

    public function testDeleteFactureIgnoresMismatchedPrestataire(): void
    {
        $prestataire = $this->buildPrestataire(1);
        $otherPrestataire = $this->buildPrestataire(2, 'Other SA');

        $facture = $this->createMock(FacturePrestataire::class);
        $facture->method('getPrestataire')->willReturn($otherPrestataire);

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

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($factureRepo);
        $em->expects($this->never())->method('remove');
        $em->expects($this->never())->method('flush');

        $controller = $this->buildController($em);
        $response = $controller->deleteFacture($prestataire, 99);

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

    // ---------------------------------------------------------------
    // entrepriseSearch
    // ---------------------------------------------------------------

    public function testEntrepriseSearchReturnsEmptyWhenQueryTooShort(): void
    {
        $svc = $this->createMock(\App\Service\EntrepriseSearchService::class);
        $svc->method('search')->willReturn(new JsonResponse(['results' => [], 'total_results' => 0]));

        $controller = $this->buildController();
        $response = $controller->entrepriseSearch(new Request(['q' => 'a']), $svc);

        $this->assertSame(200, $response->getStatusCode());
        $data = json_decode($response->getContent(), true);
        $this->assertSame(0, $data['total_results']);
    }

    public function testEntrepriseSearchForwardsApiResponse(): void
    {
        $svc = $this->createMock(\App\Service\EntrepriseSearchService::class);
        $svc->method('search')->willReturn(new JsonResponse(['results' => [['nom_complet' => 'ACME SA']], 'total_results' => 1]));

        $controller = $this->buildController();
        $response = $controller->entrepriseSearch(new Request(['q' => 'ACME']), $svc);

        $this->assertSame(200, $response->getStatusCode());
        $data = json_decode($response->getContent(), true);
        $this->assertSame(1, $data['total_results']);
    }

    public function testEntrepriseSearchHandlesHttpError(): void
    {
        $svc = $this->createMock(\App\Service\EntrepriseSearchService::class);
        $svc->method('search')->willReturn(new JsonResponse(['error' => 'Service indisponible'], 502));

        $controller = $this->buildController();
        $response = $controller->entrepriseSearch(new Request(['q' => 'ACME']), $svc);

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

    // ---------------------------------------------------------------
    // addFacture — month boundary validation
    // ---------------------------------------------------------------

    public function testAddFactureRedirectsOnInvalidMonth(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->never())->method('persist');

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

        // month = 13 is invalid
        $request = new Request([], ['numFacture' => 'FAC001', 'year' => 2026, 'month' => 13, 'montantHt' => '100', 'montantTtc' => '120']);
        $response = $controller->addFacture($prestataire, $request);

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

    public function testAddFactureRedirectsOnZeroMonth(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->never())->method('persist');

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

        // month = 0 is invalid
        $request = new Request([], ['numFacture' => 'FAC002', 'year' => 2026, 'month' => 0, 'montantHt' => '50', 'montantTtc' => '60']);
        $response = $controller->addFacture($prestataire, $request);

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

    public function testAddFactureWithValidFileUpload(): void
    {
        // Create a real temporary PDF file to simulate a valid UploadedFile
        $tmpFile = tempnam(sys_get_temp_dir(), 'presta_pdf_');
        file_put_contents($tmpFile, '%PDF-1.4 test content');

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

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

        $uploadedFile = new \Symfony\Component\HttpFoundation\File\UploadedFile(
            $tmpFile,
            'facture.pdf',
            'application/pdf',
            null,
            true // test mode — bypass is_uploaded_file check
        );

        $request = new Request([], [
            'numFacture' => 'FAC-UPLOAD-001',
            'year' => 2026,
            'month' => 4,
            'montantHt' => '300.00',
            'montantTtc' => '360.00',
        ]);
        $request->files->set('facturePdf', $uploadedFile);

        $response = $controller->addFacture($prestataire, $request);

        @unlink($tmpFile);

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

    public function testCreateWithAllFields(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('persist');
        $em->expects($this->once())->method('flush');

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

        $request = new Request([], [
            'raisonSociale' => 'Full SARL',
            'siret' => '12345678901234',
            'email' => 'full@example.com',
            'phone' => '0600000000',
            'address' => '1 rue des Tests',
            'zipCode' => '75001',
            'city' => 'Paris',
        ]);
        $response = $controller->create($request);

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