<?php

namespace App\Base\Service;

use Aimeos\Map;
use App\Download\Framework\Entity\Download;
use App\Base\Util\PTN;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

class MediaFiles
{
    private Finder $finder;

    private string $basePath;

    private string $moviesPath;

    private string $tvShowsPath;

    private Filesystem $filesystem;
    private LoggerInterface $logger;

    public function __construct(
        #[Autowire(param: 'media.base_path')]
        string $basePath,

        #[Autowire(param: 'media.movies_path')]
        string $moviesPath,

        #[Autowire(param: 'media.tvshows_path')]
        string $tvShowsPath,

        Filesystem $filesystem,
        LoggerInterface $logger,
    ) {
        $this->finder = new Finder();
        $this->basePath = $basePath;
        $this->moviesPath = $moviesPath;
        $this->tvShowsPath = $tvShowsPath;
        $this->filesystem = $filesystem;
        $this->logger = $logger;
    }

    public function getPathByType(string $mediaType): string
    {
        if ('movies' === $mediaType) {
            return $this->moviesPath;
        } elseif ('tvshows' === $mediaType) {
            return $this->tvShowsPath;
        }

        throw new \Exception(sprintf('A path for media type %s does not exist.', $mediaType));
    }

    public function getBasePath(): string
    {
        return $this->basePath;
    }

    public function getMoviesPath(): string
    {
        return $this->moviesPath;
    }

    public function getTvShowsPath(): string
    {
        return $this->tvShowsPath;
    }

    public function getMovieDirs(): Map
    {
        $results = [];
        foreach ($this->finder->in($this->moviesPath)->directories() as $file) {
            $results[] = $file;
        }

        return Map::from($results);
    }

    public function getTvShowDirs(): Map
    {
        $results = [];
        foreach ($this->finder->in($this->tvShowsPath)->directories() as $file) {
            $results[] = $file;
        }

        return Map::from($results);
    }

    public function getEpisodes(string $path, bool $onlyFilenames = true): Map
    {
        if (!str_starts_with($path, $this->tvShowsPath)) {
            $path = $this->tvShowsPath . DIRECTORY_SEPARATOR . $path;
        }

        if (false === $this->filesystem->exists($path)) {
            $this->filesystem->mkdir($path);
        }

        $results = [];
        foreach ($this->finder->in($path)->files() as $file) {
            if ($onlyFilenames) {
                $results[] = $file->getRelativePathname();
            } else {
                $results[] = $file;
            }
        }

        return Map::from($results);
    }

    public function createMovieDirectory(string $title, string|int $year, string $imdbId): string
    {
        $path = sprintf(
            '%s' . DIRECTORY_SEPARATOR . '%s (%s) [imdbid-%s]',
            $this->moviesPath,
            $title,
            $year,
            $imdbId
        );

        if (false === $this->filesystem->exists($path)) {
            $this->filesystem->mkdir($path);
        }

        return $path;
    }

    public function createTvShowDirectory(string $title, string|int $year, string|int $season, string|int $episode, string $imdbId): string
    {
        $path = sprintf(
            '%s' . DIRECTORY_SEPARATOR . '%s (%s) [imdbid-%s]' . DIRECTORY_SEPARATOR . 'Season %s',
            $this->tvShowsPath,
            $title,
            $year,
            $imdbId,
            str_pad($season, 2, '0', STR_PAD_LEFT),
        );

        if (false === $this->filesystem->exists($path)) {
            $this->filesystem->mkdir($path);
        }

        return $path;
    }

    public function createDirectory(string $path): string
    {
        if (false === $this->filesystem->exists($path)) {
            $this->filesystem->mkdir($path);
        }

        return $path;
    }

    public function episodeExists(string $tvshowTitle, int $seasonNumber, int $episodeNumber): SplFileInfo|false
    {
        $existingEpisodes = $this->getEpisodes($tvshowTitle, false);

        if ($existingEpisodes->isEmpty()) {
            return false;
        }

        /** @var SplFileInfo $episode */
        foreach ($existingEpisodes as $episode) {
            $ptn = (object) (new PTN())->parse($episode->getFilename());

            if (!property_exists($ptn, 'season') || !property_exists($ptn, 'episode')) {
                continue;
            }

            if ($ptn->season === $seasonNumber && $ptn->episode === $episodeNumber) {
                return $episode;
            }
        }

        return false;
    }

    /**
     * @param string $tvshowTitle
     * @return array<SplFileInfo>|false
     */
    public function tvshowExists(string $tvshowTitle): Map|false
    {
        $existingEpisodes = $this->getEpisodes($tvshowTitle, false);

        if ($existingEpisodes->isEmpty()) {
            return false;
        }

        $episodes = new Map;
        /** @var SplFileInfo $episode */
        foreach ($existingEpisodes as $episode) {
            $ptn = (object) (new PTN())->parse($episode->getFilename());

            if (!property_exists($ptn, 'season') || !property_exists($ptn, 'episode')) {
                continue;
            }

            $episodes->push($episode);
        }

        if ($episodes->count() > 0) {
            return $episodes;
        }

        return false;
    }

    public function movieExists(string $title)
    {
        $filepath = $this->moviesPath . DIRECTORY_SEPARATOR . $title;
        $directoryExists = $this->filesystem->exists($filepath);

        if (false === $directoryExists) {
            return false;
        }

        if (false === $this->finder->in($filepath)->files()->hasResults()) {
            return false;
        }

        $files = Map::from($this->finder->in($filepath)->files())->filter(function (SplFileInfo $file) {
            $validExtensions = ['mkv', 'mp4', 'mpeg'];
            return in_array($file->getExtension(), $validExtensions);
        })->values();

        if (false === $files->isEmpty()) {
            return $files[0];
        }

        return false;
    }

    public function getDownloadPath(Download $download): string
    {
        $basePath = $this->getBasePath();

        if ($download->getMediaType() === 'movies') {
            $basePath = $this->getMoviesPath();
            if ($download->getUser()->hasUserPreference('movie_folder')) {
                $basePath .= DIRECTORY_SEPARATOR . $download->getTitle();
            }
        } elseif ($download->getMediaType() === 'tvshows') {
            $basePath = $this->getTvShowsPath() . DIRECTORY_SEPARATOR . $download->getTitle();
        }

        $filepath = $basePath . DIRECTORY_SEPARATOR . $download->getFilename();

        if (false === $this->filesystem->exists($filepath)) {
            throw new \Exception(sprintf('File %s does not exist.', $filepath));
        }

        return $filepath;
    }

    public function renameFile(string $oldFile, string $newFile)
    {
        $this->filesystem->rename($oldFile, $newFile);
    }

    public function setFilePermissions(string $filepath, int $permissions)
    {
        $this->filesystem->chmod($filepath, $permissions);
    }

    /**
     * @param string $filepath
     * @return bool
     * Returns true if file was deleted
     * Returns false is file not found or was not deleted
     */
    public function removeFile(string $filepath): bool
    {
        if (true === $this->filesystem->exists($filepath)) {
            try {
                $this->filesystem->remove($filepath);
                return true;
            } catch (\Throwable $exception) {
                $this->logger->error($exception->getMessage(), ['file' => $filepath]);
                return false;
            }
        }

        $this->logger->warning('> [MediaFiles] Attempted to remove file, but it did not exist.', ['file' => $filepath]);
        return false;
    }
}
