<?php

namespace App\Tests\Controller;

use App\Controller\WebhookDocuSealController;
use App\Entity\Attestation;
use App\Entity\Customer;
use App\Entity\Devis;
use App\Entity\DocusealEvent;
use App\Entity\OrderNumber;
use App\Entity\User;
use App\Repository\AttestationRepository;
use App\Repository\DevisRepository;
use App\Service\DocuSealService;
use App\Service\MailerService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Twig\Environment;

class WebhookDocuSealControllerTest extends TestCase
{
    private function invoke(array $payload, string $secret = 'test', string $headerSecret = 'X-Sign', string $headerValue = 'test'): JsonResponse
    {
        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);
        $twig->method('render')->willReturn('<html></html>');

        $request = new Request([], [], [], [], [], [], json_encode($payload));
        $request->headers->set($headerSecret, $headerValue);
        $request->headers->set('Content-Type', 'application/json');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        return $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, $headerSecret, $secret, '/tmp');
    }

    public function testUnauthorized(): void
    {
        $response = $this->invoke([], 'correct-secret', 'X-Sign', 'wrong-secret');
        $this->assertSame(401, $response->getStatusCode());
    }

    public function testInvalidPayload(): void
    {
        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], 'invalid json{{{');
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
        $this->assertSame(400, $response->getStatusCode());
    }

    public function testIgnoredDocType(): void
    {
        $response = $this->invoke([
            'event_type' => 'form.viewed',
            'data' => ['id' => 1, 'metadata' => ['doc_type' => 'other']],
        ]);

        $data = json_decode($response->getContent(), true);
        $this->assertSame('ignored', $data['status']);
    }

    public function testEmptySecret(): void
    {
        $response = $this->invoke(['event_type' => 'form.viewed', 'data' => []], '', 'X-Sign', '');
        // Empty secret = bypass verification
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testFormViewedAttestationNotFound(): void
    {
        $response = $this->invoke([
            'event_type' => 'form.viewed',
            'data' => ['id' => 999, 'metadata' => ['doc_type' => 'attestation', 'reference' => 'REF-123']],
        ]);

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

    public function testFormViewedAttestationFound(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.viewed',
            'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation', 'reference' => $attestation->getReference()]],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testFormStarted(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.started',
            'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation']],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testFormDeclined(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.declined',
            'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation']],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testUnknownEvent(): void
    {
        $response = $this->invoke([
            'event_type' => 'form.unknown',
            'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation']],
        ]);

        $data = json_decode($response->getContent(), true);
        $this->assertSame('ignored', $data['status']);
    }

    public function testFormCompletedAttestationNotFound(): void
    {
        $response = $this->invoke([
            'event_type' => 'form.completed',
            'data' => ['id' => 999, 'metadata' => ['doc_type' => 'attestation', 'reference' => 'REF']],
        ]);

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

    public function testFormCompletedSuccess(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);
        $twig->method('render')->willReturn('<html></html>');

        $tmpDir = sys_get_temp_dir().'/docuseal_test_'.uniqid();
        mkdir($tmpDir);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.completed',
            'data' => [
                'id' => 1,
                'metadata' => ['doc_type' => 'attestation', 'reference' => $attestation->getReference()],
                'documents' => [],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', $tmpDir);
        $this->assertSame(200, $response->getStatusCode());

        $data = json_decode($response->getContent(), true);
        $this->assertSame('completed', $data['event']);
        $this->assertSame('sent', $attestation->getStatus());

        @rmdir($tmpDir.'/var/rgpd/signed');
        @rmdir($tmpDir.'/var/rgpd');
        @rmdir($tmpDir.'/var');
        @rmdir($tmpDir);
    }

    public function testBodySecretVerification(): void
    {
        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.viewed',
            'data' => ['id' => 1, 'metadata' => ['doc_type' => 'other']],
            'secret' => 'body-secret',
        ]));
        $request->headers->set('X-Sign', 'wrong');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'body-secret', '/tmp');
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testSyncSubmitterIdFromMetadata(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        // First call by submitterId returns null, then by reference returns attestation,
        // then by submitterId again returns attestation (after sync)
        $repo->method('findOneBy')->willReturnCallback(function (array $criteria) use ($attestation) {
            if (isset($criteria['reference'])) {
                return $attestation;
            }
            if (isset($criteria['submitterId']) && 42 === $criteria['submitterId']) {
                // After sync, return the attestation
                return $attestation->getSubmitterId() === 42 ? $attestation : null;
            }

            return null;
        });

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.viewed',
            'data' => [
                'id' => 42,
                'metadata' => ['doc_type' => 'attestation', 'reference' => $attestation->getReference()],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
        // The submitterId is synced, then form.viewed is handled
        $this->assertSame(42, $attestation->getSubmitterId());
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testDocusealEventPersistedOnFormViewed(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $persisted = [];
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('persist')->willReturnCallback(function ($e) use (&$persisted) { $persisted[] = $e; });
        $em->method('flush');

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.viewed',
            'data' => [
                'id' => 622,
                'submission_id' => 465,
                'metadata' => ['doc_type' => 'devis', 'num_order' => '04/2026-55501'],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');

        $events = array_values(array_filter($persisted, fn ($e) => $e instanceof DocusealEvent));
        $this->assertCount(1, $events);
        $this->assertSame('devis', $events[0]->getType());
        $this->assertSame('form.viewed', $events[0]->getEventType());
        $this->assertSame(465, $events[0]->getSubmissionId());
        $this->assertSame(622, $events[0]->getSubmitterId());
    }

    public function testDocusealEventPersistedOnFormStarted(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $persisted = [];
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('persist')->willReturnCallback(function ($e) use (&$persisted) { $persisted[] = $e; });
        $em->method('flush');

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.started',
            'data' => [
                'id' => 1,
                'submission_id' => 10,
                'metadata' => ['doc_type' => 'attestation'],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');

        $events = array_values(array_filter($persisted, fn ($e) => $e instanceof DocusealEvent));
        $this->assertCount(1, $events);
        $this->assertSame('form.started', $events[0]->getEventType());
    }

    public function testDocusealEventPersistedOnFormDeclined(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $persisted = [];
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('persist')->willReturnCallback(function ($e) use (&$persisted) { $persisted[] = $e; });
        $em->method('flush');

        $controller = new WebhookDocuSealController();
        $mailer = $this->createStub(MailerService::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.declined',
            'data' => [
                'id' => 1,
                'submission_id' => 11,
                'metadata' => ['doc_type' => 'attestation'],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');

        $events = array_values(array_filter($persisted, fn ($e) => $e instanceof DocusealEvent));
        $this->assertCount(1, $events);
        $this->assertSame('form.declined', $events[0]->getEventType());
    }

    public function testDocusealEventPersistedOnFormCompleted(): void
    {
        $attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');

        $repo = $this->createStub(AttestationRepository::class);
        $repo->method('findOneBy')->willReturn($attestation);

        $persisted = [];
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('persist')->willReturnCallback(function ($e) use (&$persisted) { $persisted[] = $e; });
        $em->method('flush');

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

        $tmpDir = sys_get_temp_dir().'/docuseal_event_'.uniqid();
        mkdir($tmpDir);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.completed',
            'data' => [
                'id' => 1,
                'submission_id' => 12,
                'metadata' => ['doc_type' => 'attestation', 'reference' => $attestation->getReference()],
                'documents' => [],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $devisRepo = $this->createStub(DevisRepository::class);
        $docuSealService = $this->createStub(DocuSealService::class);
        $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', $tmpDir);

        $events = array_values(array_filter($persisted, fn ($e) => $e instanceof DocusealEvent));
        $this->assertCount(1, $events);
        $this->assertSame('form.completed', $events[0]->getEventType());
        $this->assertSame(12, $events[0]->getSubmissionId());

        @rmdir($tmpDir.'/var/rgpd/signed');
        @rmdir($tmpDir.'/var/rgpd');
        @rmdir($tmpDir);
    }

    public function testFormStartedNotFound(): void
    {
        $response = $this->invoke([
            'event_type' => 'form.started',
            'data' => ['id' => 999, 'metadata' => ['doc_type' => 'attestation', 'reference' => 'REF']],
        ]);

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

    public function testFormDeclinedNotFound(): void
    {
        $response = $this->invoke([
            'event_type' => 'form.declined',
            'data' => ['id' => 999, 'metadata' => ['doc_type' => 'attestation', 'reference' => 'REF']],
        ]);

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

    private function createDevis(): Devis
    {
        $user = new User();
        $user->setEmail('c@t.com');
        $user->setFirstName('C');
        $user->setLastName('T');
        $user->setPassword('h');
        $customer = new Customer($user);
        $orderNumber = new OrderNumber('04/2026-55501');

        return new Devis($orderNumber, 'secret');
    }

    public function testDevisFormCompletedSetsAccepted(): void
    {
        $devis = $this->createDevis();

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

        $docuSealService = $this->createMock(DocuSealService::class);
        $docuSealService->expects($this->once())->method('downloadSignedDevis')->with($devis);

        $em = $this->createStub(EntityManagerInterface::class);
        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.completed',
            'data' => [
                'id' => 622,
                'submission_id' => 465,
                'metadata' => ['doc_type' => 'devis', 'devis_id' => 1],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');

        $this->assertSame(200, $response->getStatusCode());
        $this->assertSame(Devis::STATE_ACCEPTED, $devis->getState());
    }

    public function testDevisFormDeclinedSetsRefused(): void
    {
        $devis = $this->createDevis();

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

        $docuSealService = $this->createStub(DocuSealService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.declined',
            'data' => [
                'id' => 622,
                'submission_id' => 465,
                'metadata' => ['doc_type' => 'devis', 'devis_id' => 1],
                'decline_reason' => 'Trop cher',
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');

        $this->assertSame(200, $response->getStatusCode());
        $this->assertSame(Devis::STATE_REFUSED, $devis->getState());
        $this->assertSame('Trop cher', $devis->getRaisonMessage());
    }

    public function testDevisNotFoundReturns404(): void
    {
        $devisRepo = $this->createStub(DevisRepository::class);
        // find() et findOneBy() retournent null par defaut

        $docuSealService = $this->createStub(DocuSealService::class);
        $em = $this->createStub(EntityManagerInterface::class);
        $controller = new WebhookDocuSealController();
        $repo = $this->createStub(AttestationRepository::class);
        $mailer = $this->createStub(MailerService::class);
        $twig = $this->createStub(Environment::class);

        $request = new Request([], [], [], [], [], [], json_encode([
            'event_type' => 'form.completed',
            'data' => [
                'id' => 999,
                'metadata' => ['doc_type' => 'devis', 'devis_id' => 999],
            ],
        ]));
        $request->headers->set('X-Sign', 'test');

        $response = $controller->__invoke($request, $repo, $devisRepo, $docuSealService, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');

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