<?php

namespace App\Tests\Controller\Admin;

use App\Controller\Admin\StatsController;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
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 Twig\Environment;

class StatsControllerTest extends TestCase
{
    private function createEmWithQueryBuilder(): EntityManagerInterface
    {
        $stubEm = $this->createStub(EntityManagerInterface::class);
        $query = $this->getMockBuilder(Query::class)
            ->setConstructorArgs([$stubEm])
            ->onlyMethods(['getResult', '_doExecute', 'getSQL'])
            ->getMock();
        $query->method('getResult')->willReturn([]);

        $qb = $this->createStub(QueryBuilder::class);
        $qb->method('select')->willReturnSelf();
        $qb->method('from')->willReturnSelf();
        $qb->method('where')->willReturnSelf();
        $qb->method('andWhere')->willReturnSelf();
        $qb->method('setParameter')->willReturnSelf();
        $qb->method('getQuery')->willReturn($query);

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

        return $em;
    }

    private function setupController(StatsController $controller): void
    {
        $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>');

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturn(true);
        $container->method('get')->willReturnMap([
            ['twig', $twig],
            ['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(\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface::class)],
        ]);
        $controller->setContainer($container);
    }

    public function testIndexCurrentPeriod(): void
    {
        $controller = new StatsController($this->createEmWithQueryBuilder());
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testIndexCustomPeriod(): void
    {
        $controller = new StatsController($this->createEmWithQueryBuilder());
        $this->setupController($controller);

        $request = new Request(['period' => 'custom', 'from' => '2026-01-01', 'to' => '2026-03-31']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testIndexMonthsPeriod(): void
    {
        $controller = new StatsController($this->createEmWithQueryBuilder());
        $this->setupController($controller);

        $request = new Request(['period' => '3']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testIndexDefaultPeriod(): void
    {
        $controller = new StatsController($this->createEmWithQueryBuilder());
        $this->setupController($controller);

        $request = new Request();
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testIndexWithRichDataExercisesAllPrivateMethods(): void
    {
        // This test uses the simple EM (empty results) but exercises all branches
        // by verifying the controller handles empty-data cases in all private methods.
        $controller = new StatsController($this->createEmWithQueryBuilder());
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testIndexWith6MonthPeriodExercisesMonthlyEvolution(): void
    {
        $controller = new StatsController($this->createEmWithQueryBuilder());
        $this->setupController($controller);

        $request = new Request(['period' => '6']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // Tests avec données réelles pour couvrir les corps de boucles
    // ---------------------------------------------------------------

    /**
     * Construit un EM dont les résultats alternent selon un tableau de séquences.
     * Chaque appel à getResult() dépile le prochain résultat du tableau.
     *
     * @param list<list<object>> $resultSequence
     */
    private function createEmWithSequence(array $resultSequence): EntityManagerInterface
    {
        $stubEm = $this->createStub(EntityManagerInterface::class);

        $callIndex = 0;
        $query = $this->getMockBuilder(Query::class)
            ->setConstructorArgs([$stubEm])
            ->onlyMethods(['getResult', '_doExecute', 'getSQL'])
            ->getMock();
        $query->method('getResult')->willReturnCallback(
            function () use ($resultSequence, &$callIndex) {
                $result = $resultSequence[$callIndex] ?? [];
                ++$callIndex;

                return $result;
            }
        );

        $qb = $this->createStub(QueryBuilder::class);
        $qb->method('select')->willReturnSelf();
        $qb->method('from')->willReturnSelf();
        $qb->method('where')->willReturnSelf();
        $qb->method('andWhere')->willReturnSelf();
        $qb->method('setParameter')->willReturnSelf();
        $qb->method('getQuery')->willReturn($query);

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

        return $em;
    }

    private function buildPaidFacture(): \App\Entity\Facture
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $facture = new \App\Entity\Facture($orderNumber, 'secret');
        $facture->setTotalHt('100.00');
        $facture->setTotalTva('20.00');
        $facture->setTotalTtc('120.00');
        $facture->setIsPaid(true);
        $facture->setPaidAt(new \DateTimeImmutable('2026-04-01'));

        return $facture;
    }

    private function buildUnpaidFacture(): \App\Entity\Facture
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00002');
        $facture = new \App\Entity\Facture($orderNumber, 'secret');
        $facture->setTotalHt('50.00');
        $facture->setTotalTva('0.00');
        $facture->setTotalTtc('50.00');

        return $facture;
    }

    /**
     * Correct call order in index():
     *   1. getFactureStats       (line 51)  → 1 query
     *   2. getPaymentStats       (line 54)  → 1 query
     *   3. getCoutPrestataire    (line 63)  → 1 query
     *   4. getPaymentsByMethod   (line 87)  → 1 query
     *   5. getMonthlyEvolution   (line 91)  → 6 queries
     *   6. getServiceStats       (line 93)  → 1 query
     * Total: 11 calls
     */

    /**
     * Teste index() avec des factures payées et impayées — exercice getFactureStats.
     */
    public function testIndexWithPaidAndUnpaidFacturesExercisesGetFactureStats(): void
    {
        $paidFacture = $this->buildPaidFacture();
        $unpaidFacture = $this->buildUnpaidFacture();

        $sequence = [
            [$paidFacture, $unpaidFacture],  // 1. getFactureStats
            [],                              // 2. getPaymentStats
            [],                              // 3. getCoutPrestataire

            [], [], [], [], [], [],           // 5. getMonthlyEvolution (6 months)
            [$paidFacture],                  // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste index() avec des AdvertPayments — exercice getPaymentStats et getPaymentsByMethod.
     */
    public function testIndexWithAdvertPaymentsExercisesPaymentStats(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $advert = new \App\Entity\Advert($orderNumber, 'secret');
        $payment = new \App\Entity\AdvertPayment($advert, \App\Entity\AdvertPayment::TYPE_SUCCESS, '120.00');
        $payment->setMethod('card');

        $paymentNoMethod = new \App\Entity\AdvertPayment($advert, \App\Entity\AdvertPayment::TYPE_SUCCESS, '50.00');
        // method null -> tests the 'inconnu' branch in getPaymentsByMethod

        $sequence = [
            [],                           // 1. getFactureStats
            [$payment, $paymentNoMethod], // 2. getPaymentStats
            [],                           // 3. getCoutPrestataire

            [], [], [], [], [], [],        // 5. getMonthlyEvolution
            [],                           // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste index() avec des lignes de factures — exercice getServiceStats.
     */
    public function testIndexWithFactureLinesExercisesGetServiceStats(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00003');
        $facture = new \App\Entity\Facture($orderNumber, 'secret');
        $facture->setTotalHt('200.00');
        $facture->setTotalTva('0.00');
        $facture->setTotalTtc('200.00');
        $facture->setIsPaid(true);

        $user = new \App\Entity\User();
        $user->setEmail('c@t.com');
        $user->setFirstName('A');
        $user->setLastName('B');
        $user->setPassword('h');
        $customer = new \App\Entity\Customer($user);
        $ref = new \ReflectionProperty(\App\Entity\Customer::class, 'id');
        $ref->setAccessible(true);
        $ref->setValue($customer, 42);
        $facture->setCustomer($customer);

        // Lignes de différents types pour couvrir tous les groupes
        $lineWebsite = new \App\Entity\FactureLine($facture, 'E-Site Standard', '100.00');
        $lineWebsite->setType('website');

        $lineNddRenew = new \App\Entity\FactureLine($facture, 'Renouvellement test.fr', '15.00');
        $lineNddRenew->setType('ndd');

        $lineNddGestion = new \App\Entity\FactureLine($facture, 'Gestion DNS', '5.00');
        $lineNddGestion->setType('ndd');

        $lineUnknown = new \App\Entity\FactureLine($facture, 'Service divers', '30.00');
        $lineUnknown->setType('unknown_xyz'); // rebascule sur 'other'

        $lineEsymail = new \App\Entity\FactureLine($facture, 'E-Mail Pro', '50.00');
        $lineEsymail->setType('esymail');

        $facture->addLine($lineWebsite);
        $facture->addLine($lineNddRenew);
        $facture->addLine($lineNddGestion);
        $facture->addLine($lineUnknown);
        $facture->addLine($lineEsymail);

        $sequence = [
            [$facture], // 1. getFactureStats
            [],         // 2. getPaymentStats
            [],         // 3. getCoutPrestataire

            [], [], [], [], [], [],  // 5. getMonthlyEvolution
            [$facture], // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste index() avec des FacturePrestataire — exercice getCoutPrestataire.
     */
    public function testIndexWithFacturePrestataireExercisesGetCoutPrestataire(): void
    {
        $fp = $this->createStub(\App\Entity\FacturePrestataire::class);
        $fp->method('getMontantHt')->willReturn('500.00');

        $sequence = [
            [],    // 1. getFactureStats
            [],    // 2. getPaymentStats
            [$fp], // 3. getCoutPrestataire

            [], [], [], [], [], [],  // 5. getMonthlyEvolution
            [],    // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste le status "Surplus" de resolveStatus (ratio > 0.3).
     */
    public function testIndexStatusSurplus(): void
    {
        // CA = 1000, cout total faible -> marge > 30% => Surplus
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00010');
        $facture = new \App\Entity\Facture($orderNumber, 'secret');
        $facture->setTotalHt('1000.00');
        $facture->setTotalTva('0.00');
        $facture->setTotalTtc('1000.00');
        $facture->setIsPaid(true);

        $sequence = [
            [$facture], // 1. getFactureStats
            [],         // 2. getPaymentStats
            [],         // 3. getCoutPrestataire

            [], [], [], [], [], [],  // 5. getMonthlyEvolution
            [$facture], // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste le status "Negatif" de resolveStatus (marge < 0).
     */
    public function testIndexStatusNegatif(): void
    {
        // CA très faible, beaucoup de coûts -> negatif
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00011');
        $facture = new \App\Entity\Facture($orderNumber, 'secret');
        $facture->setTotalHt('1.00');
        $facture->setTotalTva('0.00');
        $facture->setTotalTtc('1.00');
        $facture->setIsPaid(true);

        // FacturePrestataire avec un gros montant pour créer un déficit
        $fp = $this->createStub(\App\Entity\FacturePrestataire::class);
        $fp->method('getMontantHt')->willReturn('999.00');

        $sequence = [
            [$facture], // 1. getFactureStats
            [],         // 2. getPaymentStats
            [$fp],      // 3. getCoutPrestataire

            [], [], [], [], [], [],  // 5. getMonthlyEvolution
            [$facture], // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste le status quand caHt <= 0 dans resolveStatus.
     */
    public function testIndexStatusRentableZeroCa(): void
    {
        // Aucune facture payée -> caHt = 0 -> resolveStatus retourne Rentable
        $sequence = array_fill(0, 11, []);

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }

    /**
     * Teste la branche getMonthlyEvolution avec des factures pour les 6 derniers mois.
     */
    public function testIndexMonthlyEvolutionWithPaidFactures(): void
    {
        $paidFacture = $this->buildPaidFacture();

        $sequence = [
            [],                                                      // 1. getFactureStats
            [],                                                      // 2. getPaymentStats
            [],                                                      // 3. getCoutPrestataire

            [$paidFacture], [$paidFacture], [$paidFacture], [], [], [], // 5. getMonthlyEvolution
            [],                                                      // 6. getServiceStats
        ];

        $em = $this->createEmWithSequence($sequence);
        $controller = new StatsController($em);
        $this->setupController($controller);

        $request = new Request(['period' => 'current']);
        $response = $controller->index($request);
        $this->assertSame(200, $response->getStatusCode());
    }
}
