<?php

namespace Zoomyboy\MedialibraryHelper;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Spatie\Image\Image;
use Spatie\LaravelData\DataCollection;
use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidBase64Data;
use Spatie\MediaLibrary\MediaCollections\FileAdder;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Zoomyboy\MedialibraryHelper\Rules\ModelRule;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\File\File;

class MediaController
{
    use AuthorizesRequests;

    public function store(Request $request)
    {
        $request->validate([
            'parent' => ['required', new ModelRule()],
        ]);

        if (is_null($request->input('parent.id'))) {
            return $this->storeDeferred($request);
        }

        $model = ModelRule::getModel($request->input('parent'));
        $collection = ModelRule::getCollection($request->input('parent'));
        $isSingle = 1 === $collection->collectionSizeLimit;
        $this->authorize('storeMedia', [$model, $collection->name]);

        $request->validate($isSingle ? [
            'payload' => 'array',
            'payload.name' => 'required|string|regex:/\..*$/|max:255',
            'payload.content' => 'required|string',
        ] : [
            'payload' => 'required|array|min:1',
            'payload.*' => 'array',
            'payload.*.name' => 'required|string|regex:/\..*$/|max:255',
            'payload.*.content' => 'required|string',
        ]);

        $content = $isSingle ? [$request->input('payload')] : $request->input('payload');

        $medias = collect($content)->map(function ($c) use ($collection, $model) {
            $file = new MediaFile($c['name']);
            $file->setBasename($collection->runCallback('forceFileName', $model, $file->getBasename()));
            $file->setExtension(MediaFile::extensionFromData($c['content']));

            $file->setExtension($collection->runCallback('convert', $file->getExtension()));
            $adder = $this->fileAdderFromData($model, $c['content'], $collection)
                ->usingName($file->getBasename())
                ->usingFileName($file->getFilename())
                ->withCustomProperties($collection->runCallback('withDefaultProperties', $file->getFilename()));

            return tap(
                $collection->runCallback('storing', $adder, $file->getFilename())->toMediaCollection($collection->name),
                fn ($media) => $collection->runCallback('stored', $media)
            );
        });

        $collection->runCallback('after', $model->fresh());

        return $isSingle ? MediaData::from($medias->first()) : MediaData::collection($medias);
    }

    protected function storeDeferred(Request $request)
    {
        $modelName = ModelRule::getModelClassName($request->input('parent'));
        $collection = ModelRule::getCollection($request->input('parent'));
        $isSingle = 1 === $collection->collectionSizeLimit;

        $request->validate($isSingle ? [
            'payload' => 'array',
            'payload.name' => 'required|string|regex:/\..*$/|max:255',
            'payload.content' => 'required|string',
        ] : [
            'payload' => 'required|array|min:1',
            'payload.*' => 'array',
            'payload.*.name' => 'required|string|regex:/\..*$/|max:255',
            'payload.*.content' => 'required|string',
        ]);

        $content = $isSingle ? [$request->input('payload')] : $request->input('payload');

        $tempPaths = collect($content)->map(function ($c) use ($collection) {
            $file = new MediaFile($c['name']);
            $file->setExtension(MediaFile::extensionFromData($c['content']));
            $file->setExtension($collection->runCallback('convert', $file->getExtension()));
            $tmpFile = $this->storeTemporaryFile($c['content'], $collection);
            Storage::disk(config('media-library.temp_disk'))->move($tmpFile, 'media-library/' . $file->getFilename());
            return 'media-library/' . $file->getFilename();
        });

        $this->authorize('storeMedia', [$modelName, null, $collection->name]);
        return $isSingle ? DeferredMediaData::fromPath($tempPaths->first(), $collection) : DeferredMediaData::collectionFromPaths($tempPaths, $collection);
    }

    public function update(Request $request, Media $media): MediaData
    {
        $this->authorize('updateMedia', [$media->model, $media->collection_name]);

        $rules = collect($media->model->getMediaCollection($media->collection_name)->runCallback('withPropertyValidation', $media->file_name))
            ->mapWithKeys(fn ($rule, $key) => ["properties.{$key}" => $rule])->toArray();

        $validated = $request->validate($rules);

        $media->update(['custom_properties' => data_get($validated, 'properties', [])]);
        $media->model->getMediaCollection($media->collection_name)->runCallback('after', $media->model->fresh());

        return MediaData::from($media);
    }

    public function index(Request $request, $parentModel, int $parentId, string $collectionName): MediaData|DataCollection
    {
        $model = app('media-library-helpers')->get($parentModel);
        $model = $model::find($parentId);
        $this->authorize('listMedia', [$model, $collectionName]);
        $collection = $model->getMediaCollection($collectionName);
        $isSingle = 1 === $collection->collectionSizeLimit;

        abort_if($isSingle && !$model->getFirstMedia($collectionName) && !MediaData::defaultFromCollection($model, $collection), 404);

        if ($isSingle && !$model->getFirstMedia($collectionName)) {
            return MediaData::defaultFromCollection($model, $collection);
        }

        return $isSingle
            ? MediaData::from($model->getFirstMedia($collectionName))
            : MediaData::collection($model->getMedia($collectionName));
    }

    public function destroy(Media $media, Request $request): JsonResponse
    {
        $this->authorize('destroyMedia', [$media->model, $media->collection_name]);
        $model = $media->model->fresh();
        $collection = $model->getMediaCollection($media->collection_name);
        $media->delete();
        $collection->runCallback('destroyed', $media->model->fresh());
        $collection->runCallback('after', $media->model->fresh());

        return response()->json([]);
    }

    protected function hasCallback(MediaCollection $collection, string $callback): bool
    {
        return property_exists($collection, $callback);
    }

    private function storeTemporaryFile(string $data, MediaCollection $collection): string
    {
        $maxWidth = $collection->runCallback('maxWidth', 9);
        if (str_contains($data, ';base64')) {
            [$_, $data] = explode(';', $data);
            [$_, $data] = explode(',', $data);
        }

        // strict mode filters for non-base64 alphabet characters
        $binaryData = base64_decode($data, true);

        if (false === $binaryData) {
            throw InvalidBase64Data::create();
        }

        // decoding and then reencoding should not change the data
        if (base64_encode($binaryData) !== $data) {
            throw InvalidBase64Data::create();
        }

        $tmpFile = 'media-library/' . str()->uuid()->toString() . '.' . $collection->runCallback('convert', MediaFile::extensionFromData($data));
        Storage::disk(config('media-library.temp_disk'))->put($tmpFile, $binaryData);

        $imagePath = Storage::disk(config('media-library.temp_disk'))->path($tmpFile);
        $image = Image::load($imagePath);
        if (null !== $maxWidth && 'image/jpeg' === Storage::disk(config('media-library.temp_disk'))->mimeType($tmpFile)) {
            $image->width($maxWidth);
        }

        $image->save();

        return $tmpFile;
    }

    protected function fileAdderFromData($model, $data, $collection): FileAdder
    {
        $tmpFile = $this->storeTemporaryFile($data, $collection);

        return $model->addMediaFromDisk($tmpFile, config('media-library.temp_disk'));
    }
}
