<?php

namespace App\Service;

use App\Entity\AnalyticsEvent;
use App\Entity\AnalyticsUniqId;
use App\Entity\Attestation;
use Doctrine\ORM\EntityManagerInterface;
use Dompdf\Dompdf;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Writer\PngWriter;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;

class RgpdService
{
    private const CODE_EXPIRY_MINUTES = 15;

    public function __construct(
        private EntityManagerInterface $em,
        private Environment $twig,
        private DocuSealService $docuSealService,
        private MailerService $mailer,
        private UrlGeneratorInterface $urlGenerator,
        #[Autowire('%kernel.project_dir%')] private string $projectDir,
        #[Autowire('%env(APP_SECRET)%')] private string $hmacSecret,
    ) {
    }

    private function generateQrCodeBase64(Attestation $attestation): string
    {
        $verifyUrl = $this->urlGenerator->generate('app_attestation_verify', [
            'reference' => $attestation->getReference(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);

        $builder = new Builder(
            writer: new PngWriter(),
            data: $verifyUrl,
            size: 200,
            margin: 10,
        );
        $result = $builder->build();

        return 'data:image/png;base64,'.base64_encode($result->getString());
    }

    private function getVerifyUrl(Attestation $attestation): string
    {
        return $this->urlGenerator->generate('app_attestation_verify', [
            'reference' => $attestation->getReference(),
        ], UrlGeneratorInterface::ABSOLUTE_URL);
    }

    /**
     * Genere et envoie un code de verification par email.
     */
    public function sendVerificationCode(string $email, string $ip, string $type): void
    {
        $code = str_pad((string) random_int(0, 999999), 6, '0', \STR_PAD_LEFT);

        // Stocke le code en session-like via un hash deterministe
        $codeHash = $this->generateCodeHash($email, $ip, $type, $code);

        // Stocke en fichier temporaire (expire dans 15 min)
        $codePath = $this->getCodeFilePath($email, $ip, $type);
        file_put_contents($codePath, json_encode([
            'code' => $code,
            'hash' => $codeHash,
            'expires' => time() + (self::CODE_EXPIRY_MINUTES * 60),
        ]));

        $typeName = 'access' === $type ? 'acces a vos donnees' : 'suppression de vos donnees';

        $this->mailer->sendEmail(
            $email,
            'Code de verification RGPD - '.$typeName,
            $this->twig->render('emails/rgpd_verification_code.html.twig', [
                'code' => $code,
                'type' => $type,
                'typeName' => $typeName,
                'expiryMinutes' => self::CODE_EXPIRY_MINUTES,
            ]),
            null,
            null,
            false,
        );
    }

    /**
     * Verifie le code saisi par l'utilisateur.
     */
    public function verifyCode(string $email, string $ip, string $type, string $code): bool
    {
        $codePath = $this->getCodeFilePath($email, $ip, $type);

        if (!file_exists($codePath)) {
            return false;
        }

        $data = json_decode(file_get_contents($codePath), true);
        $isValid = null !== $data
            && time() <= ($data['expires'] ?? 0)
            && $code === ($data['code'] ?? '');

        // Supprime le fichier si invalide (expire/corrompu) ou valide (usage unique)
        if (null === $data || time() > ($data['expires'] ?? 0) || $isValid) {
            @unlink($codePath);
        }

        return $isValid;
    }

    private function getCodeFilePath(string $email, string $ip, string $type): string
    {
        $dir = $this->projectDir.'/var/rgpd/codes';
        if (!is_dir($dir)) {
            mkdir($dir, 0o755, true);
        }

        return $dir.'/'.hash('sha256', $email.'|'.$ip.'|'.$type.'|'.$this->hmacSecret).'.json';
    }

    private function generateCodeHash(string $email, string $ip, string $type, string $code): string
    {
        return hash_hmac('sha256', $email.'|'.$ip.'|'.$type.'|'.$code, $this->hmacSecret);
    }

    /**
     * @return array{found: bool, count: int}
     */
    public function handleAccessRequest(string $ip, string $email): array
    {
        $visitors = $this->getVisitorsByIp($ip);

        if (0 === \count($visitors)) {
            $this->createAndSignAttestation('no_data', $ip, $email);

            return ['found' => false, 'count' => 0];
        }

        $data = [];
        foreach ($visitors as $visitor) {
            $events = $this->em->getRepository(AnalyticsEvent::class)->findBy(
                ['visitor' => $visitor],
                ['createdAt' => 'DESC'],
            );

            $data[] = [
                'visitor' => $visitor,
                'events' => $events,
            ];
        }

        $this->createAndSignAttestation('access', $ip, $email, $data);

        return ['found' => true, 'count' => \count($visitors)];
    }

    /**
     * @return array{found: bool, deleted: int}
     */
    public function handleDeletionRequest(string $ip, string $email): array
    {
        $visitors = $this->getVisitorsByIp($ip);

        if (0 === \count($visitors)) {
            $this->createAndSignAttestation('no_data', $ip, $email);

            return ['found' => false, 'deleted' => 0];
        }

        $count = \count($visitors);

        foreach ($visitors as $visitor) {
            $this->em->remove($visitor);
        }

        $this->createAndSignAttestation('deletion', $ip, $email);

        return ['found' => true, 'deleted' => $count];
    }

    /**
     * @return list<AnalyticsUniqId>
     */
    private function getVisitorsByIp(string $ip): array
    {
        $ipHash = hash('sha256', $ip);

        return $this->em->getRepository(AnalyticsUniqId::class)->findBy(['ipHash' => $ipHash]);
    }

    private function createAndSignAttestation(string $type, string $ip, string $email, mixed $data = null): Attestation
    {
        $attestation = new Attestation($type, $ip, $email, $this->hmacSecret);
        $this->em->persist($attestation);
        $this->em->flush();

        $pdfPath = match ($type) {
            'no_data' => $this->generateNoDataPdf($ip, $attestation),
            'access' => $this->generateAccessPdf($data, $ip, $attestation),
            'deletion' => $this->generateDeletionPdf($ip, $attestation),
            // @codeCoverageIgnoreStart
            default => throw new \InvalidArgumentException('Invalid attestation type'),
            // @codeCoverageIgnoreEnd
        };

        $attestation->setPdfFileUnsigned($pdfPath);
        $this->em->flush();

        $this->docuSealService->signAttestation($attestation);

        return $attestation;
    }

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

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

    /**
     * @return array{ip: string, logo: string, date: \DateTimeImmutable, attestation: Attestation, qrcode: string, verify_url: string}
     */
    private function getPdfVars(string $ip, Attestation $attestation): array
    {
        return [
            'ip' => $ip,
            'logo' => $this->getLogoBase64(),
            'date' => $attestation->getCreatedAt(),
            'attestation' => $attestation,
            'qrcode' => $this->generateQrCodeBase64($attestation),
            'verify_url' => $this->getVerifyUrl($attestation),
        ];
    }

    /**
     * @param list<array{visitor: AnalyticsUniqId, events: list<AnalyticsEvent>}> $data
     */
    private function generateAccessPdf(array $data, string $ip, Attestation $attestation): string
    {
        $vars = $this->getPdfVars($ip, $attestation);
        $vars['data'] = $data;

        return $this->renderPdf($this->twig->render('pdf/rgpd_access.html.twig', $vars));
    }

    private function generateNoDataPdf(string $ip, Attestation $attestation): string
    {
        return $this->renderPdf($this->twig->render('pdf/rgpd_no_data.html.twig', $this->getPdfVars($ip, $attestation)));
    }

    private function generateDeletionPdf(string $ip, Attestation $attestation): string
    {
        return $this->renderPdf($this->twig->render('pdf/rgpd_deletion.html.twig', $this->getPdfVars($ip, $attestation)));
    }

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

        $dompdf = new Dompdf();
        $dompdf->loadHtml($html);
        $dompdf->setPaper('A4');
        $dompdf->render();

        $path = $dir.'/'.bin2hex(random_bytes(16)).'.pdf';
        file_put_contents($path, $dompdf->output());

        return $path;
    }
}
