<?php

declare(strict_types=1);

namespace App\Http\Controllers\Studio;

use App\Http\Controllers\Controller;
use App\Models\World;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\ImageManager;
use RuntimeException;

final class StudioWorldMediaApiController extends Controller
{
    private const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'];

    private const MAX_FILE_SIZE_KB = 6144;

    private const SLOT_CONFIG = [
        'cover' => [
            'max_width' => 2200,
            'max_height' => 1400,
            'min_width' => 1200,
            'min_height' => 630,
        ],
        'og' => [
            'max_width' => 1600,
            'max_height' => 1000,
            'min_width' => 1200,
            'min_height' => 630,
        ],
    ];

    private ?ImageManager $manager = null;

    public function __construct()
    {
        try {
            $this->manager = extension_loaded('gd')
                ? new ImageManager(new GdDriver())
                : new ImageManager(new ImagickDriver());
        } catch (\Throwable) {
            $this->manager = null;
        }
    }

    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'slot' => ['required', 'string', 'in:cover,og'],
            'image' => [
                'required',
                'file',
                'image',
                'max:' . self::MAX_FILE_SIZE_KB,
                'mimes:jpg,jpeg,png,webp',
                'mimetypes:image/jpeg,image/png,image/webp',
            ],
            'world_id' => ['nullable', 'integer', 'exists:worlds,id'],
        ]);

        $world = isset($validated['world_id']) ? World::query()->findOrFail((int) $validated['world_id']) : null;

        if ($world instanceof World) {
            $this->authorize('update', $world);
        } else {
            $this->authorize('create', World::class);
        }

        /** @var UploadedFile $file */
        $file = $validated['image'];
        $slot = (string) $validated['slot'];

        try {
            $stored = $this->storeMediaFile($file, $slot);

            return response()->json([
                'success' => true,
                'slot' => $slot,
                'path' => $stored['path'],
                'url' => $this->publicUrlForPath($stored['path']),
                'width' => $stored['width'],
                'height' => $stored['height'],
                'mime_type' => 'image/webp',
                'size_bytes' => $stored['size_bytes'],
            ]);
        } catch (RuntimeException $e) {
            return response()->json([
                'error' => 'Validation failed',
                'message' => $e->getMessage(),
            ], 422);
        } catch (\Throwable $e) {
            logger()->error('World media upload failed', [
                'user_id' => (int) ($request->user()?->id ?? 0),
                'world_id' => $world?->id,
                'slot' => $slot,
                'message' => $e->getMessage(),
            ]);

            return response()->json([
                'error' => 'Upload failed',
                'message' => 'Could not upload image right now.',
            ], 500);
        }
    }

        public function destroy(Request $request): JsonResponse
        {
            $validated = $request->validate([
                'path' => ['required', 'string', 'max:2048'],
                'world_id' => ['nullable', 'integer', 'exists:worlds,id'],
            ]);

            $world = isset($validated['world_id']) ? World::query()->findOrFail((int) $validated['world_id']) : null;

            if ($world instanceof World) {
                $this->authorize('update', $world);
            } else {
                $this->authorize('create', World::class);
            }

            $this->deleteMediaFile((string) $validated['path']);

            return response()->json([
                'success' => true,
            ]);
        }

    /**
     * @return array{path:string,width:int,height:int,size_bytes:int}
     */
    private function storeMediaFile(UploadedFile $file, string $slot): array
    {
        $this->assertImageManager();
        $this->assertStorageIsAllowed();

        $config = self::SLOT_CONFIG[$slot] ?? self::SLOT_CONFIG['cover'];
        $uploadPath = (string) ($file->getRealPath() ?: $file->getPathname());

        if ($uploadPath === '' || ! is_readable($uploadPath)) {
            throw new RuntimeException('Unable to resolve uploaded image path.');
        }

        $raw = file_get_contents($uploadPath);
        if ($raw === false || $raw === '') {
            throw new RuntimeException('Unable to read uploaded image.');
        }

        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mime = strtolower((string) $finfo->buffer($raw));
        if (! in_array($mime, self::ALLOWED_MIME_TYPES, true)) {
            throw new RuntimeException('Unsupported image mime type.');
        }

        $size = @getimagesizefromstring($raw);
        if (! is_array($size) || ($size[0] ?? 0) < 1 || ($size[1] ?? 0) < 1) {
            throw new RuntimeException('Uploaded file is not a valid image.');
        }

        $width = (int) ($size[0] ?? 0);
        $height = (int) ($size[1] ?? 0);

        if ($width < $config['min_width'] || $height < $config['min_height']) {
            throw new RuntimeException(sprintf(
                'Image is too small. Minimum required size is %dx%d.',
                $config['min_width'],
                $config['min_height'],
            ));
        }

        $image = $this->manager->read($raw)->scaleDown(width: $config['max_width'], height: $config['max_height']);
        $encoded = (string) $image->encode(new WebpEncoder(85));

        $hash = hash('sha256', $encoded);
        $path = $this->mediaPath($slot, $hash);
        $disk = Storage::disk($this->mediaDiskName());

        $written = $disk->put($path, $encoded, [
            'visibility' => 'public',
            'CacheControl' => 'public, max-age=31536000, immutable',
            'ContentType' => 'image/webp',
        ]);

        if ($written !== true) {
            throw new RuntimeException('Unable to store image in object storage.');
        }

        return [
            'path' => $path,
            'width' => (int) $image->width(),
            'height' => (int) $image->height(),
            'size_bytes' => strlen($encoded),
        ];
    }

    private function mediaDiskName(): string
    {
        return (string) config('covers.disk', 's3');
    }

    private function mediaPath(string $slot, string $hash): string
    {
        return sprintf(
            'worlds/media/%s/%s/%s/%s.webp',
            $slot,
            substr($hash, 0, 2),
            substr($hash, 2, 2),
            $hash,
        );
    }

    private function publicUrlForPath(string $path): string
    {
        return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
    }

    private function deleteMediaFile(string $path): void
    {
        $trimmed = ltrim(trim($path), '/');

        if ($trimmed === '' || ! Str::startsWith($trimmed, 'worlds/media/')) {
            return;
        }

        Storage::disk($this->mediaDiskName())->delete($trimmed);
    }

    private function assertImageManager(): void
    {
        if ($this->manager !== null) {
            return;
        }

        throw new RuntimeException('Image processing is not available on this environment.');
    }

    private function assertStorageIsAllowed(): void
    {
        if (! app()->environment('production')) {
            return;
        }

        $diskName = $this->mediaDiskName();
        if (in_array($diskName, ['local', 'public'], true)) {
            throw new RuntimeException('Production world media storage must use object storage, not local/public disks.');
        }
    }
}