<?php

namespace App\Tests\Service;

use App\Entity\Attestation;
use App\Entity\Devis;
use App\Service\DocuSealService;
use Doctrine\ORM\EntityManagerInterface;
use Docuseal\Api;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class DocuSealServiceTest extends TestCase
{
    private EntityManagerInterface $em;
    private Api $api;
    private DocuSealService $service;
    private string $projectDir;

    protected function setUp(): void
    {
        $this->em = $this->createStub(EntityManagerInterface::class);
        $this->api = $this->createStub(Api::class);
        $this->projectDir = sys_get_temp_dir().'/docuseal-test-'.bin2hex(random_bytes(4));
        mkdir($this->projectDir.'/public', 0775, true);

        $this->service = new DocuSealService($this->em, $this->createStub(LoggerInterface::class), 'https://fake.docuseal.test', 'fake-key', $this->projectDir);

        // Replace the real Api with our stub
        $ref = new \ReflectionProperty(DocuSealService::class, 'api');
        $ref->setValue($this->service, $this->api);
    }

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

    private function removeDir(string $dir): void
    {
        if (!is_dir($dir)) {
            return;
        }
        foreach (scandir($dir) as $item) {
            if ('.' === $item || '..' === $item) {
                continue;
            }
            $path = $dir.'/'.$item;
            is_dir($path) ? $this->removeDir($path) : unlink($path);
        }
        rmdir($dir);
    }

    private function createAttestation(?string $pdfPath = null, ?int $submitterId = null): Attestation
    {
        $attestation = new Attestation('access', '127.0.0.1', 'test@example.com', 'secret');
        if (null !== $pdfPath) {
            $attestation->setPdfFileUnsigned($pdfPath);
        }
        if (null !== $submitterId) {
            $attestation->setSubmitterId($submitterId);
        }

        return $attestation;
    }

    // --- signAttestation ---

    public function testSignAttestationSuccess(): void
    {
        $pdfPath = $this->projectDir.'/test.pdf';
        file_put_contents($pdfPath, '%PDF-fake');
        $attestation = $this->createAttestation($pdfPath);

        $this->api->method('createSubmissionFromPdf')->willReturn([['id' => 42]]);

        $result = $this->service->signAttestation($attestation);

        $this->assertTrue($result);
        $this->assertSame(42, $attestation->getSubmitterId());
    }

    public function testSignAttestationNullSubmitterId(): void
    {
        $pdfPath = $this->projectDir.'/test.pdf';
        file_put_contents($pdfPath, '%PDF-fake');
        $attestation = $this->createAttestation($pdfPath);

        $this->api->method('createSubmissionFromPdf')->willReturn([[]]);

        $this->assertFalse($this->service->signAttestation($attestation));
    }

    public function testSignAttestationNoPdfPath(): void
    {
        $attestation = $this->createAttestation();

        $this->assertFalse($this->service->signAttestation($attestation));
    }

    public function testSignAttestationPdfFileDoesNotExist(): void
    {
        $attestation = $this->createAttestation('/nonexistent/file.pdf');

        $this->assertFalse($this->service->signAttestation($attestation));
    }

    public function testSignAttestationApiThrows(): void
    {
        $pdfPath = $this->projectDir.'/test.pdf';
        file_put_contents($pdfPath, '%PDF-fake');
        $attestation = $this->createAttestation($pdfPath);

        $this->api->method('createSubmissionFromPdf')->willThrowException(new \RuntimeException('API error'));

        $this->assertFalse($this->service->signAttestation($attestation));
    }

    public function testSignAttestationWithLogoFile(): void
    {
        $pdfPath = $this->projectDir.'/test.pdf';
        file_put_contents($pdfPath, '%PDF-fake');
        file_put_contents($this->projectDir.'/public/logo_facture.png', 'fake-jpeg');
        $attestation = $this->createAttestation($pdfPath);

        $this->api->method('createSubmissionFromPdf')->willReturn([['id' => 99]]);

        $this->assertTrue($this->service->signAttestation($attestation));
    }

    // --- downloadSignedDocuments ---

    public function testDownloadSignedDocumentsNoSubmitterId(): void
    {
        $attestation = $this->createAttestation();

        $this->assertFalse($this->service->downloadSignedDocuments($attestation));
    }

    public function testDownloadSignedDocumentsEmptyDocuments(): void
    {
        $attestation = $this->createAttestation(null, 42);

        $this->api->method('getSubmitter')->willReturn(['documents' => []]);

        $this->assertFalse($this->service->downloadSignedDocuments($attestation));
    }

    public function testDownloadSignedDocumentsApiThrows(): void
    {
        $attestation = $this->createAttestation(null, 42);

        $this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));

        $this->assertFalse($this->service->downloadSignedDocuments($attestation));
    }

    public function testDownloadSignedDocumentsNoPdfUrl(): void
    {
        $attestation = $this->createAttestation(null, 42);

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['name' => 'doc']],
        ]);

        $this->assertFalse($this->service->downloadSignedDocuments($attestation));
        $this->assertNull($attestation->getPdfFileSigned());
    }

    public function testDownloadSignedDocumentsWithPdfAndAudit(): void
    {
        $attestation = $this->createAttestation(null, 42);

        // Create a temp file to serve as the "downloaded" PDF
        $fakePdfPath = $this->projectDir.'/fake-signed.pdf';
        file_put_contents($fakePdfPath, '%PDF-signed-content');
        $fakeAuditPath = $this->projectDir.'/fake-audit.pdf';
        file_put_contents($fakeAuditPath, '%PDF-audit');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePdfPath]],
            'audit_log_url' => $fakeAuditPath,
        ]);

        // file_get_contents works with local paths
        $result = $this->service->downloadSignedDocuments($attestation);

        $this->assertTrue($result);
        $this->assertNotNull($attestation->getPdfFileSigned());
        $this->assertNotNull($attestation->getPdfFileCertificate());
    }

    public function testDownloadSignedDocumentsNoAuditUrl(): void
    {
        $attestation = $this->createAttestation(null, 42);

        $fakePdfPath = $this->projectDir.'/fake-signed.pdf';
        file_put_contents($fakePdfPath, '%PDF-signed-content');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePdfPath]],
        ]);

        $result = $this->service->downloadSignedDocuments($attestation);

        $this->assertTrue($result);
        $this->assertNotNull($attestation->getPdfFileSigned());
        $this->assertNull($attestation->getPdfFileCertificate());
    }

    public function testDownloadSignedDocumentsInvalidPdfContent(): void
    {
        $attestation = $this->createAttestation(null, 42);

        $fakePath = $this->projectDir.'/not-a-pdf.txt';
        file_put_contents($fakePath, 'this is not a PDF');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePath]],
        ]);

        $result = $this->service->downloadSignedDocuments($attestation);

        $this->assertFalse($result);
        $this->assertNull($attestation->getPdfFileSigned());
    }

    public function testDownloadSignedDocumentsCreatesDirectory(): void
    {
        $attestation = $this->createAttestation(null, 42);

        $fakePdfPath = $this->projectDir.'/fake-signed.pdf';
        file_put_contents($fakePdfPath, '%PDF-signed');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePdfPath]],
        ]);

        // Ensure the signed dir doesn't exist yet
        $signedDir = $this->projectDir.'/var/rgpd/signed';
        $this->assertDirectoryDoesNotExist($signedDir);

        $this->service->downloadSignedDocuments($attestation);

        $this->assertDirectoryExists($signedDir);
    }

    // --- getApi ---

    public function testGetApiReturnsApiInstance(): void
    {
        $api = $this->service->getApi();

        $this->assertInstanceOf(Api::class, $api);
    }

    // --- sendDevisForSignature ---

    private function createDevis(?string $pdfFilename = 'test.pdf', ?string $email = 'client@example.com'): Devis
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');

        if (null !== $email) {
            $customer = $this->createStub(\App\Entity\Customer::class);
            $customer->method('getEmail')->willReturn($email);
            $customer->method('getFullName')->willReturn('Jean Dupont');
            $devis->setCustomer($customer);
        }

        if (null !== $pdfFilename) {
            $devis->setUnsignedPdf($pdfFilename);
        }

        return $devis;
    }

    public function testSendDevisForSignatureNoCustomer(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');

        $result = $this->service->sendDevisForSignature($devis);

        $this->assertNull($result);
    }

    public function testSendDevisForSignatureNoEmail(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $customer = $this->createStub(\App\Entity\Customer::class);
        $customer->method('getEmail')->willReturn(null);
        $devis->setCustomer($customer);

        $result = $this->service->sendDevisForSignature($devis);

        $this->assertNull($result);
    }

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

        $result = $this->service->sendDevisForSignature($devis);

        $this->assertNull($result);
    }

    public function testSendDevisForSignaturePdfNotFound(): void
    {
        $devis = $this->createDevis('nonexistent.pdf');

        $result = $this->service->sendDevisForSignature($devis);

        $this->assertNull($result);
    }

    public function testSendDevisForSignatureSuccess(): void
    {
        // Create the PDF file
        $pdfPath = $this->projectDir.'/public/uploads/devis/test-devis.pdf';
        mkdir(dirname($pdfPath), 0775, true);
        file_put_contents($pdfPath, '%PDF-fake');

        $devis = $this->createDevis('test-devis.pdf');

        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 77]],
        ]);

        $result = $this->service->sendDevisForSignature($devis);

        $this->assertSame(77, $result);
    }

    public function testSendDevisForSignatureWithRedirectUrl(): void
    {
        $pdfPath = $this->projectDir.'/public/uploads/devis/test-devis2.pdf';
        mkdir(dirname($pdfPath), 0775, true);
        file_put_contents($pdfPath, '%PDF-fake');

        $devis = $this->createDevis('test-devis2.pdf');

        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 88]],
        ]);

        $result = $this->service->sendDevisForSignature($devis, 'https://redirect.example.com');

        $this->assertSame(88, $result);
    }

    public function testSendDevisForSignatureApiThrows(): void
    {
        $pdfPath = $this->projectDir.'/public/uploads/devis/test-devis3.pdf';
        mkdir(dirname($pdfPath), 0775, true);
        file_put_contents($pdfPath, '%PDF-fake');

        $devis = $this->createDevis('test-devis3.pdf');

        $this->api->method('createSubmissionFromPdf')->willThrowException(new \RuntimeException('API error'));

        $result = $this->service->sendDevisForSignature($devis);

        $this->assertNull($result);
    }

    // --- resendDevisSignature ---

    public function testResendDevisSignatureNoOldSubmitter(): void
    {
        $pdfPath = $this->projectDir.'/public/uploads/devis/resend1.pdf';
        mkdir(dirname($pdfPath), 0775, true);
        file_put_contents($pdfPath, '%PDF-fake');

        $devis = $this->createDevis('resend1.pdf');
        // No submissionId set (zero)

        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 55]],
        ]);

        $result = $this->service->resendDevisSignature($devis);

        $this->assertSame(55, $result);
    }

    public function testResendDevisSignatureWithOldSubmitter(): void
    {
        $pdfPath = $this->projectDir.'/public/uploads/devis/resend2.pdf';
        mkdir(dirname($pdfPath), 0775, true);
        file_put_contents($pdfPath, '%PDF-fake');

        $devis = $this->createDevis('resend2.pdf');
        $devis->setSubmissionId('123');

        $this->api->method('getSubmitter')->willReturn(['submission_id' => 456]);
        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 99]],
        ]);

        $result = $this->service->resendDevisSignature($devis);

        $this->assertSame(99, $result);
    }

    public function testResendDevisSignatureArchiveFails(): void
    {
        $pdfPath = $this->projectDir.'/public/uploads/devis/resend3.pdf';
        mkdir(dirname($pdfPath), 0775, true);
        file_put_contents($pdfPath, '%PDF-fake');

        $devis = $this->createDevis('resend3.pdf');
        $devis->setSubmissionId('200');

        // getSubmitter throws => archive warning logged, then sendDevisForSignature called
        $this->api->method('getSubmitter')->willThrowException(new \RuntimeException('not found'));
        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 42]],
        ]);

        $result = $this->service->resendDevisSignature($devis);

        $this->assertSame(42, $result);
    }

    // --- getSubmitterSlug ---

    public function testGetSubmitterSlugSuccess(): void
    {
        $this->api->method('getSubmitter')->willReturn(['slug' => 'abc-def-123']);

        $slug = $this->service->getSubmitterSlug(42);

        $this->assertSame('abc-def-123', $slug);
    }

    public function testGetSubmitterSlugNoSlugKey(): void
    {
        $this->api->method('getSubmitter')->willReturn(['id' => 42]);

        $slug = $this->service->getSubmitterSlug(42);

        $this->assertNull($slug);
    }

    public function testGetSubmitterSlugApiThrows(): void
    {
        $this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));

        $slug = $this->service->getSubmitterSlug(42);

        $this->assertNull($slug);
    }

    // --- getSubmitterData ---

    public function testGetSubmitterDataSuccess(): void
    {
        $data = ['id' => 42, 'slug' => 'xyz', 'documents' => []];
        $this->api->method('getSubmitter')->willReturn($data);

        $result = $this->service->getSubmitterData(42);

        $this->assertSame($data, $result);
    }

    public function testGetSubmitterDataApiThrows(): void
    {
        $this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));

        $result = $this->service->getSubmitterData(42);

        $this->assertNull($result);
    }

    // --- archiveSubmission ---

    public function testArchiveSubmissionSuccess(): void
    {
        $result = $this->service->archiveSubmission(123);

        $this->assertTrue($result);
    }

    public function testArchiveSubmissionApiThrows(): void
    {
        $this->api->method('archiveSubmission')->willThrowException(new \RuntimeException('fail'));

        $result = $this->service->archiveSubmission(123);

        $this->assertFalse($result);
    }

    // --- downloadSignedDevis ---

    public function testDownloadSignedDevisNoSubmissionId(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertFalse($result);
    }

    public function testDownloadSignedDevisZeroSubmitterId(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('0');

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertFalse($result);
    }

    public function testDownloadSignedDevisEmptyDocuments(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $this->api->method('getSubmitter')->willReturn(['documents' => []]);

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertFalse($result);
    }

    public function testDownloadSignedDevisNoPdfUrl(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['name' => 'doc']],
        ]);

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertFalse($result);
    }

    public function testDownloadSignedDevisInvalidPdfContent(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $fakePath = $this->projectDir.'/not-a-pdf.txt';
        file_put_contents($fakePath, 'not pdf content');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePath]],
        ]);

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertFalse($result);
    }

    public function testDownloadSignedDevisSuccess(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $fakePdf = $this->projectDir.'/signed-devis.pdf';
        file_put_contents($fakePdf, '%PDF-signed');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePdf]],
        ]);

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertTrue($result);
    }

    public function testDownloadSignedDevisWithAudit(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $fakePdf = $this->projectDir.'/signed-devis2.pdf';
        $fakeAudit = $this->projectDir.'/audit-devis.pdf';
        file_put_contents($fakePdf, '%PDF-signed');
        file_put_contents($fakeAudit, '%PDF-audit');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePdf]],
            'audit_log_url' => $fakeAudit,
        ]);

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertTrue($result);
    }

    public function testDownloadSignedDevisApiThrows(): void
    {
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));

        $result = $this->service->downloadSignedDevis($devis);

        $this->assertFalse($result);
    }

    public function testDownloadSignedDevisWithAuditUrlNotFound(): void
    {
        // Covers the downloadAuditForDevis branch where file_get_contents returns false
        // (audit URL does not exist → early return null)
        $orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
        $devis = new Devis($orderNumber, 'secret');
        $devis->setSubmissionId('42');

        $fakePdf = $this->projectDir.'/signed-noaudit.pdf';
        file_put_contents($fakePdf, '%PDF-signed');

        $this->api->method('getSubmitter')->willReturn([
            'documents' => [['url' => $fakePdf]],
            // Provide an audit URL that does NOT resolve to valid content
            'audit_log_url' => '/nonexistent/path/audit.pdf',
        ]);

        $result = $this->service->downloadSignedDevis($devis);

        // PDF was downloaded successfully; audit simply not attached
        $this->assertTrue($result);
    }

    // --- sendComptaForSignature ---

    public function testSendComptaForSignatureSuccess(): void
    {
        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 111]],
        ]);

        $result = $this->service->sendComptaForSignature(
            '%PDF-content',
            'Export Compta',
            'user@example.com',
            'Jean Dupont',
            ['export_type' => 'fec', 'period_from' => '01/01/2026', 'period_to' => '31/03/2026'],
        );

        $this->assertSame(111, $result);
    }

    public function testSendComptaForSignatureWithRedirectUrl(): void
    {
        $this->api->method('createSubmissionFromPdf')->willReturn([
            'submitters' => [['id' => 222]],
        ]);

        $result = $this->service->sendComptaForSignature(
            '%PDF-content',
            'Export Compta',
            'user@example.com',
            'Jean Dupont',
            ['export_type' => 'grand_livre', 'period_from' => '01/01/2026', 'period_to' => '31/12/2026'],
            'https://redirect.example.com',
        );

        $this->assertSame(222, $result);
    }

    public function testSendComptaForSignatureFallbackId(): void
    {
        // Result has no 'submitters' key, fallback to result[0]['id']
        $this->api->method('createSubmissionFromPdf')->willReturn([
            ['id' => 333],
        ]);

        $result = $this->service->sendComptaForSignature(
            '%PDF-content',
            'Export Compta',
            'user@example.com',
            'Jean Dupont',
            ['export_type' => 'balance', 'period_from' => '01/01/2026', 'period_to' => '31/12/2026'],
        );

        $this->assertSame(333, $result);
    }

    public function testSendComptaForSignatureApiThrows(): void
    {
        $this->api->method('createSubmissionFromPdf')->willThrowException(new \RuntimeException('API error'));

        $result = $this->service->sendComptaForSignature(
            '%PDF-content',
            'Export Compta',
            'user@example.com',
            'Jean Dupont',
            ['export_type' => 'fec', 'period_from' => '01/01/2026', 'period_to' => '31/03/2026'],
        );

        $this->assertNull($result);
    }

    // --- getLogoBase64 (private) — covered via signAttestation when logo.jpg exists ---

    public function testSignAttestationWithLogoJpg(): void
    {
        $pdfPath = $this->projectDir.'/test-logo.pdf';
        file_put_contents($pdfPath, '%PDF-fake');
        // Create the expected logo.jpg file so the data:image/jpeg branch is exercised
        file_put_contents($this->projectDir.'/public/logo.jpg', 'fake-jpeg-content');
        $attestation = $this->createAttestation($pdfPath);

        $this->api->method('createSubmissionFromPdf')->willReturn([['id' => 55]]);

        $result = $this->service->signAttestation($attestation);

        $this->assertTrue($result);
        $this->assertSame(55, $attestation->getSubmitterId());
    }
}
