<?php

namespace App\Download\Action\Handler;

use App\Base\Enum\MediaType;
use App\Base\Service\Broadcaster;
use App\Base\Service\MediaFiles;
use App\Base\Util\EpisodeId;
use App\Download\Action\Command\DownloadMediaCommand;
use App\Download\Action\Result\DownloadMediaResult;
use App\Download\DownloadEvents;
use App\Download\DownloadOptionEvaluator;
use App\Download\Framework\Entity\Download;
use App\Download\Framework\Repository\DownloadRepository;
use App\Download\Downloader\DownloaderInterface;
use App\EventLog\Action\Command\AddEventLogCommand;
use App\Library\Dto\MediaFileDto;
use App\Search\Action\Command\GetMediaInfoCommand;
use App\Search\Action\Handler\GetMediaInfoHandler;
use App\Search\Action\Result\GetMediaInfoResult;
use App\Torrentio\Action\Command\GetMovieOptionsCommand;
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
use App\Torrentio\Result\TorrentioResult;
use App\User\Dto\UserPreferencesFactory;
use App\User\Framework\Repository\UserRepository;
use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\MessageBusInterface;


/** @implements HandlerInterface<DownloadMediaCommand, DownloadMediaResult> */
readonly class DownloadMediaHandler implements HandlerInterface
{
    public function __construct(
        private DownloadOptionEvaluator $downloadOptionEvaluator,
        private GetTvShowOptionsHandler $getTvShowOptionsHandler,
        private GetMovieOptionsHandler  $getMovieOptionsHandler,
        private GetMediaInfoHandler     $getMediaInfoHandler,
        private MessageBusInterface     $bus,
        private DownloaderInterface     $downloader,
        private DownloadRepository      $downloadRepository,
        private UserRepository          $userRepository,
        private Broadcaster             $broadcaster,
        private MediaFiles              $mediaFiles,
    ) {}

    public function handle(CommandInterface $command): ResultInterface
    {
        $user = $this->userRepository->find($command->userId);
        /** @var \App\Download\Framework\Entity\Download $download */
        $download = $this->downloadRepository->find($command->downloadId);
        $media = $this->getMediaInfoHandler->handle(new GetMediaInfoCommand(
            $command->imdbId,
            $command->mediaType,
            $command->season,
            $command->episode,
        ));
        $downloadOptions = match ($command->mediaType) {
            MediaType::Movie->value => $this->getMovieOptionsHandler->handle(
                new GetMovieOptionsCommand(
                    $media->media->tmdbId,
                    $media->media->imdbId
                )
            ),
            MediaType::TvShow->value => $this->getTvShowOptionsHandler->handle(
                new GetTvShowOptionsCommand(
                    $media->media->tmdbId,
                    $media->media->imdbId,
                    $command->season,
                    $command->episode
                )
            )
        };

        $filter = $command->filter !== null
            ? UserPreferencesFactory::createFromArray($command->filter)
            : UserPreferencesFactory::createFromUser($user);

        $matchingOption = $this->downloadOptionEvaluator->evaluateOptions($downloadOptions->results, $filter);

        if ($matchingOption === null) {
            $download->setProgress(100);
            $download->setStatus('Failed');
            $this->downloadRepository->getEntityManager()->flush();
            $this->broadcaster->alert(
                title:'Uh oh',
                message: 'No matching download options found.',
                type: 'warning',
                mercureAlertTopic: $command->mercureAlertTopic
            );
            throw new UnrecoverableMessageHandlingException('No matching download options found.', 404);
        }

        $download->setUrl($matchingOption->url);
        $download->setTitle($media->media->title);
        $download->setFileName(
            $this->getFilename(MediaType::from($command->mediaType), $media, $matchingOption)
        );

        $this->bus->dispatch(new AddEventLogCommand(
            $user,
            DownloadEvents::DOWNLOAD_STARTED->type(),
            DownloadEvents::DOWNLOAD_STARTED->message(),
            (array) $command
        ));

        try {
            $this->validateDownloadUrl($download->getUrl());
        } catch (\Throwable $exception) {
            $download->setProgress(100);
            $download->setStatus('Failed');
            $this->downloadRepository->getEntityManager()->flush();
            $this->sendFailedDownloadAlert($download, $command->mercureAlertTopic, $exception->getMessage());
            return new DownloadMediaResult(400, $exception->getMessage());
        }

        try {
            if ($download->getStatus() !== 'Paused') {
                $this->downloadRepository->updateStatus($download->getId(), 'In Progress');
            }

            $this->downloader->download(
                $command->mediaType,
                $download->getUrl(),
                $this->getFilepath(MediaType::from($command->mediaType), $media, $matchingOption),
                $download->getId()
            );

            if ($download->getStatus() !== 'Paused') {
                $this->downloadRepository->updateStatus($download->getId(), 'Complete');
            }
        } catch (\Throwable $exception) {
            throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
        }

        $this->bus->dispatch(new AddEventLogCommand(
            $user,
            DownloadEvents::DOWNLOAD_FINISHED->type(),
            DownloadEvents::DOWNLOAD_FINISHED->message(),
            (array) $command
        ));
        return new DownloadMediaResult(200, "Success.");
    }

    private function getFilepath(MediaType $mediaType, GetMediaInfoResult $media, TorrentioResult $option): ?string
    {
        return match ($mediaType) {
            MediaType::Movie => $this->mediaFiles->createMovieDirectory(
                $media->media->title,
                $media->media->year,
                $media->media->imdbId,
            ),
            MediaType::TvShow => $this->mediaFiles->createTvShowDirectory(
                $media->media->title,
                $media->media->year,
                $media->season,
                $media->episode,
                $media->media->imdbId,
            )
        };
    }

    private function getFilename(MediaType $mediaType, GetMediaInfoResult $media, TorrentioResult $option): ?string
    {
        $fileType = $option->ptn->container;
        return (match ($mediaType) {
            MediaType::Movie => function () use ($media, $fileType, $option) {
                $template = "%s (%s) [imdbid-%s]";
                $filename = sprintf(
                    $template,
                    $media->media->title,
                    $media->media->year,
                    $media->media->imdbId,
                    $fileType
                );
                if ($option->resolution !== null && $option->resolution !== '-') {
                    $filename .= ' - [' . $option->resolution . ']';
                }
                return $filename . '.' . $fileType;
            },
            MediaType::TvShow => function () use ($media, $fileType, $option) {
                $template = "%s %s.%s";
                $episodeId = EpisodeId::fromSeasonEpisodeNumbers(
                    $media->season,
                    $media->episode,
                );
                $filename = sprintf(
                    $template,
                    $media->media->title,
                    $episodeId,
                    $fileType
                );
                if ($option->resolution !== null && $option->resolution !== '-') {
                    $filename .= ' - [' . $option->resolution . ']';
                }
                return $filename . '.' . $fileType;
            }
        })();
    }

    public function validateDownloadUrl(string $downloadUrl)
    {
        $badFileLocations = [
            'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
            'https://torrentio.strem.fun/videos/downloading_v2.mp4' => 'Your torrent is downloading to your debrid provider.'
        ];

        $headers = get_headers($downloadUrl, true);
        if (array_key_exists($headers['Location'], $badFileLocations)) {
            throw new \Exception($badFileLocations[$headers['Location']]);
        }
    }

    private function sendFailedDownloadAlert(Download $download, string $mercureAlertTopic, ?string $message = null): void
    {
        $this->broadcaster->alert(
            title: 'Download Failed',
            message: $message ?? "{$download->getTitle()} failed to download.",
            type: 'warning',
            mercureAlertTopic: $mercureAlertTopic
        );
    }
}
