<?php

namespace App\Services;

use App\Exceptions\AlbumNameConflictException;
use App\Models\Album;
use App\Repositories\AlbumRepository;
use App\Values\Album\AlbumUpdateData;
use App\Values\ImageWritingConfig;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Symfony\Component\Finder\Finder;

class AlbumService
{
    public function __construct(
        private readonly AlbumRepository $albumRepository,
        private readonly ImageStorage $imageStorage,
        private readonly Finder $finder,
    ) {
    }

    public function updateAlbum(Album $album, AlbumUpdateData $dto): Album
    {
        // Ensure that the album name is unique within the artist
        $existingAlbumWithTheSameName = $this->albumRepository->findOneBy([
            'name' => $dto->name,
            'artist_id' => $album->artist_id,
        ]);

        throw_if($existingAlbumWithTheSameName?->isNot($album), AlbumNameConflictException::class);

        $data = $dto->toArray();

        if (is_string($dto->cover)) {
            // A non-empty string means the user is uploading another cover,
            // when an empty string means the user is removing the cover.
            $data['cover'] = rescue_if($dto->cover, fn () => $this->imageStorage->storeImage($dto->cover), '');
        } else {
            // If the cover is null, the user's not changing or removing the cover at all.
            Arr::forget($data, 'cover');
        }

        $album->update($data);

        return $album->refresh();
    }

    public function storeAlbumCover(Album $album, mixed $source): ?string
    {
        $fileName = $this->imageStorage->storeImage($source);
        $album->cover = $fileName;
        $album->save();

        return $fileName;
    }

    public function trySetAlbumCoverFromDirectory(Album $album, string $directory): void
    {
        // As directory scanning can be expensive, we cache and reuse the result.
        Cache::remember(cache_key($directory, 'cover'), now()->addDay(), function () use ($album, $directory): ?string {
            $matches = array_keys(
                iterator_to_array(
                    $this->finder::create()
                        ->depth(0)
                        ->ignoreUnreadableDirs()
                        ->files()
                        ->followLinks()
                        ->name('/(cov|fold)er\.(jpe?g|gif|png|webp|avif)$/i')
                        ->in($directory)
                )
            );

            $cover = $matches[0] ?? null;

            if ($cover && is_image($cover)) {
                $this->storeAlbumCover($album, $cover);
            }

            return $cover;
        });
    }

    public function generateAlbumThumbnail(Album $album): string
    {
        $this->imageStorage->storeImage(
            source: image_storage_path($album->cover),
            config: ImageWritingConfig::make(
                maxWidth: 48,
                blur: 10,
            ),
            path: image_storage_path($album->thumbnail),
        );

        return $album->thumbnail;
    }

    public function getOrCreateAlbumThumbnail(Album $album): ?string
    {
        $thumbnailPath = image_storage_path($album->thumbnail);

        if ($thumbnailPath && !File::exists($thumbnailPath)) {
            $this->generateAlbumThumbnail($album);
        }

        return $album->thumbnail;
    }
}