<?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\Attendance;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Export\FormulaEscaper;
use Ferienpass\CoreBundle\Repository\ConsentFormRepository;
use Ferienpass\CoreBundle\Settings\SystemSettingsManager;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\Translation\TranslatorInterface;

final readonly class XlsxExport
{
    public function __construct(
        private Filesystem $filesystem,
        private Security $security,
        private ConsentManager $consentManager,
        private ConsentFormRepository $consentFormRepository,
        private TranslatorInterface $translator,
        private readonly SystemSettingsManager $systemSettingsManager
    ) {
    }

    public function generate(OfferInterface $offer, ?string $destination = null): string
    {
        $spreadsheet = $this->createSpreadsheet($offer);
        $hash = md5(serialize($offer->getId()));
        $xlsxPath = \sprintf('%s/xlsx-%s.xlsx', sys_get_temp_dir(), $hash);

        $writer = new XlsxWriter($spreadsheet);
        $writer->save($xlsxPath);

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

        return $destination ?? $xlsxPath;
    }

    private function createSpreadsheet(OfferInterface $offer): Spreadsheet
    {
        $spreadsheet = new Spreadsheet();
        $spreadsheet->getDefaultStyle()->getFont()->setName('Arial');
        $sheet = $spreadsheet->getActiveSheet();

        // Set title
        $sheet->setTitle('Teilnehmerliste');

        // Header
        $sheet->setCellValue('A1', 'Teilnehmerliste: '.$offer->getName());
        $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);

        // Column headers
        $headers = ['Nr.', 'Name', 'Vorname'];
        if ($this->security->isGranted('ROLE_PARTICIPANTS_ADMIN') || $offer->isCollectDateOfBirth()) {
            $headers[] = 'Geburtsdatum';
        }
        $headers[] = 'Alter';
        $headers[] = 'E-Mail';
        $headers[] = 'Mobil';
        $headers[] = 'Telefon';
        $headers[] = 'Status';

        $consentForms = $this->consentFormRepository->findForOffer($offer);
        foreach ($consentForms as $consentForm) {
            $headers[] = $consentForm->getTitle() ?: 'Einwilligung';
        }

        foreach ($offer->getApplicationExtra() ?? [] as $extra) {
            $headers[] = $extra['question'];
        }

        $headers[] = 'Wichtige Info';

        $colIdx = 1;
        foreach ($headers as $header) {
            $col = Coordinate::stringFromColumnIndex($colIdx);
            $sheet->setCellValue($col.'3', $header);
            $sheet->getStyle($col.'3')->getFont()->setBold(true);
            ++$colIdx;
        }

        $sheet->mergeCells('A1:'.Coordinate::stringFromColumnIndex($colIdx - 1).'1');

        // Get participants
        $attendances = $offer->getAttendancesConfirmedOrWaitlisted();

        // Sort participants based on configuration
        $attendancesArray = iterator_to_array($attendances);

        // Group by status FIRST, then apply the configured sort within the group.
        // Confirmed and waiting-list entries must never interleave: this sheet is
        // printed and taken to the event, and a waiting-list child sitting between
        // two confirmed ones reads as a regular participant — which is exactly how
        // waiting-list children ended up being called out on site.
        usort($attendancesArray, function (Attendance $a, Attendance $b) {
            $statusOrder = [AttendanceStatus::CONFIRMED => 1, AttendanceStatus::WAITLISTED => 2];
            $byStatus = ($statusOrder[$a->getStatus()] ?? 3) <=> ($statusOrder[$b->getStatus()] ?? 3);
            if (0 !== $byStatus) {
                return $byStatus;
            }

            if ('name' === $this->systemSettingsManager->getParticipantListSort()) {
                return strcasecmp($a->getParticipant()->getLastname().' '.$a->getParticipant()->getFirstname(), $b->getParticipant()->getLastname().' '.$b->getParticipant()->getFirstname());
            }

            return ($a->getSorting() <=> $b->getSorting())
                ?: strcasecmp($a->getParticipant()->getLastname(), $b->getParticipant()->getLastname())
                ?: strcasecmp($a->getParticipant()->getFirstname(), $b->getParticipant()->getFirstname());
        });

        // Data rows
        $row = 4;
        $nr = 1;
        $lastColumn = Coordinate::stringFromColumnIndex($colIdx - 1);
        $section = null;
        foreach ($attendancesArray as $attendance) {
            // Announce every status change with a full-width banner row and start
            // counting from 1 again, so no waiting-list entry can be mistaken for
            // the continuation of the participant list.
            if ($section !== $attendance->getStatus()) {
                $section = $attendance->getStatus();

                if (null !== $section && AttendanceStatus::CONFIRMED !== $section) {
                    $sheet->setCellValue('A'.$row, mb_strtoupper($this->translator->trans('status.'.$section)));
                    $sheet->mergeCells('A'.$row.':'.$lastColumn.$row);
                    $sheet->getStyle('A'.$row)->getFont()->setBold(true);
                    ++$row;
                }

                $nr = 1;
            }

            $participant = $attendance->getParticipant();
            $user = $participant->getUser();

            $sheet->setCellValue('A'.$row, $nr);
            $sheet->setCellValue('B'.$row, FormulaEscaper::escape($participant->getLastname()));
            $sheet->setCellValue('C'.$row, FormulaEscaper::escape($participant->getFirstname()));

            $colIdx = 4;
            if ($this->security->isGranted('ROLE_PARTICIPANTS_ADMIN') || $offer->isCollectDateOfBirth()) {
                if ($participant->getDateOfBirth()) {
                    $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx).$row, $participant->getDateOfBirth()->format('d.m.Y'));
                }
                ++$colIdx;
            }

            $begin = $offer->getDates()->first() ? $offer->getDates()->first()->getBegin() : null;
            $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, $participant->getAge($begin));

            $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, FormulaEscaper::escape($attendance->getEmail() ?: $participant->getEmail()));
            $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, FormulaEscaper::escape($participant->getMobile()));
            $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, FormulaEscaper::escape($attendance->getPhone() ?: $participant->getPhone()));

            $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, $this->translator->trans('status.'.$attendance->getStatus()));

            foreach ($consentForms as $consentForm) {
                // Cardinality is driven by isPerParticipant(), NOT isUserLevel():
                // an edition-scoped participant consent is "user level" for
                // placement but signed once per participant. Passing null there
                // made ConsentManager match against participant === null, so every
                // child fell through to "Nein". See PdfExport for the full
                // rationale; mirrors the (correct) list view.
                $subject = $consentForm->isPerParticipant() ? $participant : null;
                if (null !== $user) {
                    $state = $this->consentManager->findState($consentForm, $user, $subject);
                } else {
                    $state = new ConsentState($consentForm, participant: $subject);
                }
                $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, $state->isAgreed() ? 'Ja' : 'Nein');
            }

            foreach ($offer->getApplicationExtra() ?? [] as $extra) {
                $val = $attendance->getExtra($extra['uuid']);
                if ('yn' === $extra['type']) {
                    $val = 'y' === $val ? 'Ja' : 'Nein';
                }
                $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, FormulaEscaper::escape($val));
            }

            if (method_exists($participant, 'getSharedNotes')) {
                $sheet->setCellValue(Coordinate::stringFromColumnIndex($colIdx++).$row, FormulaEscaper::escape($participant->getSharedNotes()));
            }

            ++$row;
            ++$nr;
        }

        // Auto-size columns
        for ($i = 1; $i < $colIdx; ++$i) {
            $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setAutoSize(true);
        }

        return $spreadsheet;
    }
}
