<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Export\ParticipantList;

use Ferienpass\CoreBundle\ConsentManager\ConsentManager;
use Ferienpass\CoreBundle\ConsentManager\ConsentState;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Repository\ConsentFormRepository;
use Ferienpass\CoreBundle\Settings\SystemSettingsManager;
use Mpdf\Mpdf;
use Mpdf\Output\Destination as MpdfDestination;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Twig\Environment as TwigEnvironment;

final readonly class PdfExport
{
    public function __construct(
        private Filesystem $filesystem,
        private TwigEnvironment $twig,
        private EventDispatcherInterface $dispatcher,
        private ConsentManager $consentManager,
        private ConsentFormRepository $consentFormRepository,
        private readonly SystemSettingsManager $systemSettingsManager,
        #[Autowire(param: 'kernel.cache_dir')]
        private string $cacheDir
    ) {
    }

    public function generate(OfferInterface $offer, ?string $destination = null, bool $expanded = false): string
    {
        $html = $this->renderHtml($offer, $expanded);
        $hash = md5($html.($expanded ? 'expanded' : ''));
        $pdfPath = \sprintf('%s/pdf-%s.pdf', sys_get_temp_dir(), $hash);

        $this->createPdf($pdfPath, $html, expanded: $expanded);

        if (null !== $destination) {
            $this->filesystem->copy($pdfPath, $destination);
        }

        return $destination ?? $pdfPath;
    }

    private function renderHtml(OfferInterface $offer, bool $expanded = false): string
    {
        $data = [
            'offer' => $offer,
            'dispatcher' => $this->dispatcher,
            'sort' => $this->systemSettingsManager->getParticipantListSort(),
            'expanded' => $expanded,
        ];

        if ($expanded) {
            $consentForms = $this->consentFormRepository->findForOffer($offer);
            $data['consentForms'] = $consentForms;

            $attendanceConsents = [];
            foreach ($offer->getAttendancesConfirmedOrWaitlisted() as $attendance) {
                $participant = $attendance->getParticipant();
                $user = $participant?->getUser();

                $consents = [];
                foreach ($consentForms as $consentForm) {
                    // Whether the participant must be passed (and matched) is driven
                    // by isPerParticipant(), NOT isUserLevel(). An edition-scoped
                    // participant consent (offer === null, subject === 'participant')
                    // — e.g. a season-wide Fotoeinwilligung — is "user level" for
                    // gathering/placement but is still signed once PER PARTICIPANT.
                    // Dropping the participant here (as isUserLevel() did) makes
                    // ConsentManager look for a signature with participant === null,
                    // which never matches the per-child rows, so every child rendered
                    // as "— (no decision)". This mirrors the list view, which always
                    // passes the participant via forOfferAndParticipants().
                    $subject = $consentForm->isPerParticipant() ? $participant : null;
                    if (null !== $user) {
                        $state = $this->consentManager->findState($consentForm, $user, $subject);
                    } else {
                        $state = new ConsentState($consentForm, participant: $subject);
                    }
                    $consents[(string) $consentForm->getId()] = $state;
                }
                $attendanceConsents[(string) $attendance->getId()] = $consents;
            }
            $data['attendanceConsents'] = $attendanceConsents;
        }

        return $this->twig->render('@FerienpassCore/participant_list/pdf/page.html.twig', $data);
    }

    private function createPdf(string $path, string $html, array $mPdfConfig = [], bool $expanded = false): void
    {
        if (file_exists($path)) {
            return;
        }

        $mPdfConfig = array_merge([
            'tempDir' => $this->cacheDir,
            'format' => $expanded ? 'A4-L' : 'A4',
            'default_font_size' => 12,
            'default_font' => 'roboto',
            'fontDir' => \dirname(__DIR__).'/../../fonts/Roboto',
            'fontdata' => [
                'roboto' => [
                    'R' => 'Roboto-Regular.ttf',
                    'B' => 'Roboto-Bold.ttf',
                    'I' => 'Roboto-Italic.ttf',
                ],
                'roboto-light' => [
                    'R' => 'Roboto-Light.ttf',
                    'I' => 'Roboto-LightItalic.ttf',
                ],
                'roboto-thin' => [
                    'R' => 'Roboto-Thin.ttf',
                    'I' => 'Roboto-ThinItalic.ttf',
                ],
            ],
        ], $mPdfConfig);

        // Forbid mPDF from fetching remote resources (<img>, url()) to prevent SSRF.
        // Only the local "file" stream wrapper is allowed; "http"/"https" are dropped.
        $mPdfConfig['whitelistStreamWrappers'] = ['file'];

        $mPdf = new Mpdf($mPdfConfig);

        $mPdf->WriteHTML($html);

        $mPdf->Output($path, MpdfDestination::FILE);
    }
}
