<?php

namespace App\Modules\Media\Infrastructure;

use App\Modules\Media\Domain\Media;
use App\Modules\Media\Domain\MediaData;
use App\Modules\Media\Domain\MediaHasLarge;
use App\Modules\Media\Domain\MediaPath;
use App\Modules\Media\Domain\MediaRepository;
use App\Modules\Media\Domain\MediaSaveConfiguration;
use App\Modules\Media\Domain\MediaType;
use App\Modules\Media\Domain\ThumbnailCreator;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;

class MemoryMediaRepository implements MediaRepository
{

    private const THUMBNAIL_EXTENSION = 'jpg';

    public function __construct(
        private LaravelFileReceptor $fileReceptor
    ) {}

    public function save(
        Media $media,
        MediaSaveConfiguration $config,
        ThumbnailCreator $thumbnailCreator,
        MediaType $mediaType,
        $directoryName = null,
    ) {
        $inputName = $media->inputName()->value();
        $store = $media->inputStore()->value();
        if (!$this->fileReceptor->hasFile($inputName)) {
            return null;
        }

        $thumbUrl = '';
        $imageUrl = '';
        $tempUrl = '';
        try {
            $path = config('var.PATH_PRIVATE');
            $imageDir = $this->createDirIfNotExists('image' . DIRECTORY_SEPARATOR . $store . DIRECTORY_SEPARATOR . 'large', 'img', $directoryName);
            $thumbDir = $this->createDirIfNotExists('image' . DIRECTORY_SEPARATOR . $store . DIRECTORY_SEPARATOR . 'thumb', 'img');
            $ext = $this->fileReceptor->getExtension($inputName);

            $tempUrl = $this->moveToTemp($inputName, $ext);

            $thumbUrl = $this->generateThumb($path . $thumbDir, $ext, $config, $tempUrl, $thumbnailCreator);
            if ($media->hasLarge()->value() === MediaHasLarge::VALUE_TRUE) {
                $largeThumbUrl = $this->generateLargeThumb($path . $thumbDir, $ext, $config, $tempUrl, $thumbnailCreator);
            } else {
                $largeThumbUrl = '';
            }

            $imageUrl = $this->generateFile($path . $imageDir, $ext, $tempUrl);
            unlink($tempUrl);
            $tempUrl = '';
            return new MediaData(
                $thumbUrl ? $thumbDir . DIRECTORY_SEPARATOR . $thumbUrl : '',
                $largeThumbUrl ? $thumbDir . DIRECTORY_SEPARATOR . $largeThumbUrl : '',
                $imageDir . DIRECTORY_SEPARATOR . $imageUrl,
                $ext,
                filesize($path . $imageDir . DIRECTORY_SEPARATOR . $imageUrl),
                $mediaType->value()
            );
        } catch (Throwable $ex) {
            if ($tempUrl) {
                unlink($tempUrl);
            }
            if ($thumbUrl) {
                unlink($path . $thumbDir . DIRECTORY_SEPARATOR . $thumbUrl);
            }
            if ($imageUrl) {
                unlink($path . $imageDir . DIRECTORY_SEPARATOR . $imageUrl);
            }
            throw $ex;
        }
    }

    private function generateThumb($thumbDir, $ext, MediaSaveConfiguration $config, $temp, ThumbnailCreator $thumbnailCreator)
    {
        $imageData = $thumbnailCreator->create($temp, $config->getWidth(), $config->getHeight(), self::THUMBNAIL_EXTENSION);
        if ($imageData === '') {
            return '';
        }
        list($thumbUrl, $thumbFile) = $this->generateFilePointer($thumbDir, self::THUMBNAIL_EXTENSION);
        fwrite($thumbFile, $imageData);
        fclose($thumbFile);
        return $thumbUrl;
    }

    private function generateLargeThumb($thumbDir, $ext, MediaSaveConfiguration $config, $temp, ThumbnailCreator $thumbnailCreator)
    {
        $imageData = $thumbnailCreator->createScale(
            $temp,
            $config->getLargeWidth(),
            $config->getLargeHeight(),
            self::THUMBNAIL_EXTENSION
        );
        if ($imageData === '') {
            return '';
        }
        list($thumbUrl, $thumbFile) = $this->generateFilePointer($thumbDir, self::THUMBNAIL_EXTENSION);
        fwrite($thumbFile, $imageData);
        fclose($thumbFile);
        return $thumbUrl;
    }

    private function generateFile($imageDir, $ext, $temp, $directoryName = null): string
    {
        list($imageUrl, $imageFile) = $this->generateFilePointer($imageDir, $ext, $directoryName);
        $origin = fopen($temp, 'r');
        stream_copy_to_stream($origin, $imageFile);
        fclose($imageFile);
        fclose($origin);
        return $imageUrl;
    }

    private function generateFilePointer($dir, $ext, $directoryName = null)
    {
        $flag = 0;
        $attempts = 10;
        do {
            $generated = date('His') . uniqid() . '.' . $ext;
            $generatedUrl = $dir . DIRECTORY_SEPARATOR . $generated;
            $flag++;
            $file = false;
            if ($flag < $attempts) {
                try {
                    $file = fopen($generatedUrl, 'x');
                } catch (\Throwable $th) {
                }
            }
        } while ($file === false && $flag < $attempts);
        if ($flag >= $attempts) {
            throw new HttpException(406, 'Error generating file.');
        }
        return [$generated, $file];
    }

    private function moveToTemp($inputName, $ext): string
    {
        $tempName = uniqid() . '.' . $ext;
        $path = config('var.PATH_PRIVATE') . 'temp' . DIRECTORY_SEPARATOR;
        if (!$this->fileReceptor->move($inputName, $path, $tempName)) {
            throw new HttpException(406, 'Error uploading file');
        }
        return $path . $tempName;
    }

    private function createDirIfNotExists($path, $prefix, $directoryName = null): string
    {
        $filesPath = config('var.PATH_PRIVATE');
        $directory = $path . DIRECTORY_SEPARATOR . ($directoryName ?? $prefix . date('ym'));
        $dirPath = $filesPath . $directory;

        if (!is_dir($dirPath)) {
            @mkdir($dirPath, 0700);
        }
        return $directory;
    }

    public function delete(MediaPath $path): void
    {
        unlink(config('var.PATH_PRIVATE') . $path->value());
    }
}
