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

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\OfferDate;
use Ferienpass\CoreBundle\Settings\PageConfigManager;
use Recurr\Rule;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;

#[AsLiveComponent(template: '@FerienpassAdmin/components/Offer/OfferDateForm.html.twig', route: 'live_component_admin')]
class OfferDateForm extends AbstractController
{
    use ComponentToolsTrait;
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    #[LiveProp(hydrateWith: 'hydrateDate', dehydrateWith: 'dehydrateDate')]
    public OfferDate $date;

    #[LiveProp(writable: true, hydrateWith: 'hydrateDateTime', dehydrateWith: 'dehydrateDateTime', onUpdated: 'updatedBegin')]
    #[Assert\NotBlank(message: 'Bitte geben Sie einen Beginn an.')]
    public ?\DateTime $begin = null;

    #[LiveProp(writable: true, hydrateWith: 'hydrateDateTime', dehydrateWith: 'dehydrateDateTime')]
    #[Assert\NotBlank(message: 'Bitte geben Sie ein Ende an.')]
    #[Assert\GreaterThanOrEqual(propertyPath: 'begin', message: 'Das Ende muss nach dem Beginn liegen.')]
    public ?\DateTime $end = null;

    #[LiveProp(writable: true, onUpdated: 'updatedRecurring')]
    public bool $recurring = false;

    #[LiveProp(writable: true)]
    public string $mode = 'NONE';

    #[LiveProp(writable: true)]
    #[Assert\GreaterThanOrEqual(value: 1, message: 'Mindestens 1 Termin.')]
    public int $count = 5;

    public bool $success = false;
    public bool $wasNew = false;

    #[LiveProp(writable: true)]
    public ?string $applicationDeadline = null;
    private readonly string $timezone;

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly PageConfigManager $pageConfigManager
    ) {
        $this->timezone = $this->pageConfigManager->getPageConfig()->getTimezone();
    }

    public function updatedBegin(): void
    {
        if (!$this->begin) {
            return;
        }

        try {
            if (!$this->end) {
                $this->end = (clone $this->begin)->modify('+1 hour');

                return;
            }

            if ($this->begin >= $this->end) {
                $this->end = (clone $this->begin)->modify('+1 hour');
            }
        } catch (\Exception) {
        }
    }

    public function updatedRecurring(bool $value): void
    {
        if ($value && 'NONE' === $this->mode) {
            $this->mode = 'WEEKLY';
        }
    }

    public function getWeekdayName(): ?string
    {
        if (!$this->begin) {
            return null;
        }

        try {
            $date = $this->begin;
            $formatter = new \IntlDateFormatter('de_DE', \IntlDateFormatter::NONE, \IntlDateFormatter::NONE, $this->timezone, null, 'EEEE');

            return $formatter->format($date);
        } catch (\Exception) {
            return null;
        }
    }

    public function mount(OfferDate $date): void
    {
        $this->date = $date;
        $this->begin = $date->getBegin();
        $this->end = $date->getEnd();
        $this->recurring = null !== $date->getRecurrence();

        if ($this->recurring) {
            $recurrence = $date->getRecurrence();
            if (str_contains($recurrence, 'FREQ=DAILY')) {
                $this->mode = 'DAILY';
            } elseif (str_contains($recurrence, 'FREQ=WEEKLY')) {
                $this->mode = 'WEEKLY';
            } else {
                // Default to DAILY for unsupported types
                $this->mode = 'DAILY';
            }

            if (preg_match('/COUNT=(\d+)/', $recurrence, $matches)) {
                $this->count = (int) $matches[1];
            }
        }
    }

    public function dehydrateDate(OfferDate $date): int|array
    {
        if ($date->getId()) {
            return $date->getId();
        }

        return [
            'offer' => $date->getOffer()->getId(),
        ];
    }

    public function hydrateDate(mixed $value): OfferDate
    {
        if (\is_int($value)) {
            $date = $this->entityManager->find(OfferDate::class, $value);
            if (!$date instanceof OfferDate) {
                throw new NotFoundHttpException(\sprintf('OfferDate %d no longer exists.', $value));
            }

            return $date;
        }

        if (\is_array($value) && isset($value['offer'])) {
            $offer = $this->entityManager->find(OfferInterface::class, $value['offer']);
            if (!$offer instanceof OfferInterface) {
                throw new NotFoundHttpException(\sprintf('Offer %s no longer exists.', (string) $value['offer']));
            }

            return new OfferDate($offer);
        }

        throw new NotFoundHttpException('Unable to hydrate OfferDate from the given props.');
    }

    public function hydrateDateTime(?string $value): ?\DateTime
    {
        if (!$value) {
            return null;
        }

        try {
            return new \DateTime($value, new \DateTimeZone($this->timezone));
        } catch (\DateMalformedStringException) {
            return null;
        }
    }

    public function dehydrateDateTime(?\DateTime $value): ?string
    {
        if (!$value) {
            return null;
        }

        return (clone $value)->setTimezone(new \DateTimeZone($this->timezone))->format('Y-m-d\TH:i');
    }

    #[LiveAction]
    public function save()
    {
        $this->denyAccessUnlessGranted('edit', $this->date->getOffer());

        $this->validate();
        $this->wasNew = null === $this->date->getId();

        $this->date->setBegin($this->begin ? (clone $this->begin)->setTimezone(new \DateTimeZone(date_default_timezone_get())) : null);
        $this->date->setEnd($this->end ? (clone $this->end)->setTimezone(new \DateTimeZone(date_default_timezone_get())) : null);

        if (!$this->recurring || 'NONE' === $this->mode) {
            $this->date->setRecurrence(null);
        } else {
            $rule = new Rule();
            $rule->setStartDate($this->date->getBegin());
            $rule->setFreq($this->mode);
            $rule->setInterval(1);

            if ($this->count) {
                $rule->setCount($this->count);
            }

            $this->date->setRecurrence($rule->getString());
        }

        // Persist new OfferDate entities created via admin_offer_date_add
        if (null === $this->date->getId()) {
            $this->entityManager->persist($this->date);
        }

        $this->entityManager->flush();
        $this->success = true;
    }
}
