<?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\CmsBundle\Components;

use Ferienpass\CoreBundle\DataAccess\HostDataAccess;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Host;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Settings\PageConfigManager;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

/**
 * Data-usage transparency overview: which hosts (Veranstalter:innen) can
 * currently access the family's data, exactly what data that is, and until
 * when. Plus the responsible body (verantwortliche Stelle / Impressum) which
 * has account-wide access for the lifetime of the account.
 *
 * Read-only. The interactive parts (expanding a recipient) are pure CSS via
 * native <details> in the template — no server round-trips.
 */
#[AsTwigComponent(name: 'Cms:DataUsage', template: '@Contao/components/data_usage.html.twig')]
class DataUsage
{
    /** A small palette so each participant gets a stable colour accent. */
    private const TINTS = ['#d9582b', '#2f6f57', '#2b6f9e', '#7b5ea7', '#b8852a'];

    /** @var array<string,mixed>|null */
    private ?array $model = null;

    public function __construct(
        private readonly Security $security,
        private readonly HostDataAccess $hostDataAccess,
        private readonly PageConfigManager $pageConfigManager,
        #[Autowire(param: 'ferienpass_admin.participant_list.hosts_can_edit')]
        private readonly bool $hostsCanEdit = false,
    ) {
    }

    /**
     * @return array{name: string, phone: ?string, mobile: ?string, email: ?string}|null
     */
    #[ExposeInTemplate]
    public function guardian(): ?array
    {
        return $this->build()['guardian'];
    }

    /**
     * @return array<array{name: string, initials: string, tint: string, dateOfBirth: ?\DateTimeInterface, age: ?int}>
     */
    #[ExposeInTemplate]
    public function children(): array
    {
        return $this->build()['children'];
    }

    /**
     * @return array{name: string, department: ?string, address: ?string}|null
     */
    #[ExposeInTemplate]
    public function controller(): ?array
    {
        return $this->build()['controller'];
    }

    /**
     * Recipients grouped by host, each with one block per booked offer.
     */
    #[ExposeInTemplate]
    public function recipients(): array
    {
        return $this->build()['recipients'];
    }

    #[ExposeInTemplate]
    public function hasPayments(): bool
    {
        return $this->build()['hasPayments'];
    }

    /**
     * The fallback retention window in whole months (rounded up), for the
     * legal note. Defaults to 6 when the configured period is sub-monthly.
     */
    #[ExposeInTemplate]
    public function retentionMonths(): int
    {
        $interval = $this->hostDataAccess->retentionInterval();
        $months = $interval->y * 12 + $interval->m + (int) ceil($interval->d / 30);

        return max(1, $months);
    }

    /**
     * @return array<string,mixed>
     */
    private function build(): array
    {
        if (null !== $this->model) {
            return $this->model;
        }

        $user = $this->security->getUser();
        if (!$user instanceof User) {
            return $this->model = [
                'guardian' => null,
                'children' => [],
                'controller' => null,
                'recipients' => [],
                'hasPayments' => false,
            ];
        }

        // ── stored profile data ──────────────────────────────────────────
        $guardian = [
            'name' => $user->getName(),
            'phone' => $user->getPhone(),
            'mobile' => $user->getMobile(),
            'email' => $user->getEmail(),
        ];

        $tintFor = [];
        $children = [];
        $index = 0;
        foreach ($user->getParticipants() as $participant) {
            $tint = self::TINTS[$index % \count(self::TINTS)];
            $tintFor[(string) $participant->getId()] = $tint;
            $children[] = [
                'name' => $participant->getName(),
                'initials' => $this->initials($participant->getName()),
                'tint' => $tint,
                'dateOfBirth' => $participant->getDateOfBirth(),
                'age' => $participant->getAge(),
            ];
            ++$index;
        }

        // ── recipients (hosts with current access via an attendance) ─────
        $byHost = [];
        foreach ($user->getParticipants() as $participant) {
            foreach ($participant->getAttendances() as $attendance) {
                $offer = $this->offerWithAccess($attendance);
                if (null === $offer) {
                    continue;
                }

                $tint = $tintFor[(string) $participant->getId()] ?? self::TINTS[0];
                $accessUntil = $this->hostDataAccess->accessEndDate($offer);

                foreach ($offer->getHosts() as $host) {
                    if (!$host instanceof Host) {
                        continue;
                    }

                    $hostId = (string) $host->getId();
                    $byHost[$hostId] ??= [
                        'host' => $host->getName() ?? '',
                        'address' => $this->addressOf($host),
                        'accessUntil' => null,
                        'offers' => [],
                    ];

                    $byHost[$hostId]['offers'][] = [
                        'offer' => $offer,
                        'offerName' => $offer->getName(),
                        'childName' => $participant->getName(),
                        'childInitials' => $this->initials($participant->getName()),
                        'childTint' => $tint,
                        'age' => $participant->getAge(),
                        'dateOfBirth' => $participant->getDateOfBirth(),
                        'collectDob' => $offer->isCollectDateOfBirth(),
                        'endDate' => $offer->getDateEnd(),
                        'accessUntil' => $accessUntil,
                    ];

                    if (null !== $accessUntil && (null === $byHost[$hostId]['accessUntil'] || $accessUntil > $byHost[$hostId]['accessUntil'])) {
                        $byHost[$hostId]['accessUntil'] = $accessUntil;
                    }
                }
            }
        }

        // Soonest-expiring recipient first.
        uasort($byHost, static function (array $a, array $b): int {
            return [null === $a['accessUntil'], $a['accessUntil']] <=> [null === $b['accessUntil'], $b['accessUntil']];
        });

        // ── responsible body (Impressum) ─────────────────────────────────
        $config = $this->pageConfigManager->getPageConfig();
        $controller = $config->hasImprint() ? [
            'name' => $config->getImprintName(),
            'department' => $config->getImprintDepartment(),
            'address' => $config->getImprintAddressLine(),
        ] : null;

        return $this->model = [
            'guardian' => $guardian,
            'children' => $children,
            'controller' => $controller,
            'recipients' => array_values($byHost),
            'hasPayments' => !$user->getPayments()->isEmpty(),
        ];
    }

    /**
     * Returns the attendance's offer if the host can actually see this
     * participant on their list right now, or null otherwise.
     *
     * This mirrors the host-facing participant-list query in
     * {@see \Ferienpass\AdminBundle\Controller\Page\OfferParticipantsController}:
     * a host without edit rights only sees CONFIRMED bookings; with edit rights
     * they see everyone except WITHDRAWN. On top of that, the access window
     * (publish_lists task / retention fallback) must still be open.
     */
    private function offerWithAccess(Attendance $attendance): ?\Ferienpass\CoreBundle\Entity\Offer\OfferInterface
    {
        $status = $attendance->getStatus();
        if (null === $status) {
            return null;
        }

        if ($this->hostsCanEdit) {
            if (AttendanceStatus::WITHDRAWN === $status) {
                return null;
            }
        } elseif (AttendanceStatus::CONFIRMED !== $status) {
            return null;
        }

        $offer = $attendance->getOffer();
        if (!$this->hostDataAccess->hasAccess($offer)) {
            return null;
        }

        return $offer;
    }

    private function addressOf(Host $host): ?string
    {
        $address = $host->getPostalAddresses()->first();
        if (false === $address) {
            return null;
        }

        $cityLine = trim(($address->getPostalCode() ?? '').' '.($address->getCity() ?? ''));
        $parts = array_filter([$address->getStreet(), '' !== $cityLine ? $cityLine : null]);

        return $parts ? implode(', ', $parts) : null;
    }

    private function initials(string $name): string
    {
        $words = preg_split('/\s+/', trim($name)) ?: [];
        $letters = array_map(static fn (string $w): string => mb_substr($w, 0, 1), \array_slice($words, 0, 2));

        return mb_strtoupper(implode('', $letters));
    }
}
