<?php

namespace App\Monitor\Action\Handler;

use Aimeos\Map;
use App\Monitor\Action\Command\MonitorMovieCommand;
use App\Monitor\Action\Command\MonitorTvEpisodeCommand;
use App\Monitor\Action\Result\MonitorTvShowResult;
use App\Monitor\Framework\Entity\Monitor;
use App\Monitor\Framework\Repository\MonitorRepository;
use App\Monitor\Service\MediaFiles;
use App\Tmdb\Tmdb;
use Carbon\Carbon;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Nihilarr\PTN;
use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface;
use Psr\Log\LoggerInterface;

/** @implements HandlerInterface<MonitorMovieCommand> */
readonly class MonitorTvShowHandler implements HandlerInterface
{
    public function __construct(
        private MonitorRepository $monitorRepository,
        private EntityManagerInterface $entityManager,
        private MonitorTvEpisodeHandler $monitorTvEpisodeHandler,
        private MediaFiles $mediaFiles,
        private LoggerInterface $logger,
        private Tmdb $tmdb,
    ) {}

    public function handle(CommandInterface $command): ResultInterface
    {
        $this->logger->info('> [MonitorTvShowHandler] Executing MonitorTvShowHandler');
        $monitor = $this->monitorRepository->find($command->monitorId);

        // Check current episodes
        $downloadedEpisodes = $this->mediaFiles
            ->getEpisodes($monitor->getTitle())
            ->map(fn($episode) => (object) (new PTN())->parse($episode))
            ->filter(fn ($episode) =>
                property_exists($episode, 'episode')
                && property_exists($episode, 'season')
                && null !== $episode->episode
                && null !== $episode->season
            )
        ;
        $this->logger->info('> [MonitorTvShowHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());

        // Compare against list from TMDB
        $episodesInShow = Map::from(
            $this->tmdb->tvDetails($monitor->getTmdbId())->episodes
        )->flat(1);

        $this->logger->info('> [MonitorTvShowHandler] Found ' . count($episodesInShow) . ' episodes for title: ' . $monitor->getTitle());

        $episodeMonitors = [];
        if ($downloadedEpisodes->count() !== $episodesInShow->count()) {
            // Dispatch Episode commands for each missing Episode
            foreach ($episodesInShow as $episode) {
                // Only monitor future episodes
                $episodeInFuture = $this->episodeReleasedAfterMonitorCreated($monitor->getCreatedAt(), $episode);
                $this->logger->info('> [MonitorTvShowHandler] Episode released after monitor started for season ' . $episode['season_number'] . ' episode ' . $episode['episode_number'] . ' for title: ' . $monitor->getTitle() . ' ? ' . (true === $episodeInFuture ? 'YES' : 'NO'));
                if (false === $episodeInFuture) {
                    $this->logger->info('> [MonitorTvShowHandler] Episode released after monitor started for title: ' . 'for season ' . $episode['season_number'] . ' episode ' . $episode['episode_number'] . ', skipping');
                    continue;
                }

                // Check if the episode is already downloaded
                $episodeExists = $this->episodeExists($episode, $downloadedEpisodes);
                $this->logger->info('> [MonitorTvShowHandler] Episode exists for season ' . $episode['season_number'] . ' episode ' . $episode['episode_number'] . ' for title: ' . $monitor->getTitle() . ' ? ' . (true === $episodeExists ? 'YES' : 'NO'));
                if (true === $episodeExists) {
                    $this->logger->info('> [MonitorTvShowHandler] Episode exists for title: ' . $monitor->getTitle() . ', skipping');
                    continue;
                }

                // Check for existing monitors
                $monitorExists = $this->monitorExists($monitor, $episode);
                $this->logger->info('> [MonitorTvShowHandler] Monitor exists for season ' . $episode['season_number'] . ' episode ' . $episode['episode_number'] . ' for title: ' . $monitor->getTitle() . ' ? ' . (true === $monitorExists ? 'YES' : 'NO'));
                if (true === $monitorExists) {
                    $this->logger->info('> [MonitorTvShowHandler] Monitor exists for title: ' . $monitor->getTitle() . ', skipping');
                    continue;
                }

                // Create the monitor
                $episodeMonitor = (new Monitor())
                    ->setParent($monitor)
                    ->setUser($monitor->getUser())
                    ->setTmdbId($monitor->getTmdbId())
                    ->setImdbId($monitor->getImdbId())
                    ->setTitle($monitor->getTitle())
                    ->setMonitorType('tvepisode')
                    ->setSeason($episode['season_number'])
                    ->setEpisode($episode['episode_number'])
                    ->setCreatedAt(new DateTimeImmutable())
                    ->setSearchCount(0)
                    ->setStatus('New');

                $this->monitorRepository->getEntityManager()->persist($episodeMonitor);
                $this->monitorRepository->getEntityManager()->flush();

                $episodeMonitors[] = $episodeMonitor;

                // Immediately run the monitor
                $command = new MonitorTvEpisodeCommand($episodeMonitor->getId());
                $this->monitorTvEpisodeHandler->handle($command);
                $this->logger->info('> [MonitorTvShowHandler] Dispatching MonitorTvEpisodeCommand for season ' . $episodeMonitor->getSeason() . ' episode ' . $episodeMonitor->getEpisode() . ' for title: ' . $monitor->getTitle());
            }
        }

        // Set the status to Active, so it will be re-executed.
        $monitor->setStatus('Active');
        $monitor->setLastSearch(new DateTimeImmutable());
        $monitor->setSearchCount($monitor->getSearchCount() + 1);
        $this->entityManager->flush();

        return new MonitorTvShowResult(
            status: 'OK',
            result: [
                'monitor' => $monitor,
                'new_monitors' => $episodeMonitors,
            ]
        );
    }

    private function episodeReleasedAfterMonitorCreated(string|DateTimeImmutable $monitorStartDate, array $episodeInShow): bool
    {
        $monitorStartDate = Carbon::parse($monitorStartDate);
        $episodeAirDate = Carbon::parse($episodeInShow['air_date']);
        return $episodeAirDate >= $monitorStartDate;
    }

    private function episodeExists(array $episodeInShow, Map $downloadedEpisodes): bool
    {
        return $downloadedEpisodes->filter(
            fn (object $episode) => $episode->episode === $episodeInShow['episode_number']
                && $episode->season === $episodeInShow['season_number']
            )->count() > 0;
    }

    private function monitorExists(Monitor $monitor, array $episode): bool
    {
        return $this->monitorRepository->findOneBy([
                'imdbId' => $monitor->getImdbId(),
                'title' => $monitor->getTitle(),
                'monitorType' => 'tvepisode',
                'season' => $episode['season_number'],
                'episode' => $episode['episode_number'],
                'status' => ['New', 'Active', 'In Progress']
            ]) !== null;
    }
}
