<?php

namespace App\Service;

use App\Entity\Attestation;
use App\Entity\Devis;
use App\Entity\Echeancier;
use Doctrine\ORM\EntityManagerInterface;
use Docuseal\Api;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class DocuSealService
{
    private const PDF_BASE64_PREFIX = 'data:application/pdf;base64,';
    private const ROLE_FIRST_PARTY = 'First Party';

    private Api $api;

    public function __construct(
        private EntityManagerInterface $em,
        private LoggerInterface $logger,
        #[Autowire(env: 'DOCUSEAL_URL')] string $baseUrl,
        #[Autowire(env: 'DOCUSEAL_API')] string $apiKey,
        #[Autowire('%kernel.project_dir%')] private string $projectDir,
    ) {
        $this->api = new Api($apiKey, rtrim($baseUrl, '/').'/api');
    }

    public function getApi(): Api
    {
        return $this->api;
    }

    /**
     * Envoie l'attestation a DocuSeal pour auto-signature en un seul appel.
     */
    public function signAttestation(Attestation $attestation): bool
    {
        $pdfPath = $attestation->getPdfFileUnsigned();
        $success = false;

        if (null !== $pdfPath && file_exists($pdfPath)) {
            try {
                $pdfBase64 = base64_encode(file_get_contents($pdfPath));

                $result = $this->api->createSubmissionFromPdf([
                    'name' => 'Attestation RGPD - '.$attestation->getReference(),
                    'send_email' => false,
                    'flatten' => true,
                    'documents' => [
                        [
                            'name' => 'attestation-'.$attestation->getReference().'.pdf',
                            'file' => self::PDF_BASE64_PREFIX.$pdfBase64,
                        ],
                    ],
                    'submitters' => [
                        [
                            'email' => 'client@e-cosplay.fr',
                            'name' => 'Association E-Cosplay',
                            'role' => self::ROLE_FIRST_PARTY,
                            'completed' => true,
                            'send_email' => false,
                            'values' => [
                                'Sign' => $this->getLogoBase64(),
                            ],
                            'metadata' => [
                                'doc_type' => 'attestation',
                                'reference' => $attestation->getReference(),
                            ],
                        ],
                    ],
                ]);

                $submitterId = $result[0]['id'] ?? null;

                if (null !== $submitterId) {
                    $attestation->setSubmitterId($submitterId);
                    $this->em->flush();
                    $success = true;
                }
            } catch (\Throwable) {
                $success = false;
            }
        }

        return $success;
    }

    /**
     * Envoie un devis a DocuSeal pour signature par le client (First Party).
     * Retourne l'id du submitter client pour enregistrement en base.
     */
    public function sendDevisForSignature(Devis $devis, ?string $completedRedirectUrl = null): ?int
    {
        $customer = $devis->getCustomer();
        $pdfFilename = $devis->getUnsignedPdf();
        $pdfPath = null !== $pdfFilename ? $this->projectDir.'/public/uploads/devis/'.$pdfFilename : null;

        if (null === $customer || null === $customer->getEmail() || null === $pdfFilename || (null !== $pdfPath && !file_exists($pdfPath))) {
            $this->logger->error('DocuSeal devis: prerequis manquants (devis '.$devis->getId().')');

            return null;
        }

        try {
            $pdfBase64 = base64_encode(file_get_contents($pdfPath));
            $numOrder = $devis->getOrderNumber()->getNumOrder();

            $submitter = [
                'email' => $customer->getEmail(),
                'name' => $customer->getFullName(),
                'role' => self::ROLE_FIRST_PARTY,
                'send_email' => false,
                'metadata' => [
                    'doc_type' => 'devis',
                    'num_order' => $numOrder,
                    'devis_id' => $devis->getId(),
                ],
            ];

            if (null !== $completedRedirectUrl) {
                $submitter['completed_redirect_url'] = $completedRedirectUrl;
            }

            $result = $this->api->createSubmissionFromPdf([
                'name' => 'Devis '.$numOrder,
                'send_email' => false,
                'flatten' => true,
                'documents' => [
                    [
                        'name' => 'devis-'.str_replace('/', '-', $numOrder).'.pdf',
                        'file' => self::PDF_BASE64_PREFIX.$pdfBase64,
                    ],
                ],
                'submitters' => [$submitter],
            ]);

            $this->logger->info('DocuSeal devis: reponse API', ['result' => $result]);

            return $result['submitters'][0]['id'] ?? null;
        } catch (\Throwable $e) {
            $this->logger->error('DocuSeal devis: exception API: '.$e->getMessage(), [
                'exception' => $e,
                'devis_id' => $devis->getId(),
            ]);

            return null;
        }
    }

    /**
     * Annule la submission actuelle du devis dans DocuSeal puis en cree une nouvelle.
     * Retourne le nouvel id submitter.
     */
    public function resendDevisSignature(Devis $devis, ?string $completedRedirectUrl = null): ?int
    {
        $oldSubmitterId = (int) ($devis->getSubmissionId() ?? '0');
        if ($oldSubmitterId > 0) {
            try {
                $submitter = $this->api->getSubmitter($oldSubmitterId);
                $submissionId = $submitter['submission_id'] ?? null;
                if (null !== $submissionId) {
                    $this->api->archiveSubmission($submissionId);
                    $this->logger->info('DocuSeal devis: ancienne submission archivee', [
                        'submission_id' => $submissionId,
                        'devis_id' => $devis->getId(),
                    ]);
                }
            } catch (\Throwable $e) {
                $this->logger->warning('DocuSeal devis: echec archive ancienne submission: '.$e->getMessage());
            }
        }

        return $this->sendDevisForSignature($devis, $completedRedirectUrl);
    }

    /**
     * Recupere le slug du submitter DocuSeal pour construire l'URL de signature.
     */
    public function getSubmitterSlug(int $submitterId): ?string
    {
        try {
            $submitter = $this->api->getSubmitter($submitterId);

            return $submitter['slug'] ?? null;
        } catch (\Throwable) {
            return null;
        }
    }

    /**
     * Recupere les donnees completes d'un submitter.
     *
     * @return array<string, mixed>|null
     */
    public function getSubmitterData(int $submitterId): ?array
    {
        try {
            return $this->api->getSubmitter($submitterId);
        } catch (\Throwable) {
            return null;
        }
    }

    /**
     * Archive une submission dans DocuSeal (invalide tous les liens de signature).
     */
    public function archiveSubmission(int $submissionId): bool
    {
        try {
            $this->api->archiveSubmission($submissionId);
            $this->logger->info('DocuSeal: submission '.$submissionId.' archivee');

            return true;
        } catch (\Throwable $e) {
            $this->logger->warning('DocuSeal: echec archive submission '.$submissionId.': '.$e->getMessage());

            return false;
        }
    }

    /**
     * Telecharge et sauvegarde via Vich le PDF signe et le certificat d'audit depuis DocuSeal.
     */
    public function downloadSignedDevis(Devis $devis): bool
    {
        $submitterId = (int) ($devis->getSubmissionId() ?? '0');
        if ($submitterId <= 0) {
            return false;
        }

        try {
            return $this->fetchAndStoreDevisDocuments($submitterId, $devis);
        } catch (\Throwable $e) {
            $this->logger->error('DocuSeal: erreur telechargement devis signe: '.$e->getMessage(), [
                'devis_id' => $devis->getId(),
                'exception' => $e,
            ]);

            return false;
        }
    }

    private function fetchAndStoreDevisDocuments(int $submitterId, Devis $devis): bool
    {
        $submitter = $this->api->getSubmitter($submitterId);
        $pdfUrl = $submitter['documents'][0]['url'] ?? null;

        $content = null !== $pdfUrl ? @file_get_contents($pdfUrl) : false;
        if (false === $content || !str_starts_with($content, '%PDF')) {
            return false;
        }

        $numOrder = str_replace('/', '-', $devis->getOrderNumber()->getNumOrder());

        $tmpSigned = tempnam(sys_get_temp_dir(), 'signed_').'.pdf';
        file_put_contents($tmpSigned, $content);
        $devis->setSignedPdfFile(new UploadedFile($tmpSigned, 'signed-'.$numOrder.'.pdf', 'application/pdf', null, true));

        $tmpAudit = $this->downloadAuditForDevis($submitter, $numOrder, $devis);

        $devis->setUpdatedAt(new \DateTimeImmutable());
        $this->em->flush();

        @unlink($tmpSigned);
        if (null !== $tmpAudit) {
            @unlink($tmpAudit);
        }

        return true;
    }

    /**
     * @param array<string, mixed> $submitter
     */
    private function downloadAuditForDevis(array $submitter, string $numOrder, Devis $devis): ?string
    {
        $auditUrl = $submitter['audit_log_url'] ?? null;
        if (null === $auditUrl) {
            return null;
        }

        $auditContent = @file_get_contents($auditUrl);
        if (false === $auditContent) {
            return null;
        }

        $tmpAudit = tempnam(sys_get_temp_dir(), 'audit_').'.pdf';
        file_put_contents($tmpAudit, $auditContent);
        $devis->setAuditPdfFile(new UploadedFile($tmpAudit, 'audit-'.$numOrder.'.pdf', 'application/pdf', null, true));

        return $tmpAudit;
    }

    /**
     * Envoie un PDF comptable a DocuSeal pour signature par un utilisateur.
     * Retourne l'id du submitter pour suivi.
     */
    /**
     * @param array{export_type: string, period_from: string, period_to: string} $metadata
     */
    public function sendComptaForSignature(
        string $pdfContent,
        string $documentName,
        string $signerEmail,
        string $signerName,
        array $metadata,
        ?string $completedRedirectUrl = null,
    ): ?int {
        try {
            $pdfBase64 = base64_encode($pdfContent);

            $submitter = [
                'email' => $signerEmail,
                'name' => $signerName,
                'role' => self::ROLE_FIRST_PARTY,
                'send_email' => false,
                'metadata' => array_merge(['doc_type' => 'comptabilite'], $metadata),
            ];

            if (null !== $completedRedirectUrl) {
                $submitter['completed_redirect_url'] = $completedRedirectUrl;
            }

            $result = $this->api->createSubmissionFromPdf([
                'name' => $documentName,
                'send_email' => false,
                'flatten' => true,
                'documents' => [
                    [
                        'name' => str_replace(' ', '_', $documentName).'.pdf',
                        'file' => self::PDF_BASE64_PREFIX.$pdfBase64,
                    ],
                ],
                'submitters' => [$submitter],
            ]);

            $this->logger->info('DocuSeal compta: reponse API', ['result' => $result]);

            return $result['submitters'][0]['id'] ?? ($result[0]['id'] ?? null);
        } catch (\Throwable $e) {
            $this->logger->error('DocuSeal compta: exception API: '.$e->getMessage(), [
                'exception' => $e,
                'document' => $documentName,
            ]);

            return null;
        }
    }

    public function getLogoBase64(): string
    {
        $logoPath = $this->projectDir.'/public/logo.jpg';

        if (!file_exists($logoPath)) {
            return 'Association E-Cosplay';
        }

        return 'data:image/jpeg;base64,'.base64_encode(file_get_contents($logoPath));
    }

    /**
     * Telecharge le PDF signe et le certificat depuis DocuSeal.
     */
    public function downloadSignedDocuments(Attestation $attestation): bool
    {
        $submitterId = $attestation->getSubmitterId();
        if (null === $submitterId) {
            return false;
        }

        try {
            return $this->fetchAndStoreDocuments($submitterId, $attestation);
        } catch (\Throwable) {
            return false;
        }
    }

    private function fetchAndStoreDocuments(int $submitterId, Attestation $attestation): bool
    {
        $submitter = $this->api->getSubmitter($submitterId);
        $documents = $submitter['documents'] ?? [];

        if ([] === $documents) {
            return false;
        }

        $dir = $this->ensureSignedDirectory();
        $ref = $attestation->getReference();

        $this->downloadSignedPdf($documents, $dir, $ref, $attestation);
        $this->downloadAuditCertificate($submitter, $dir, $ref, $attestation);

        $this->em->flush();

        return null !== $attestation->getPdfFileSigned();
    }

    private function ensureSignedDirectory(): string
    {
        $dir = $this->projectDir.'/var/rgpd/signed';
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        return $dir;
    }

    /**
     * @codeCoverageIgnore
     *
     * @param list<array<string, mixed>> $documents
     */
    private function downloadSignedPdf(array $documents, string $dir, string $ref, Attestation $attestation): void
    {
        $pdfUrl = $documents[0]['url'] ?? null;
        if (null === $pdfUrl) {
            return;
        }

        $content = @file_get_contents($pdfUrl);
        if (false !== $content && str_starts_with($content, '%PDF')) {
            $path = $dir.'/'.$ref.'-signed.pdf';
            file_put_contents($path, $content);
            $attestation->setPdfFileSigned($path);
        }
    }

    /**
     * Telecharge et sauvegarde via Vich le PDF signe et le certificat d'audit d'un echeancier depuis DocuSeal.
     */
    public function downloadSignedEcheancier(Echeancier $echeancier): bool
    {
        $submitterId = (int) ($echeancier->getSubmissionId() ?? '0');
        if ($submitterId <= 0) {
            return false;
        }

        try {
            return $this->fetchAndStoreEcheancierDocuments($submitterId, $echeancier);
        } catch (\Throwable $e) {
            $this->logger->error('DocuSeal: erreur telechargement echeancier signe: '.$e->getMessage(), [
                'echeancier_id' => $echeancier->getId(),
                'exception' => $e,
            ]);

            return false;
        }
    }

    private function fetchAndStoreEcheancierDocuments(int $submitterId, Echeancier $echeancier): bool
    {
        $submitter = $this->api->getSubmitter($submitterId);
        $pdfUrl = $submitter['documents'][0]['url'] ?? null;

        $content = null !== $pdfUrl ? @file_get_contents($pdfUrl) : false;
        if (false === $content || !str_starts_with($content, '%PDF')) {
            return false;
        }

        $ref = $echeancier->getReference();

        $tmpSigned = tempnam(sys_get_temp_dir(), 'ech_signed_').'.pdf';
        file_put_contents($tmpSigned, $content);
        $echeancier->setPdfSignedFile(new UploadedFile($tmpSigned, 'echeancier-signe-'.$ref.'.pdf', 'application/pdf', null, true));

        $tmpAudit = null;
        $auditUrl = $submitter['audit_log_url'] ?? null;
        if (null !== $auditUrl) {
            $auditContent = @file_get_contents($auditUrl);
            if (false !== $auditContent) {
                $tmpAudit = tempnam(sys_get_temp_dir(), 'ech_audit_').'.pdf';
                file_put_contents($tmpAudit, $auditContent);
                $echeancier->setPdfAuditFile(new UploadedFile($tmpAudit, 'audit-'.$ref.'.pdf', 'application/pdf', null, true));
            }
        }

        $echeancier->setUpdatedAt(new \DateTimeImmutable());
        $this->em->flush();

        @unlink($tmpSigned);
        if (null !== $tmpAudit) {
            @unlink($tmpAudit);
        }

        return true;
    }

    /**
     * @codeCoverageIgnore
     *
     * @param array<string, mixed> $submitter
     */
    private function downloadAuditCertificate(array $submitter, string $dir, string $ref, Attestation $attestation): void
    {
        $auditUrl = $submitter['audit_log_url'] ?? null;
        if (null === $auditUrl) {
            return;
        }

        $content = @file_get_contents($auditUrl);
        if (false !== $content) {
            $path = $dir.'/'.$ref.'-certificat.pdf';
            file_put_contents($path, $content);
            $attestation->setPdfFileCertificate($path);
        }
    }
}
