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

use Ferienpass\CoreBundle\Entity\EditionTask;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Settings\PageConfigManager;

/**
 * Determines for how long a host (Veranstalter:in) may access the data of a
 * participant booked into one of their offers.
 *
 * The access window ends with the edition's "Teilnahmelisten verfügbar"
 * ({@see EditionTask} type `publish_lists`) period. When no such task gates
 * the edition, access falls back to the offer's end date plus a configurable
 * retention period (PageConfig::hostDataRetentionPeriod, default 6 months).
 *
 * This is the single source of truth shared by the public "Datennutzung"
 * transparency page and the admin-side access enforcement (OfferVoter).
 */
class HostDataAccess
{
    private const DEFAULT_RETENTION = '6 months';

    public function __construct(private readonly PageConfigManager $pageConfigManager)
    {
    }

    /**
     * The date on which the host loses access to the offer's participant data,
     * or null when it cannot be determined (no edition task and no offer end).
     */
    public function accessEndDate(OfferInterface $offer): ?\DateTimeImmutable
    {
        // Prefer the edition's "publish_lists" task: that window defines when
        // hosts may see the participant lists. Use the latest end date if more
        // than one such task exists.
        $edition = $offer->getEdition();
        if (null !== $edition) {
            $end = null;
            foreach ($edition->getTasksOfType('publish_lists') as $task) {
                $periodEnd = $task->getPeriodEnd();
                if (null !== $periodEnd && (null === $end || $periodEnd > $end)) {
                    $end = $periodEnd;
                }
            }

            if (null !== $end) {
                return \DateTimeImmutable::createFromInterface($end);
            }
        }

        // Fallback: offer end date + configured retention period.
        $offerEnd = $offer->getDateEnd();
        if (null === $offerEnd) {
            return null;
        }

        return \DateTimeImmutable::createFromInterface($offerEnd)->add($this->retentionInterval());
    }

    /**
     * Whether the host still has access at the given moment (defaults to now).
     * Access is granted while no end date is known or the end lies in the future.
     */
    public function hasAccess(OfferInterface $offer, ?\DateTimeInterface $now = null): bool
    {
        $end = $this->accessEndDate($offer);
        if (null === $end) {
            return true;
        }

        $now ??= new \DateTimeImmutable();

        return $now < $end;
    }

    /**
     * The configured fallback retention period as a DateInterval, defaulting
     * to 6 months when unset or malformed.
     */
    public function retentionInterval(): \DateInterval
    {
        $period = $this->pageConfigManager->getPageConfig()->getHostDataRetentionPeriod();
        if (null === $period || !preg_match('/^(\d+)\s+(day|days|week|weeks|month|months|year|years)$/i', trim($period), $m)) {
            $period = self::DEFAULT_RETENTION;
            preg_match('/^(\d+)\s+(\w+)$/', $period, $m);
        }

        $count = (int) $m[1];
        $unit = strtolower(rtrim($m[2], 's'));

        return match ($unit) {
            'day' => new \DateInterval('P'.$count.'D'),
            'week' => new \DateInterval('P'.($count * 7).'D'),
            'year' => new \DateInterval('P'.$count.'Y'),
            default => new \DateInterval('P'.$count.'M'),
        };
    }
}
