<?php

namespace App\Tests\Controller\Admin;

use App\Controller\Admin\SyncController;
use App\Entity\Customer;
use App\Entity\PriceAutomatic;
use App\Entity\Revendeur;
use App\Entity\StripeWebhookSecret;
use App\Repository\CustomerRepository;
use App\Repository\PriceAutomaticRepository;
use App\Repository\RevendeurRepository;
use App\Repository\StripeWebhookSecretRepository;
use App\Service\MeilisearchService;
use App\Service\StripePriceService;
use App\Service\StripeWebhookService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
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 SyncControllerTest extends TestCase
{
    private function createContainer(): ContainerInterface
    {
        $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('/admin/sync');

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

        return $container;
    }

    private function createPriceWithStripe(): PriceAutomatic
    {
        $price = new PriceAutomatic();
        $price->setType('esy-web');
        $price->setTitle('Esy-Web');
        $price->setPriceHt('100.00');
        $price->setStripeId('price_abc123');

        return $price;
    }

    private function createPriceWithoutStripe(): PriceAutomatic
    {
        $price = new PriceAutomatic();
        $price->setType('esy-mail');
        $price->setTitle('Esy-Mail');
        $price->setPriceHt('50.00');

        return $price;
    }

    public function testIndexWithMixedPrices(): void
    {
        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('count')->willReturn(10);

        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('count')->willReturn(3);

        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willReturn([
            $this->createPriceWithStripe(),
            $this->createPriceWithoutStripe(),
        ]);

        $secretRepo = $this->createStub(StripeWebhookSecretRepository::class);
        $secretRepo->method('findAll')->willReturn([]);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $contactRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $contactRepo->method('count')->willReturn(0);
        $em = $this->createStub(\Doctrine\ORM\EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($contactRepo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $response = $controller->index($customerRepo, $revendeurRepo, $priceRepo, $secretRepo, $em, $meilisearch);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testSyncCustomersSuccess(): void
    {
        $customer = $this->createStub(Customer::class);

        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('findAll')->willReturn([$customer]);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncCustomers($customerRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncCustomersError(): void
    {
        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('findAll')->willThrowException(new \RuntimeException('Connection refused'));

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncCustomers($customerRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncRevendeursSuccess(): void
    {
        $revendeur = $this->createStub(Revendeur::class);

        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('findAll')->willReturn([$revendeur]);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncRevendeurs($revendeurRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncRevendeursError(): void
    {
        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('findAll')->willThrowException(new \RuntimeException('Timeout'));

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncRevendeurs($revendeurRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncPricesSuccess(): void
    {
        $price = $this->createPriceWithStripe();

        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willReturn([$price]);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncPrices($priceRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncPricesError(): void
    {
        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willThrowException(new \RuntimeException('Error'));

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncPrices($priceRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncStripeWebhooksEmptyUrl(): void
    {
        $webhookService = $this->createStub(StripeWebhookService::class);
        $secretRepo = $this->createStub(StripeWebhookSecretRepository::class);
        $em = $this->createStub(EntityManagerInterface::class);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncStripeWebhooks($webhookService, $secretRepo, $em, '');
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncStripeWebhooksCreatedNew(): void
    {
        $webhookService = $this->createStub(StripeWebhookService::class);
        $webhookService->method('createAllWebhooks')->willReturn([
            'created' => [
                ['type' => 'Main Light', 'id' => 'we_123', 'status' => 'created', 'secret' => 'whsec_abc'],
                ['type' => 'Main Instant', 'id' => 'we_456', 'status' => 'exists'],
            ],
            'errors' => [],
        ]);

        $secretRepo = $this->createStub(StripeWebhookSecretRepository::class);
        $secretRepo->method('findByType')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncStripeWebhooks($webhookService, $secretRepo, $em, 'https://crm.e-cosplay.fr');
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncStripeWebhooksUpdateExisting(): void
    {
        $existing = new StripeWebhookSecret(StripeWebhookSecret::TYPE_MAIN_LIGHT, 'old_secret', 'we_old');

        $webhookService = $this->createStub(StripeWebhookService::class);
        $webhookService->method('createAllWebhooks')->willReturn([
            'created' => [
                ['type' => 'Main Light', 'id' => 'we_new', 'status' => 'created', 'secret' => 'whsec_new'],
            ],
            'errors' => ['Connect webhook failed'],
        ]);

        $secretRepo = $this->createStub(StripeWebhookSecretRepository::class);
        $secretRepo->method('findByType')->willReturn($existing);

        $em = $this->createStub(EntityManagerInterface::class);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncStripeWebhooks($webhookService, $secretRepo, $em, 'https://crm.e-cosplay.fr/');
        $this->assertSame(302, $response->getStatusCode());
        $this->assertSame('whsec_new', $existing->getSecret());
        $this->assertSame('we_new', $existing->getEndpointId());
    }

    public function testSyncStripePricesNoErrors(): void
    {
        $stripePriceService = $this->createStub(StripePriceService::class);
        $stripePriceService->method('syncAll')->willReturn(['synced' => 5, 'errors' => []]);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncStripePrices($stripePriceService);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncStripePricesWithErrors(): void
    {
        $stripePriceService = $this->createStub(StripePriceService::class);
        $stripePriceService->method('syncAll')->willReturn(['synced' => 3, 'errors' => ['Price X failed', 'Price Y failed']]);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncStripePrices($stripePriceService);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncAllSuccess(): void
    {
        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('findAll')->willReturn([$this->createStub(Customer::class)]);

        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('findAll')->willReturn([$this->createStub(Revendeur::class)]);

        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willReturn([$this->createPriceWithStripe()]);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncAll($customerRepo, $revendeurRepo, $priceRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncAllError(): void
    {
        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('findAll')->willReturn([]);

        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('findAll')->willReturn([]);

        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willReturn([]);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('Meilisearch down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncAll($customerRepo, $revendeurRepo, $priceRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    private function createEntityRepo(array $items = []): \Doctrine\ORM\EntityRepository
    {
        $repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $repo->method('findAll')->willReturn($items);

        return $repo;
    }

    public function testSyncContactsSuccess(): void
    {
        $repo = $this->createEntityRepo([$this->createStub(\App\Entity\CustomerContact::class)]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncContacts($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncContactsError(): void
    {
        $repo = $this->createEntityRepo([]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncContacts($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncDomainsSuccess(): void
    {
        $repo = $this->createEntityRepo([$this->createStub(\App\Entity\Domain::class)]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncDomains($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncDomainsError(): void
    {
        $repo = $this->createEntityRepo([]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncDomains($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncWebsitesSuccess(): void
    {
        $repo = $this->createEntityRepo([$this->createStub(\App\Entity\Website::class)]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncWebsites($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncWebsitesError(): void
    {
        $repo = $this->createEntityRepo([]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncWebsites($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncDevisSuccess(): void
    {
        $repo = $this->createEntityRepo([$this->createStub(\App\Entity\Devis::class)]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncDevis($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncDevisError(): void
    {
        $repo = $this->createEntityRepo([]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncDevis($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncAdvertsSuccess(): void
    {
        $repo = $this->createEntityRepo([$this->createStub(\App\Entity\Advert::class)]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncAdverts($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncAdvertsError(): void
    {
        $repo = $this->createEntityRepo([]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncAdverts($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncFacturesSuccess(): void
    {
        $repo = $this->createEntityRepo([$this->createStub(\App\Entity\Facture::class)]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncFactures($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testSyncFacturesError(): void
    {
        $repo = $this->createEntityRepo([]);
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        $meilisearch = $this->createStub(MeilisearchService::class);
        $meilisearch->method('setupIndexes')->willThrowException(new \RuntimeException('down'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncFactures($em, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    public function testPurgeIndexesRedirects(): void
    {
        $meilisearch = $this->createStub(MeilisearchService::class);

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->purgeIndexes($meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }

    // ---------------------------------------------------------------
    // index — customers with/without stripeCustomerId (branch coverage)
    // ---------------------------------------------------------------

    public function testIndexWithCustomersHavingStripeIds(): void
    {
        $user1 = new \App\Entity\User();
        $user1->setEmail('a@test.com');
        $user1->setFirstName('A');
        $user1->setLastName('B');
        $user1->setPassword('h');
        $customer1 = new \App\Entity\Customer($user1);
        $customer1->setStripeCustomerId('cus_abc123');

        $user2 = new \App\Entity\User();
        $user2->setEmail('b@test.com');
        $user2->setFirstName('B');
        $user2->setLastName('C');
        $user2->setPassword('h');
        $customer2 = new \App\Entity\Customer($user2);
        // customer2 has no stripeCustomerId

        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('findAll')->willReturn([$customer1, $customer2]);

        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('count')->willReturn(2);

        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willReturn([
            $this->createPriceWithStripe(),
            $this->createPriceWithoutStripe(),
        ]);

        $secretRepo = $this->createStub(StripeWebhookSecretRepository::class);
        $secretRepo->method('findAll')->willReturn([]);

        $contactRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
        $contactRepo->method('count')->willReturn(0);
        $em = $this->createStub(\Doctrine\ORM\EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($contactRepo);

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

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->index($customerRepo, $revendeurRepo, $priceRepo, $secretRepo, $em, $meilisearch);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testSyncAllErrorDuringIndexing(): void
    {
        $customerRepo = $this->createStub(CustomerRepository::class);
        $customerRepo->method('findAll')->willReturn([$this->createStub(Customer::class)]);

        $revendeurRepo = $this->createStub(RevendeurRepository::class);
        $revendeurRepo->method('findAll')->willReturn([]);

        $priceRepo = $this->createStub(PriceAutomaticRepository::class);
        $priceRepo->method('findAll')->willReturn([]);

        $meilisearch = $this->createStub(MeilisearchService::class);
        // setupIndexes succeeds but indexCustomer throws
        $meilisearch->method('indexCustomer')->willThrowException(new \RuntimeException('Index error'));

        $controller = new SyncController();
        $controller->setContainer($this->createContainer());

        $response = $controller->syncAll($customerRepo, $revendeurRepo, $priceRepo, $meilisearch);
        $this->assertSame(302, $response->getStatusCode());
    }
}
