<?php

namespace App\Tests\Service;

use App\Entity\AnalyticsEvent;
use App\Entity\AnalyticsUniqId;
use App\Entity\Attestation;
use App\Service\DocuSealService;
use App\Service\MailerService;
use App\Service\RgpdService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;

class RgpdServiceTest extends TestCase
{
    private EntityManagerInterface $em;
    private Environment $twig;
    private DocuSealService $docuSealService;
    private MailerService $mailer;
    private UrlGeneratorInterface $urlGenerator;
    private string $projectDir;
    private RgpdService $service;

    protected function setUp(): void
    {
        $this->em = $this->createStub(EntityManagerInterface::class);
        $this->twig = $this->createStub(Environment::class);
        $this->docuSealService = $this->createStub(DocuSealService::class);
        $this->mailer = $this->createStub(MailerService::class);
        $this->urlGenerator = $this->createStub(UrlGeneratorInterface::class);
        $this->projectDir = sys_get_temp_dir() . '/rgpd_test_' . uniqid();
        mkdir($this->projectDir);
        mkdir($this->projectDir . '/public');
        touch($this->projectDir . '/public/logo_facture.png');

        $this->service = new RgpdService(
            $this->em,
            $this->twig,
            $this->docuSealService,
            $this->mailer,
            $this->urlGenerator,
            $this->projectDir,
            'secret'
        );
    }

    protected function tearDown(): void
    {
        $this->removeDir($this->projectDir);
    }

    private function removeDir(string $dir): void
    {
        if (!is_dir($dir)) return;
        $files = array_diff(scandir($dir), ['.', '..']);
        foreach ($files as $file) {
            $path = $dir . '/' . $file;
            is_dir($path) ? $this->removeDir($path) : unlink($path);
        }
        rmdir($dir);
    }

    public function testHandleAccessRequestNoData(): void
    {
        $ip = '127.0.0.1';
        $email = 'test@example.com';

        $repository = $this->createStub(EntityRepository::class);
        $repository->method('findBy')->willReturn([]);
        
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repository);

        $service = new RgpdService($em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 's');
        $result = $service->handleAccessRequest($ip, $email);

        $this->assertFalse($result['found']);
        $this->assertEquals(0, $result['count']);
    }

    public function testHandleAccessRequestWithData(): void
    {
        $ip = '127.0.0.1';
        $email = 'test@example.com';

        $visitor = new AnalyticsUniqId();
        $visitorRepository = $this->createStub(EntityRepository::class);
        $visitorRepository->method('findBy')->willReturn([$visitor]);

        $eventRepository = $this->createStub(EntityRepository::class);
        $eventRepository->method('findBy')->willReturn([]);

        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturnMap([
            [AnalyticsUniqId::class, $visitorRepository],
            [AnalyticsEvent::class, $eventRepository],
        ]);

        $service = new RgpdService($em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 's');
        $result = $service->handleAccessRequest($ip, $email);

        $this->assertTrue($result['found']);
        $this->assertEquals(1, $result['count']);
    }

    public function testHandleDeletionRequest(): void
    {
        $ip = '127.0.0.1';
        $email = 'test@example.com';

        $visitor = new AnalyticsUniqId();
        $visitorRepository = $this->createStub(EntityRepository::class);
        $visitorRepository->method('findBy')->willReturn([$visitor]);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($visitorRepository);
        $em->expects($this->once())->method('remove')->with($visitor);
        
        $service = new RgpdService($em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 's');
        $result = $service->handleDeletionRequest($ip, $email);

        $this->assertTrue($result['found']);
        $this->assertEquals(1, $result['deleted']);
    }

    public function testHandleDeletionRequestNoData(): void
    {
        $ip = '127.0.0.1';
        $email = 'test@example.com';

        $repository = $this->createStub(EntityRepository::class);
        $repository->method('findBy')->willReturn([]);
        
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repository);

        $service = new RgpdService($em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 's');
        $result = $service->handleDeletionRequest($ip, $email);

        $this->assertFalse($result['found']);
        $this->assertEquals(0, $result['deleted']);
    }

    public function testHandleAccessRequestWithoutLogo(): void
    {
        unlink($this->projectDir . '/public/logo_facture.png');
        $ip = '127.0.0.1';
        $email = 'test@example.com';

        $repository = $this->createStub(EntityRepository::class);
        $repository->method('findBy')->willReturn([]);
        
        $em = $this->createStub(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repository);
        
        $service = new RgpdService($em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 's');
        $result = $service->handleAccessRequest($ip, $email);
        $this->assertFalse($result['found']);
    }

    public function testHandleAccessRequestWithExistingDir(): void
    {
        mkdir($this->projectDir . '/var/rgpd', 0777, true);
        $ip = '127.0.0.1';
        $email = 'test@example.com';

        $repository = $this->createStub(EntityRepository::class);
        $repository->method('findBy')->willReturn([]);

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

        $service = new RgpdService($em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 's');
        $result = $service->handleAccessRequest($ip, $email);
        $this->assertFalse($result['found']);
    }

    // --- sendVerificationCode ---

    public function testSendVerificationCodeCallsMailer(): void
    {
        $this->twig->method('render')->willReturn('<html>Code: 123456</html>');

        $mailer = $this->createMock(MailerService::class);
        $mailer->expects($this->once())->method('sendEmail');

        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $mailer, $this->urlGenerator, $this->projectDir, 'secret');
        $service->sendVerificationCode('test@example.com', '127.0.0.1', 'access');

        // Code file should be created
        $codesDir = $this->projectDir . '/var/rgpd/codes';
        $this->assertDirectoryExists($codesDir);
    }

    public function testSendVerificationCodeForDeletion(): void
    {
        $this->twig->method('render')->willReturn('<html>Code: 654321</html>');

        $mailer = $this->createMock(MailerService::class);
        $mailer->expects($this->once())->method('sendEmail');

        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $mailer, $this->urlGenerator, $this->projectDir, 'secret');
        $service->sendVerificationCode('test@example.com', '127.0.0.1', 'deletion');

        $this->addToAssertionCount(1);
    }

    // --- verifyCode ---

    public function testVerifyCodeReturnsFalseIfNoFile(): void
    {
        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 'secret');
        $result = $service->verifyCode('test@example.com', '127.0.0.1', 'access', '123456');

        $this->assertFalse($result);
    }

    public function testVerifyCodeReturnsTrueForCorrectCode(): void
    {
        $this->twig->method('render')->willReturn('<html>ok</html>');

        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 'secret');

        // Send a code to create the file; we'll intercept the actual code from the file
        $service->sendVerificationCode('verify@example.com', '10.0.0.1', 'access');

        // Read the created code file to get the actual code
        $codesDir = $this->projectDir . '/var/rgpd/codes';
        $files = glob($codesDir . '/*.json');
        $this->assertNotEmpty($files);

        $data = json_decode(file_get_contents($files[0]), true);
        $code = $data['code'];

        $result = $service->verifyCode('verify@example.com', '10.0.0.1', 'access', $code);

        $this->assertTrue($result);
    }

    public function testVerifyCodeReturnsFalseForWrongCode(): void
    {
        $this->twig->method('render')->willReturn('<html>ok</html>');

        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 'secret');
        $service->sendVerificationCode('wrong@example.com', '10.0.0.2', 'access');

        $result = $service->verifyCode('wrong@example.com', '10.0.0.2', 'access', '000000');

        $this->assertFalse($result);
    }

    public function testVerifyCodeReturnsFalseIfExpired(): void
    {
        $codesDir = $this->projectDir . '/var/rgpd/codes';
        if (!is_dir($codesDir)) {
            mkdir($codesDir, 0755, true);
        }

        // Write an already-expired code file
        $codeHash = hash('sha256', 'expired@example.com|127.0.0.1|access|secret');
        $filePath = $codesDir . '/' . $codeHash . '.json';
        file_put_contents($filePath, json_encode([
            'code' => '999999',
            'hash' => 'ignored',
            'expires' => time() - 1, // expired 1 second ago
        ]));

        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 'secret');
        $result = $service->verifyCode('expired@example.com', '127.0.0.1', 'access', '999999');

        $this->assertFalse($result);
    }

    public function testVerifyCodeReturnsFalseForInvalidJson(): void
    {
        $codesDir = $this->projectDir . '/var/rgpd/codes';
        if (!is_dir($codesDir)) {
            mkdir($codesDir, 0755, true);
        }

        $codeHash = hash('sha256', 'bad@example.com|127.0.0.1|access|secret');
        $filePath = $codesDir . '/' . $codeHash . '.json';
        file_put_contents($filePath, 'not valid json');

        $service = new RgpdService($this->em, $this->twig, $this->docuSealService, $this->mailer, $this->urlGenerator, $this->projectDir, 'secret');
        $result = $service->verifyCode('bad@example.com', '127.0.0.1', 'access', '000000');

        $this->assertFalse($result);
    }
}
