<?php

namespace App\Download\Action\Handler;

use App\Base\Enum\MediaType;
use App\Base\Service\Broadcaster;
use App\Download\Action\Command\DownloadMediaCommand;
use App\Download\Action\Result\DownloadMediaResult;
use App\Download\DownloadEvents;
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\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 MessageBusInterface $bus,
        private DownloaderInterface $downloader,
        private DownloadRepository  $downloadRepository,
        private UserRepository      $userRepository, private Broadcaster $broadcaster,
    ) {}

    public function handle(CommandInterface $command): ResultInterface
    {
        $user = $this->userRepository->find($command->userId);
        $this->bus->dispatch(new AddEventLogCommand(
            $user,
            DownloadEvents::DOWNLOAD_STARTED->type(),
            DownloadEvents::DOWNLOAD_STARTED->message(),
            (array) $command
        ));

        if (null === $command->downloadId) {
            $download = $this->downloadRepository->insert(
                $user,
                $command->url,
                $command->title,
                $command->filename,
                $command->imdbId,
                $command->mediaType,
                ""
            );
        } else {
            $download = $this->downloadRepository->find($command->downloadId);
        }

        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,
                $command->title,
                $command->url,
                $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.");
    }

    public function validateDownloadUrl(string $downloadUrl)
    {
        $badFileSizes = [
            2119075, // copyright infringement
        ];

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