<?php

namespace App\Services\Image;

use App\Models\Image;
use App\Models\ImageSetting;
use App\Models\ThumbnailSetting;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;

// TODO: Usar enums para Format y ThumbSize, mucho más conveniente.

/**
 * @phpstan-type Format 'jpg'|'pjpg'|'png'|'gif'|'webp'|'avif'|'tiff'
 * @phpstan-type ThumbSize 'small'|'medium'|'large'
 */
class ImageService
{
    protected ?Image $imageModel = null;
    protected ?string $name = null;
    protected string $ext = 'webp';
    protected ?string $key = null;
    protected ?string $modelType = null;
    protected ?Model $realatedModel = null;
    protected ?ImageSetting $imageSettings = null;
    /** @var ?EloquentCollection<ThumbnailSetting> */
    protected $thumbnailSettings = null;

    public function setImageModel(Image $model)
    {
        $this->imageModel = $model;

        $this->setName($model->name);
        $this->setExtension($model->ext);
        $this->setModelType($model->imageable_type);

        return $this;
    }

    public function setName(string $name)
    {
        $this->name = $name;

        return $this;
    }

    public function setKey(string $key = null)
    {
        $this->key = $key;
        return $this;
    }

    public function getKey(): ?string
    {
        return $this->key;
    }

    public function getName()
    {
        return $this->name;
    }

    /**
     * @param ThumbSize $thumbSize
     */
    public function getFilename(string $thumbSize = '')
    {
        $sizeFragment = !empty($thumbSize) ? ("@" . $thumbSize) : "";

        return "{$this->name}{$sizeFragment}.{$this->ext}";
    }

    /**
     * @param Format $ext
     */
    public function setExtension(string $ext)
    {
        $this->ext = $ext;

        return $this;
    }

    public function getExtension()
    {
        return $this->ext;
    }

    public function setModelType(string $modelType)
    {
        $this->modelType = $modelType;

        return $this;
    }

    public function getModelType()
    {
        return $this->modelType;
    }

    public function setRelatedModel(Model $model)
    {
        $this->realatedModel = $model;

        $this->setModelType(\get_class($model));

        return $this;
    }

    public function getRelatedModel()
    {
        return $this->realatedModel;
    }

    public function store(): Image
    {
        $image = new Image;
        $image->imageable_type = $this->modelType;
        $image->imageable_id = $this->realatedModel->getKey();
        $image->key = $this->key;
        $image->name = $this->name;
        $image->ext = $this->ext;

        $image->save();

        return $image;
    }

    /**
     * @return array{0: Image, 1: Image}
     */
    public function replaceFirst()
    {
        $oldImage = Image::query()
            ->where([
                'imageable_type' => $this->modelType,
                'imageable_id' => $this->realatedModel->getKey(),
                'key' => $this->key
            ])
            ->first();

        if (!\is_null($oldImage)) {
            $oldImage->delete();
        }

        $newImage = $this->store();

        return [$newImage, $oldImage];
    }

    /**
     * @throws \Exception
     * @throws ModelNotFoundException
     */
    public function getImageSettings(): ImageSetting
    {
        if (!\is_null($this->imageSettings)) {
            return $this->imageSettings;
        }

        if (\is_null($this->modelType)) {
            throw new \Exception("modelType has not been set yet");
        }
        Log::info($this->key);
        $settings = $this->imageSettings = ImageSetting::query()
            ->where('type', $this->modelType)
            ->where('key', $this->key)
            ->first();

        Log::info(json_encode($settings));

        return $settings ?? throw new ModelNotFoundException(
            "No image settings for model type [{$this->modelType}]."
        );
    }

    public function getThumbnailSettings(String $key = null)
    {
        $this->setKey($key);
        $imageSettings = $this->getImageSettings();

        if (\is_null($this->thumbnailSettings)) {
            $this->thumbnailSettings = ThumbnailSetting::query()
                ->where('image_settings_id', $imageSettings->id)

                ->where('default_functions', 1)
                ->get();
        }

        return $this->thumbnailSettings;
    }

    /**
     * @param ThumbSize $thumbSize
     */
    public function getThumbnailSettingsBySize(string $size): ?ThumbnailSetting
    {
        $thumbnailSettings = $this->getThumbnailSettings();

        return $thumbnailSettings->firstWhere('size', '=', $size);
    }

    public function getSmallThumbnailSettings()
    {
        return $this->getThumbnailSettingsBySize('small');
    }

    public function getMediumThumbnailSettings()
    {
        return $this->getThumbnailSettingsBySize('medium');
    }

    public function getLargeThumbnailSettings()
    {
        return $this->getThumbnailSettingsBySize('large');
    }

    /**
     * @param ThumbSize $size
     */
    public function getThumbnail(string $size, String $key = null): Thumbnail
    {
        $name = $this->getFilename($size);
        $url = $this->getUrl($size);
        $settingsList = $this->getThumbnailSettings($key);
        /** @var ThumbnailSetting|null */
        $settings = Collection::make($settingsList)
            ->first(
                fn (ThumbnailSetting $settings) => $settings->size === $size
            );

        return new Thumbnail(
            $name,
            $url,
            $size,
            $settings?->width ?? 0,
            $settings?->height ?? 0,
        );
    }

    /**
     * @param ThumbSize $thumbSize
     */
    public function getUrl(string $size = '')
    {
        return URL::asset(
            Storage::url(
                $this->getRelativePath($size),
            )
        );
    }

    /**
     * @param ThumbSize $thumbSize
     */
    public function getRelativePath(string $thumbSize = '')
    {
        $settings = $this->getImageSettings();
        $uploadTo = $settings->upload_to;
        $filename = $this->getFilename($thumbSize);

        return "{$uploadTo}/{$filename}";
    }
}
