<?php

namespace App\Monitor\Action\Handler;

use Aimeos\Map;
use App\Base\Service\MediaFiles;
use App\Monitor\Action\Command\MonitorTvEpisodeCommand;
use App\Monitor\Action\Command\MonitorTvSeasonCommand;
use App\Monitor\Action\Result\MonitorTvSeasonResult;
use App\Monitor\Framework\Entity\Monitor;
use App\Monitor\Framework\Repository\MonitorRepository;
use App\Tmdb\TmdbClient;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use App\Base\Util\PTN;
use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface;
use Psr\Log\LoggerInterface;

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

    public function handle(CommandInterface $command): ResultInterface
    {
        $this->logger->info('> [MonitorTvSeasonHandler] Executing MonitorTvSeasonHandler');
        $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
            )
            ->rekey(fn($episode) => $episode->episode);
        $this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());

        // Compare against list from TMDB
        $episodesInSeason = Map::from(
            $this->tmdb->tvshowDetails($monitor->getImdbId())->episodes[$monitor->getSeason()]
        )->rekey(fn($episode) => $episode['episode_number']);
        $this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($episodesInSeason) . ' episodes in season ' . $monitor->getSeason() . ' for title: ' . $monitor->getTitle());

        if ($downloadedEpisodes->count() !== $episodesInSeason->count()) {
            // Dispatch Episode commands for each missing Episode
            foreach ($episodesInSeason as $episode) {
                // Check if the episode is already downloaded
                $episodeExists = $this->episodeExists($episode, $downloadedEpisodes);
                $this->logger->info('> [MonitorTvSeasonHandler] 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('> [MonitorTvSeasonHandler] Episode exists for title: ' . $monitor->getTitle() . ', skipping');
                    continue;
                }

                // Check for existing monitors
                $monitorExists = $this->monitorExists($monitor, $episode);
                $this->logger->info('> [MonitorTvSeasonHandler] 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('> [MonitorTvSeasonHandler] Monitor exists for title: ' . $monitor->getTitle() . ', skipping');
                    continue;
                }

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

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

                $command = new MonitorTvEpisodeCommand($episodeMonitor->getId());
                $this->monitorTvEpisodeHandler->handle($command);
                $this->logger->info('> [MonitorTvSeasonHandler] 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 MonitorTvSeasonResult(
            status: 'OK',
            result: [
                'monitor' => $monitor,
            ]
        );
    }

    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;
    }
}
