<?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\Offer;

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

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

    public function generate(OfferInterface $offer, bool $appendSheets = false, ?string $destination = null): string
    {
        $attendances = $offer->getAttendancesConfirmedOrWaitlisted()->toArray();
        usort($attendances, static function (Attendance $a, Attendance $b) {
            $pA = $a->getParticipant();
            $pB = $b->getParticipant();

            if (null === $pA && null === $pB) {
                return 0;
            }
            if (null === $pA) {
                return 1;
            }
            if (null === $pB) {
                return -1;
            }

            return strcasecmp((string) $pA->getLastname(), (string) $pB->getLastname())
                ?: strcasecmp((string) $pA->getFirstname(), (string) $pB->getFirstname());
        });

        $consentForms = $this->consentFormRepository->findForOffer($offer);

        $rows = [];
        foreach ($attendances as $attendance) {
            $participant = $attendance->getParticipant();
            $user = $participant->getUser();

            $states = [];
            foreach ($consentForms as $consentForm) {
                // Cardinality (and thus whether the participant must be 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 placement but is still signed once per participant.
                // Dropping the participant (as isUserLevel() did) made
                // ConsentManager match against participant === null, so every
                // child rendered as "Fehlt". Mirrors the list view, which always
                // passes the participant. See PdfExport for the same fix.
                $subject = $consentForm->isPerParticipant() ? $participant : null;

                if (null !== $user) {
                    $states[] = $this->consentManager->findState($consentForm, $user, $subject);
                } else {
                    $states[] = new ConsentState($consentForm, participant: $subject);
                }
            }

            $rows[] = [
                'participant' => $participant,
                'states' => $states,
            ];
        }

        $html = $this->twig->render('@FerienpassCore/export/consent/overview.html.twig', [
            'offer' => $offer,
            'consentForms' => $consentForms,
            'rows' => $rows,
        ]);

        $hash = md5($html.(string) $appendSheets);
        $pdfPath = \sprintf('%s/consents-overview-%s.pdf', sys_get_temp_dir(), $hash);

        $this->createPdf($pdfPath, $html, $rows, $appendSheets);

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

        return $destination ?? $pdfPath;
    }

    private function createPdf(string $path, string $html, array $rows, bool $appendSheets): void
    {
        if (file_exists($path)) {
            return;
        }

        $mPdfConfig = [
            'tempDir' => $this->cacheDir,
            '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',
                ],
            ],
        ];

        // 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->AddPage('L');
        $mPdf->WriteHTML($html);

        if ($appendSheets) {
            foreach ($rows as $row) {
                foreach ($row['states'] as $state) {
                    if ($state->hasSignature()) {
                        $mPdf->AddPage('P');
                        $sheetHtml = $this->twig->render('@FerienpassCore/export/consent/sheet.html.twig', [
                            'consent' => $state->getSignature(),
                        ]);
                        $mPdf->WriteHTML($sheetHtml);
                    }
                }
            }
        }

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