<?php

namespace App\Controller;

use App\Entity\Domain;
use App\Entity\DomainEvent;
use App\Entity\DomainStatus;
use App\Entity\Watchlist;
use App\Repository\WatchlistRepository;
use App\Service\CalendarService;
use Eluceo\iCal\Domain\Entity\Calendar;
use Eluceo\iCal\Presentation\Component\Property;
use Eluceo\iCal\Presentation\Component\Property\Value\TextValue;
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
use Laminas\Feed\Writer\Entry;
use Laminas\Feed\Writer\Feed;
use Sabre\VObject\EofException;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\ParseException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class WatchlistController extends AbstractController
{
    public function __construct(
        private readonly WatchlistRepository $watchlistRepository,
        private readonly CalendarService $calendarService,
    ) {
    }

    /**
     * @throws ParseException
     * @throws EofException
     * @throws InvalidDataException
     * @throws \Exception
     */
    #[Route(
        path: '/api/watchlists/{token}/calendar',
        name: 'watchlist_calendar',
        defaults: [
            '_api_resource_class' => Watchlist::class,
            '_api_operation_name' => 'calendar',
        ]
    )]
    public function getWatchlistCalendar(string $token): Response
    {
        /** @var Watchlist $watchlist */
        $watchlist = $this->watchlistRepository->findOneBy(['token' => $token]);

        $calendar = new Calendar();

        /** @var Domain $domain */
        foreach ($watchlist->getDomains()->getIterator() as $domain) {
            foreach ($this->calendarService->getDomainCalendarEvents($domain) as $event) {
                $calendar->addEvent($event);
            }
        }

        $calendarResponse = (new CalendarFactory())->createCalendar($calendar);
        $calendarName = $watchlist->getName();
        if (null !== $calendarName) {
            $calendarResponse->withProperty(new Property('X-WR-CALNAME', new TextValue($calendarName)));
        }

        return new Response($calendarResponse, Response::HTTP_OK, [
            'Content-Type' => 'text/calendar; charset=utf-8',
        ]);
    }

    /**
     * @throws \Exception
     */
    #[Route(
        path: '/api/watchlists/{token}/rss/events',
        name: 'watchlist_rss_events',
        defaults: [
            '_api_resource_class' => Watchlist::class,
            '_api_operation_name' => 'rss_events',
        ]
    )]
    public function getWatchlistRssEventsFeed(string $token, Request $request): Response
    {
        /** @var Watchlist $watchlist */
        $watchlist = $this->watchlistRepository->findOneBy(['token' => $token]);

        $feed = (new Feed())
            ->setLanguage('en')
            ->setCopyright('Domain Watchdog makes this information available "as is," and does not guarantee its accuracy.')
            ->setTitle('Domain events ('.$watchlist->getName().')')
            ->setGenerator('Domain Watchdog - RSS Feed', null, 'https://github.com/maelgangloff/domain-watchdog')
            ->setDescription('The latest events for domain names in your Watchlist')
            ->setLink($request->getSchemeAndHttpHost().'/#/tracking/watchlist')
            ->setFeedLink($request->getSchemeAndHttpHost().'/api/watchlists/'.$token.'/rss/events', 'atom')
            ->setDateCreated($watchlist->getCreatedAt());

        /** @var Domain $domain */
        foreach ($watchlist->getDomains()->getIterator() as $domain) {
            foreach ($this->getRssEventEntries($request->getSchemeAndHttpHost(), $domain) as $entry) {
                $feed->addEntry($entry);
            }
        }

        return new Response($feed->export('atom'), Response::HTTP_OK, [
            'Content-Type' => 'application/atom+xml; charset=utf-8',
        ]);
    }

    /**
     * @throws \Exception
     */
    #[Route(
        path: '/api/watchlists/{token}/rss/status',
        name: 'watchlist_rss_status',
        defaults: [
            '_api_resource_class' => Watchlist::class,
            '_api_operation_name' => 'rss_status',
        ]
    )]
    public function getWatchlistRssStatusFeed(string $token, Request $request): Response
    {
        /** @var Watchlist $watchlist */
        $watchlist = $this->watchlistRepository->findOneBy(['token' => $token]);

        $feed = (new Feed())
            ->setLanguage('en')
            ->setCopyright('Domain Watchdog makes this information available "as is," and does not guarantee its accuracy.')
            ->setTitle('Domain EPP status ('.$watchlist->getName().')')
            ->setGenerator('Domain Watchdog - RSS Feed', null, 'https://github.com/maelgangloff/domain-watchdog')
            ->setDescription('The latest changes to the EPP status of the domain names in your Watchlist')
            ->setLink($request->getSchemeAndHttpHost().'/#/tracking/watchlist')
            ->setFeedLink($request->getSchemeAndHttpHost().'/api/watchlists/'.$token.'/rss/status', 'atom')
            ->setDateCreated($watchlist->getCreatedAt());

        /** @var Domain $domain */
        foreach ($watchlist->getDomains()->getIterator() as $domain) {
            foreach ($this->getRssStatusEntries($request->getSchemeAndHttpHost(), $domain) as $entry) {
                $feed->addEntry($entry);
            }
        }

        return new Response($feed->export('atom'), Response::HTTP_OK, [
            'Content-Type' => 'application/atom+xml; charset=utf-8',
        ]);
    }

    /**
     * @throws \Exception
     */
    private function getRssEventEntries(string $baseUrl, Domain $domain): array
    {
        $entries = [];
        foreach ($domain->getEvents()->filter(fn (DomainEvent $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $event) {
            $entries[] = (new Entry())
                ->setId($baseUrl.'/api/domains/'.$domain->getLdhName().'#events-'.$event->getId())
                ->setTitle($domain->getLdhName().': '.$event->getAction().'  - event update')
                ->setDescription('Domain name event')
                ->setLink($baseUrl.'/#/search/domain/'.$domain->getLdhName())
                ->setContent($this->render('rss/event_entry.html.twig', [
                    'event' => $event,
                ])->getContent())
                ->setDateModified($event->getDate())
                ->addAuthor(['name' => strtoupper($domain->getTld()->getTld()).' Registry']);
        }

        return $entries;
    }

    /**
     * @throws \Exception
     */
    private function getRssStatusEntries(string $baseUrl, Domain $domain): array
    {
        $entries = [];
        foreach ($domain->getDomainStatuses()->filter(fn (DomainStatus $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $domainStatus) {
            $authors = [['name' => strtoupper($domain->getTld()->getTld()).' Registry']];
            /** @var string $status */
            foreach ([...$domainStatus->getAddStatus(), ...$domainStatus->getDeleteStatus()] as $status) {
                if (str_starts_with($status, 'client')) {
                    $authors[] = ['name' => 'Registrar'];
                    break;
                }
            }

            $entries[] = (new Entry())
                ->setId($baseUrl.'/api/domains/'.$domain->getLdhName().'#status-'.$domainStatus->getId())
                ->setTitle($domain->getLdhName().' - EPP status update')
                ->setDescription('There has been a change in the EPP status of the domain name.')
                ->setLink($baseUrl.'/#/search/domain/'.$domain->getLdhName())
                ->setContent($this->render('rss/status_entry.html.twig', [
                    'domainStatus' => $domainStatus,
                ])->getContent())
                ->setDateCreated($domainStatus->getCreatedAt())
                ->setDateModified($domainStatus->getDate())
                ->addCategory(['term' => $domain->getLdhName()])
                ->addCategory(['term' => strtoupper($domain->getTld()->getTld())])
                ->addAuthors($authors)
            ;
        }

        return $entries;
    }
}
