<?php

namespace App\Download\Downloader;

use App\Base\Service\MediaFiles;
use App\Download\Framework\Entity\Download;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Contracts\Cache\CacheInterface;

class ProcessDownloader implements DownloaderInterface
{
    /**
     * @var RedisAdapter $cache
     */
    public function __construct(
        private EntityManagerInterface $entityManager,
        private MediaFiles $mediaFiles,
        private CacheInterface $cache,
    ) {}

    /**
     * @inheritDoc
     */
    public function download(string $mediaType, string $title, string $url, ?int $downloadId): void
    {
        /** @var Download $downloadEntity */
        $downloadEntity = $this->entityManager->getRepository(Download::class)->find($downloadId);
        $this->entityManager->flush();

        $downloadPreferences = $downloadEntity->getUser()->getDownloadPreferences();
        $path = $this->getDownloadPath($mediaType, $title, $downloadPreferences);

        $processArgs = ['wget', '-O', $downloadEntity->getFilename(), $url];

        if ($downloadEntity->getStatus() === 'Paused') {
            $downloadEntity->setStatus('In Progress');
            $processArgs = ['wget', '-c', '-O', $downloadEntity->getFilename(), $url];
        } else {
            $downloadEntity->setProgress(0);
        }

        fwrite(STDOUT, implode(" ", $processArgs));

        $process = (new Process($processArgs))->setWorkingDirectory($path);

        $process->setTimeout(1800); // 30 min
        $process->setIdleTimeout(600); // 10 min

        $process->start();

        try {
            $progress = 0;
            $this->entityManager->flush();

            $process->wait(function ($type, $buffer) use ($progress, $downloadEntity, $process): void {
                // The PauseDownloadHandler will set this to 'true'
                $doPause = $this->cache->getItem('download.pause.' . $downloadEntity->getId());

                if (true === $doPause->isHit() && true === $doPause->get()) {
                    $downloadEntity->setStatus('Paused');
                    $this->cache->deleteItem('download.pause.' . $downloadEntity->getId());
                    $this->entityManager->flush();
                    $process->stop();
                }

                if (Process::ERR === $type) {
                    $pregMatchOutput = [];
                    preg_match('/[\d]+%/', $buffer, $pregMatchOutput);

                    if (!empty($pregMatchOutput)) {
                        if ($pregMatchOutput[0] !== $progress) {
                            $progress = (int) $pregMatchOutput[0];
                            $downloadEntity->setProgress($progress);
                            $this->entityManager->flush();
                        }
                    }
                }
                fwrite(STDOUT, $buffer);
            });
            if ($downloadEntity->getStatus() !== 'Paused') {
                $downloadEntity->setProgress(100);
            }
        } catch (ProcessFailedException $exception) {
            $downloadEntity->setStatus('Failed');
        }

        $this->entityManager->flush();
    }

    public function getDownloadPath(string $mediaType, string $title, array $downloadPreferences): string
    {
        if ($mediaType === 'movies') {
            if ((bool) $downloadPreferences['movie_folder']->getPreferenceValue() === true) {
                return $this->mediaFiles->createMovieDirectory($title);
            }
            return $this->mediaFiles->getMoviesPath();
        }

        if ($mediaType === 'tvshows') {
            return $this->mediaFiles->createTvShowDirectory($title);
        }

        throw new \Exception("There is no download path for media type: $mediaType");
    }
}