<?php

namespace App\Library\Action\Handler;

use Aimeos\Map;
use App\Base\Enum\MediaType;
use App\Base\Service\MediaFiles;
use App\Base\Util\PTN;
use App\Library\Action\Command\GetMediaFromLibraryCommand;
use App\Library\Action\Result\GetMediaFromLibraryResult;
use App\Monitor\Framework\Repository\MonitorRepository;
use App\Tmdb\TmdbClient;
use App\Tmdb\TmdbResult;
use OneToMany\RichBundle\Contract\CommandInterface as C;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface as R;
use Psr\Log\LoggerInterface;

/**
 * @implements HandlerInterface<GetMediaFromLibraryCommand,GetMediaFromLibraryResult>
 */
class GetMediaInfoFromLibraryHandler implements HandlerInterface
{
    public function __construct(
        private readonly TmdbClient $tmdb,
        private readonly MediaFiles $mediaFiles,
        private readonly LoggerInterface $logger,
        private readonly MonitorRepository $monitorRepository,
    ) {}

    public function handle(C $command): R
    {
        $result = new GetMediaFromLibraryResult();
        $tmdbResult = $this->fetchTmdbData($command->imdbId, $command->mediaType);

        if (null === $tmdbResult) {
            $this->logger->warning('[GetMediaInfoFromLibraryHandler] TMDb result was not found, this may lead to issues in the rest of the library search', (array) $command);
        }

        $this->setResultExists($tmdbResult->mediaType, $tmdbResult->title, $result);
        if ($result->notExists()) {
            return $result;
        }

        $this->parseFromTmdbResult($tmdbResult, $result);

        if ($command->mediaType === MediaType::TvShow->value) {
            $this->setEpisodes($tmdbResult, $result);
            $this->setSeasons($tmdbResult, $result);
            $this->setMonitors($command->userId, $command->imdbId, $result);
        }

        return $result;
    }

    private function fetchTmdbData(string $imdbId, string $mediaType): ?TmdbResult
    {
        return match($mediaType) {
            MediaType::Movie->value => $this->tmdb->movieDetails($imdbId),
            MediaType::TvShow->value => $this->tmdb->tvShowDetails($imdbId),
            default => null,
        };
    }

    private function setResultExists(string $mediaType, string $title, GetMediaFromLibraryResult $result): void
    {
        $fsResult = match($mediaType) {
            MediaType::Movie->value => $this->mediaFiles->movieExists($title),
            MediaType::TvShow->value => $this->mediaFiles->tvShowExists($title),
            default => false,
        };
        if (false === $fsResult) {
            $result->setExists(false);
        } else {
            $result->setExists(true);
        }
    }

    public function parseFromTmdbResult(TmdbResult $tmdbResult, GetMediaFromLibraryResult $result): void
    {
        $result->setTitle($tmdbResult->title);
        $result->setMediaType($tmdbResult->mediaType);
        $result->setImdbId($tmdbResult->imdbId);
    }

    public function setEpisodes(TmdbResult $tmdbResult, GetMediaFromLibraryResult $result): void
    {
        $existingEpisodeFiles = $this->mediaFiles->tvshowExists($tmdbResult->title);

        $existingEpisodeMap = [];
        foreach ($existingEpisodeFiles as $file) {
            /** @var \SplFileInfo $file */
            $ptn = (object) new PTN()->parse($file->getBasename());

            if (!array_key_exists($ptn->season, $existingEpisodeMap)) {
                $existingEpisodeMap[$ptn->season] = [];
            }

            if (!in_array($ptn->episode, $existingEpisodeMap[$ptn->season])) {
                $existingEpisodeMap[$ptn->season][] = $ptn->episode;
            }
        }

        $existingEpisodes = [];
        $missingEpisodes = [];
        foreach ($tmdbResult->episodes as $season => $episodes) {
            foreach ($episodes as $episode) {
                if (array_key_exists($season, $existingEpisodeMap)) {
                    if (in_array($episode->episodeNumber, $existingEpisodeMap[$season])) {
                        $existingEpisodes[] = $episode;
                    } else {
                        $missingEpisodes[] = $episode;
                    }
                } else {
                    $missingEpisodes[] = $episode;
                }
            }
        }
        $result->setEpisodes($existingEpisodes);
        $result->setMissingEpisodes($missingEpisodes);
    }

    public function setSeasons(TmdbResult $tmdbResult, GetMediaFromLibraryResult $result): void
    {
        $existingEpisodeFiles = $this->mediaFiles->tvshowExists($tmdbResult->title);

        $existingEpisodeMap = [];
        foreach ($existingEpisodeFiles as $file) {
            /** @var \SplFileInfo $file */
            $ptn = (object) new PTN()->parse($file->getBasename());
            $existingEpisodeMap[$ptn->season][] = $ptn->episode;
        }

        $existingFullSeasons = [];
        $existingPartialSeasons = [];
        $missingSeasons = [];
        foreach ($existingEpisodeMap as $season => $episodes) {
            if (count($tmdbResult->episodes[$season]) === count($episodes)) {
                $existingFullSeasons[] = $season;
            } elseif (count($episodes) > 0) {
                $existingPartialSeasons[] = $season;
            }
        }

        $seasons = array_keys($tmdbResult->episodes);
        foreach ($seasons as $season) {
            if (!in_array($season, $existingFullSeasons) && !in_array($season, $existingPartialSeasons)) {
                $missingSeasons[] = $season;
            }
        }

        $result->setSeasons($existingFullSeasons);
        $result->setPartialSeasons($existingPartialSeasons);
        $result->setMissingSeasons($missingSeasons);
    }

    public function setMonitors(int $userId, string $imdbId, GetMediaFromLibraryResult $result)
    {
        $result->setMonitorCount(
            $this->monitorRepository->countUserChildrenByParentId($userId, $imdbId)
        );
        $result->setActiveMonitorCount(
            $this->monitorRepository->countUserActiveChildrenByParentId($userId, $imdbId)
        );
        $result->setCompleteMonitorCount(
            $this->monitorRepository->countUserCompleteChildrenByParentId($userId, $imdbId)
        );
    }
}
