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

use Ferienpass\CoreBundle\Entity\Edition;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Repository\HostRepository;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Twig\Environment;
use Twig\Extension\CoreExtension;

#[AsLiveComponent('Admin:OfferCalendar', route: 'live_component_admin')]
final class OfferCalendar extends AbstractController
{
    use DefaultActionTrait;

    #[LiveProp]
    public ?Edition $edition = null;

    /**
     * Edition aliases to filter by when more than one edition is selected
     * (the headline switcher writes them into the URL path). Empty means "no
     * filter" — falls back to {@see $edition} if that is set instead.
     *
     * @var string[]
     */
    #[LiveProp]
    public array $editionAliases = [];

    #[LiveProp(url: true, writable: true)]
    public string $date = 'today';

    #[LiveProp(url: true, writable: true)]
    public string $view = 'month';

    public function __construct(
        private readonly OfferRepositoryInterface $offerRepository,
        private readonly HostRepository $hostRepository,
        private readonly Environment $twig,
    ) {
    }

    #[CurrentUser]
    public function getUser(): ?User
    {
        return parent::getUser();
    }

    public function getReferenceDate(): \DateTimeImmutable
    {
        $tz = $this->getTimezone();
        try {
            if ('today' === $this->date) {
                return new \DateTimeImmutable('today', $tz);
            }

            return new \DateTimeImmutable($this->date, $tz);
        } catch (\Exception) {
            return new \DateTimeImmutable('today', $tz);
        }
    }

    public function getDays(): array
    {
        $tz = $this->getTimezone();
        $referenceDate = $this->getReferenceDate();
        $days = [];

        if ('day' === $this->view) {
            $days[] = [
                'date' => $referenceDate,
                'isToday' => $referenceDate->format('Y-m-d') === new \DateTimeImmutable('now', $tz)->format('Y-m-d'),
                'events' => [],
            ];
        } elseif ('week' === $this->view) {
            $start = $referenceDate->modify('Monday this week');
            $currentDate = $start;
            for ($i = 0; $i < 7; ++$i) {
                $days[] = [
                    'date' => $currentDate,
                    'isToday' => $currentDate->format('Y-m-d') === new \DateTimeImmutable('now', $tz)->format('Y-m-d'),
                    'events' => [],
                ];
                $currentDate = $currentDate->modify('+1 day');
            }
        } else {
            $firstDayOfMonth = $referenceDate->modify('first day of this month');
            $currentDate = $firstDayOfMonth->modify('Monday this week');

            while (\count($days) < 42) {
                $days[] = [
                    'date' => $currentDate,
                    'isCurrentMonth' => $currentDate->format('m') === $referenceDate->format('m'),
                    'isToday' => $currentDate->format('Y-m-d') === new \DateTimeImmutable('now', $tz)->format('Y-m-d'),
                    'events' => [],
                ];
                $currentDate = $currentDate->modify('+1 day');
            }
        }

        $this->populateEvents($days);

        return $days;
    }

    public function getSidebarDays(): array
    {
        $tz = $this->getTimezone();
        $referenceDate = $this->getReferenceDate();
        $firstDayOfMonth = $referenceDate->modify('first day of this month');
        $currentDate = $firstDayOfMonth->modify('Monday this week');
        $days = [];

        while (\count($days) < 42) {
            $days[] = [
                'date' => $currentDate,
                'isCurrentMonth' => $currentDate->format('m') === $referenceDate->format('m'),
                'isToday' => $currentDate->format('Y-m-d') === new \DateTimeImmutable('now', $tz)->format('Y-m-d'),
                'isSelected' => $currentDate->format('Y-m-d') === $referenceDate->format('Y-m-d'),
            ];
            $currentDate = $currentDate->modify('+1 day');
        }

        return $days;
    }

    public function getCreateUrl(): ?string
    {
        if ($this->edition instanceof Edition) {
            return $this->isGranted('ROLE_ADMIN') || $this->isGranted('offer.create', $this->edition)
                ? $this->generateUrl('admin_offers_new_in_edition', array_filter(['edition' => $this->edition->getAlias()]))
                : null;
        }

        return $this->generateUrl('admin_offers_new');
    }

    #[LiveAction]
    public function navigate(#[LiveArg] string $direction, #[LiveArg] ?string $interval = null): void
    {
        $date = $this->getReferenceDate();
        $interval ??= match ($this->view) {
            'day' => '1 day',
            'week' => '1 week',
            default => '1 month',
        };

        if ('prev' === $direction) {
            $this->date = $date->modify("- $interval")->format('Y-m-d');
        } elseif ('next' === $direction) {
            $this->date = $date->modify("+ $interval")->format('Y-m-d');
        } elseif ('today' === $direction) {
            $this->date = new \DateTimeImmutable('now', $this->getTimezone())->format('Y-m-d');
        }
    }

    #[LiveAction]
    public function changeView(#[LiveArg] string $view): void
    {
        $this->view = $view;
    }

    #[LiveAction]
    public function setDate(#[LiveArg] string $date): void
    {
        $this->date = $date;
        $this->view = 'day';
    }

    private function populateEvents(array &$days): void
    {
        if (empty($days)) {
            return;
        }

        $start = $days[0]['date']->setTime(0, 0);
        $end = end($days)['date']->setTime(23, 59, 59);

        $qb = $this->offerRepository->createQueryBuilder('o')
            ->innerJoin('o.dates', 'd')
            ->addSelect('d')
            ->where('d.begin <= :end')
            ->andWhere('d.end >= :start')
            ->setParameter('start', $start)
            ->setParameter('end', $end)
        ;

        if (!empty($this->editionAliases)) {
            $qb->innerJoin('o.edition', 'e')->andWhere('e.alias IN (:editionAliases)')->setParameter('editionAliases', $this->editionAliases);
        } elseif ($this->edition) {
            $qb->andWhere('o.edition = :edition')->setParameter('edition', $this->edition);
        }

        if (!$this->isGranted('ROLE_ADMIN')) {
            $user = $this->getUser();
            if ($user instanceof User) {
                $qb->innerJoin('o.hosts', 'h');
                $qb->andWhere('h IN (:hosts)')->setParameter('hosts', $this->hostRepository->findByUser($user));
            } else {
                $qb->andWhere('1 = 0');
            }
        }

        $offers = $qb->getQuery()->getResult();

        foreach ($offers as $offer) {
            foreach ($offer->getDates() as $date) {
                $begin = $date->getBegin();
                $dateEnd = $date->getEnd();
                if (!$begin || !$dateEnd) {
                    continue;
                }

                if ($dateEnd < $start || $begin > $end) {
                    continue;
                }

                // Normalize to the application's default timezone for positioning and day grouping
                $tz = $this->getTimezone();
                $beginLocal = \DateTimeImmutable::createFromInterface($begin)->setTimezone($tz);
                $endLocal = \DateTimeImmutable::createFromInterface($dateEnd)->setTimezone($tz);

                $dayStr = $beginLocal->format('Y-m-d');
                foreach ($days as &$day) {
                    if ($day['date']->format('Y-m-d') === $dayStr) {
                        $event = [
                            'name' => $offer->getName(),
                            'begin' => $begin,
                            'end' => $dateEnd,
                            'url' => $this->generateUrl('admin_offers_show', ['uuid' => $offer->getUuid()->toBase58()]),
                            'meetingPoint' => $offer->getMeetingPoint(),
                            'status' => (string) array_key_first($offer->getStatus()),
                        ];

                        if ('week' === $this->view || 'day' === $this->view) {
                            $hour = (int) $beginLocal->format('H');
                            $minute = (int) $beginLocal->format('i');
                            $duration = ($dateEnd->getTimestamp() - $begin->getTimestamp()) / 60;

                            $event['startRow'] = ($hour * 12) + (int) ($minute / 5) + 2;
                            $event['span'] = max(1, (int) ($duration / 5));
                            $event['dayIndex'] = (int) $beginLocal->format('N');
                        }

                        $day['events'][] = $event;
                    }
                }
            }
        }
    }

    private function getTimezone(): \DateTimeZone
    {
        return $this->twig->getExtension(CoreExtension::class)->getTimezone();
    }
}
