<?php

namespace App\Services\Concerns;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\Conversions\ConversionCollection;
use Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

trait RegistersMedia
{
    private function registerMediaFromBase64(
        Model $model,
        string $base64Data,
        string $collectionName,
        string $folder,
    ): void {
        $dataUrl = $base64Data;
        $mime = 'image/jpeg';

        if (preg_match('/^data:(.+?);base64,/', $dataUrl, $matches)) {
            $mime = $matches[1];
            $dataUrl = preg_replace('/^data:.+?;base64,/', '', $dataUrl);
        }

        $binary = base64_decode($dataUrl, true);

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

        $extension = match ($mime) {
            'image/png' => 'png',
            'image/webp' => 'webp',
            'image/gif' => 'gif',
            default => 'jpg',
        };

        $date = now()->format('Y/m/d');
        $uuid = Str::uuid();
        $key = "{$folder}/{$date}/{$uuid}/photo.{$extension}";

        Storage::disk('s3')->put($key, $binary, [
            'ContentType' => $mime,
            'ACL' => 'public-read',
        ]);

        $this->registerMedia(
            model: $model,
            s3Key: $key,
            collectionName: $collectionName,
            mimeType: $mime,
            fileSize: strlen($binary),
            orderColumn: 1,
        );
    }

    private function registerMedia(
        Model $model,
        string $s3Key,
        string $collectionName,
        ?int $fileSize = null,
        ?string $mimeType = null,
        ?int $orderColumn = null,
        ?string $name = null,
    ): void {
        $fileName = pathinfo($s3Key, PATHINFO_BASENAME);

        $media = Media::create([
            'model_type' => $model->getMorphClass(),
            'model_id' => $model->id,
            'uuid' => Str::uuid(),
            'collection_name' => $collectionName,
            'name' => $name ?? pathinfo($s3Key, PATHINFO_FILENAME),
            'file_name' => $fileName,
            'mime_type' => $mimeType ?? 'image/jpeg',
            'disk' => 's3',
            'conversions_disk' => 's3',
            'size' => $fileSize ?? 0,
            'manipulations' => [],
            'custom_properties' => ['s3_key' => $s3Key],
            'generated_conversions' => [],
            'responsive_images' => [],
            'order_column' => $orderColumn ?? 1,
        ]);

        $this->generateMediaConversions($media);
    }

    private function generateMediaConversions(Media $media): void
    {
        $model = $media->model;

        if (! $model || ! method_exists($model, 'registerMediaConversions')) {
            return;
        }

        $conversions = ConversionCollection::createForMedia($media);

        if ($conversions->isEmpty()) {
            return;
        }

        Bus::dispatch(new PerformConversionsJob($conversions, $media));
    }

    private function syncPhoto(Model $model, array $data, string $collectionName = 'photos'): void
    {
        if (! array_key_exists('photo_key', $data)) {
            return;
        }

        $currentKey = $model->getFirstMedia($collectionName)?->getCustomProperty('s3_key');

        if ($data['photo_key'] === $currentKey) {
            return;
        }

        $model->clearMediaCollection($collectionName);

        if (! empty($data['photo_key'])) {
            $this->registerMedia(
                model: $model,
                s3Key: $data['photo_key'],
                collectionName: $collectionName,
                orderColumn: 1,
            );
        }
    }

    private function syncReceipt(Model $model, ?string $newKey, string $collectionName = 'receipts', string $cachePrefix = 'receipt', ?int $fileSize = null, ?string $fileMimeType = null): void
    {
        $currentMedia = $model->getFirstMedia($collectionName);
        $currentKey = $currentMedia?->getCustomProperty('s3_key');

        if ($newKey === $currentKey) {
            return;
        }

        if ($currentMedia) {
            Cache::forget("{$cachePrefix}_{$currentMedia->id}");
        }

        $model->clearMediaCollection($collectionName);

        if (! empty($newKey)) {
            $this->registerMedia(
                model: $model,
                s3Key: $newKey,
                collectionName: $collectionName,
                fileSize: $fileSize,
                mimeType: $fileMimeType,
            );
        }
    }

    private function registerPhoto(Model $model, string $s3Key, string $collectionName = 'images'): void
    {
        $this->registerMedia(
            model: $model,
            s3Key: $s3Key,
            collectionName: $collectionName,
            orderColumn: 1,
        );
    }

    public function registerPhotos(Model $model, array $photoKeys, string $collectionName = 'images'): void
    {
        foreach ($photoKeys as $order => $s3Key) {
            $this->registerMedia(
                model: $model,
                s3Key: $s3Key,
                collectionName: $collectionName,
                orderColumn: $order + 1,
            );
        }
    }

    public function syncPhotos(Model $model, array $photoKeys, string $collectionName = 'images'): void
    {
        $existingMedia = $model->getMedia($collectionName);
        $existingKeys = $existingMedia
            ->map(fn (Media $m) => $m->getCustomProperty('s3_key') ?? $m->file_name)
            ->toArray();

        foreach ($existingMedia as $media) {
            $key = $media->getCustomProperty('s3_key') ?? $media->file_name;

            if (! in_array($key, $photoKeys)) {
                $media->delete();
            }
        }

        $newKeys = array_values(array_diff($photoKeys, $existingKeys));

        $this->registerPhotos($model, $newKeys, $collectionName);
    }

    public function getTemporaryUrls(Model $model, string $collectionName = 'images'): array
    {
        $media = $model->getMedia($collectionName);

        return $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
    }
}
