<?php

namespace App\Monitor\Action\Handler;

use App\Base\Util\EpisodeId;
use App\Download\Action\Command\DownloadMediaCommand;
use App\Download\DownloadOptionEvaluator;
use App\Download\Framework\Repository\DownloadRepository;
use App\EventLog\Action\Command\AddEventLogCommand;
use App\Monitor\Action\Command\MonitorMovieCommand;
use App\Monitor\Action\Result\MonitorTvEpisodeResult;
use App\Monitor\Framework\Entity\Monitor;
use App\Monitor\Framework\Repository\MonitorRepository;
use App\Monitor\MonitorEvents;
use App\Tmdb\TmdbClient;
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
use App\User\Dto\UserPreferencesFactory;
use Carbon\Carbon;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\MessageBusInterface;

/** @implements HandlerInterface<MonitorMovieCommand> */
readonly class MonitorTvEpisodeHandler implements HandlerInterface
{
    public function __construct(
        private GetTvShowOptionsHandler $getTvShowOptionsHandler,
        private DownloadOptionEvaluator $downloadOptionEvaluator,
        private EntityManagerInterface $entityManager,
        private MessageBusInterface $bus,
        private LoggerInterface $logger,
        private MonitorRepository $monitorRepository,
        private TmdbClient $tmdb,
        private DownloadRepository $downloadRepository,
    ) {}

    public function handle(CommandInterface $command): ResultInterface
    {
        try {
            $monitor = $this->monitorRepository->find($command->movieMonitorId);
            $this->logger->info('> [MonitorTvEpisodeHandler] Executing MonitorTvEpisodeHandler for ' . $monitor->getTitle() . ' season ' . $monitor->getSeason() . ' episode ' . $monitor->getEpisode());
            $this->refreshData($monitor);

            $this->bus->dispatch(new AddEventLogCommand(
                $monitor->getUser(),
                MonitorEvents::MONITOR_STARTED->type(),
                MonitorEvents::MONITOR_STARTED->message(),
                (array) $command
            ));

            $episodeData = $this->tmdb->tvEpisodeDetails($monitor->getTmdbId(), $monitor->getImdbId(), $monitor->getSeason(), $monitor->getEpisode());

            if (null === $episodeData->airDate || "" === $episodeData->airDate) {
                $this->logger->info('> [MonitorTvEpisodeHandler] ...Episode does not have an air date, skipping for now');
                return new MonitorTvEpisodeResult(
                    status: 'OK',
                    result: [
                        'message' => 'No change',
                        'monitor' => $monitor,
                    ]
                );
            }

            if (null === $monitor->getAirDate()) {
                $monitor->setAirDate(Carbon::parse($episodeData->airDate));
            }

            if (Carbon::createFromTimestamp($episodeData->airDate) > Carbon::today('UTC')) {
                $this->logger->info('> [MonitorTvEpisodeHandler] ...Episode has not aired yet, skipping for now');
                return new MonitorTvEpisodeResult(
                    status: 'OK',
                    result: [
                        'message' => 'No change',
                        'monitor' => $monitor,
                    ]
                );
            }

            $monitor->setStatus('In Progress');
            $this->monitorRepository->getEntityManager()->flush();

            $results = $this->getTvShowOptionsHandler->handle(
                new GetTvShowOptionsCommand(
                    $monitor->getTmdbId(),
                    $monitor->getImdbId(),
                    $monitor->getSeason(),
                    $monitor->getEpisode()
                )
            );

            $this->logger->info('> [MonitorTvEpisodeHandler] ...Found ' . count($results->results) . ' total download options, beginning evaluation');

            $result = $this->downloadOptionEvaluator->evaluateOptions($results->results, UserPreferencesFactory::createFromUser($monitor->getUser()));

            if (null !== $result) {
                $this->logger->info('> [MonitorTvEpisodeHandler] ...Found 1 matching result found: dispatching DownloadMediaCommand for "' . $result->title . '"');
                $download = $this->downloadRepository->insert(
                    user: $monitor->getUser(),
                    url: $result->url,
                    title: $monitor->getTitle(),
                    filename: $result->filename,
                    imdbId: $monitor->getImdbId(),
                    mediaType: 'tvshows',
                    episodeId: EpisodeId::fromSeasonEpisodeNumbers($monitor->getSeason(), $monitor->getEpisode()),
                );
                $this->bus->dispatch(new DownloadMediaCommand(
                    $download->getUrl(),
                    $download->getTitle(),
                    $download->getFilename(),
                    'tvshows',
                    $download->getImdbId(),
                    $monitor->getUser()->getId(),
                    $download->getId(),
                ));
                $monitor->setStatus('Complete');
                $monitor->setDownloadedAt(new DateTimeImmutable());
            } else {
                $this->logger->info('> [MonitorTvEpisodeHandler] ...Found 0 matching results found, monitor will run at next interval');
                $monitor->setStatus('Active');
            }
        } catch (\Throwable $exception) {
            $this->logger->error('> [MonitorTvEpisodeHandler] ...Exception thrown: ' . $exception->getMessage());
            $this->logger->error($exception->getMessage());
            $monitor->setStatus('Active');
            $this->bus->dispatch(new AddEventLogCommand(
                $monitor->getUser(),
                MonitorEvents::MONITOR_ERROR->type(),
                MonitorEvents::MONITOR_ERROR->message() . ': ' . $exception->getMessage(),
                (array) $monitor
            ));
        }

        $monitor->setLastSearch(new DateTimeImmutable());
        $monitor->setSearchCount($monitor->getSearchCount() + 1);
        $this->monitorRepository->getEntityManager()->flush();

        $this->bus->dispatch(new AddEventLogCommand(
            $monitor->getUser(),
            MonitorEvents::MONITOR_FINISHED->type(),
            MonitorEvents::MONITOR_FINISHED->message(),
            (array) $monitor
        ));

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

    private function refreshData(Monitor $monitor)
    {
        if (null === $monitor->getPoster()) {
            $this->logger->info('> [MonitorTvEpisodeHandler] Refreshing poster for "' . $monitor->getTitle() . '"');
            $poster = $monitor->getParent()->getPoster();
            if (null !== $poster && "" !== $poster) {
                $monitor->setPoster($poster);
            }
        }
    }
}
