<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Media;
use App\Services\MediaLibraryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class MediaController extends Controller
{
    public function index(Request $request): View
    {
        $query = Media::query();

        if ($search = trim((string) $request->query('search'))) {
            $query->where('file_name', 'like', "%{$search}%");
        }

        if ($type = $request->query('type')) {
            $query->where('mime_type', 'like', $type === 'image' ? 'image/%' : "$type/%");
        }

        if ($folder = $request->query('folder')) {
            $query->where('folder', 'like', $folder . '%');
        }

        if ($purpose = $request->query('purpose')) {
            $query->where('purpose', $purpose);
        }

        if ($productType = $request->query('product_type')) {
            $query->where('product_type', $productType);
        }

        if ($brand = $request->query('brand')) {
            $query->where('brand', $brand);
        }

        $media = $query->latest()->paginate(24)->withQueryString();

        return view('admin.media.index', [
            'media' => $media,
            'roots' => config('media-library.roots'),
            'purposes' => config('media-library.purposes'),
            'productTypes' => config('media-library.product_types'),
            'brands' => config('media-library.brands'),
        ]);
    }

    public function store(Request $request, MediaLibraryService $library): JsonResponse|RedirectResponse
    {
        $metadata = $this->metadataRules();

        $request->validate([
            'file' => ['nullable', 'image', 'mimes:jpeg,png,webp,gif', 'max:5120'],
            'images' => ['nullable', 'array'],
            'images.*' => ['image', 'mimes:jpeg,png,webp,gif', 'max:5120'],
            'source' => ['nullable', Rule::in(array_keys(config('media-library.sources')))],
            'url' => ['nullable', 'string', 'max:2048'],
            ...$metadata,
        ]);

        $source = $request->input('source', 'local');
        $meta = $this->metadataPayload($request);

        $items = [];

        if ($source === 'direct_link') {
            $url = (string) $request->input('url', '');

            if ($url === '') {
                return $this->uploadError('A direct link URL is required.', $request);
            }

            try {
                $file = $this->fetchRemoteImage($url);

                if ($file === null) {
                    return $this->uploadError('Could not retrieve a valid image from that URL.', $request);
                }

                $items[] = $library->store($file, null, 'library', ['source' => 'direct_link', ...$meta]);
            } catch (\Throwable $e) {
                return $this->uploadError('Could not import that image: ' . $e->getMessage(), $request);
            }
        } else {
            $files = $request->hasFile('images')
                ? $request->file('images')
                : ($request->hasFile('file') ? [$request->file('file')] : []);

            if ($files === []) {
                return $this->uploadError('No file was selected.', $request);
            }

            foreach ($files as $file) {
                $items[] = $library->store($file, null, 'library', ['source' => 'local', ...$meta]);
            }
        }

        if ($request->expectsJson()) {
            return response()->json([
                'media' => array_map(fn ($media) => $this->serializeMedia($media), $items),
            ]);
        }

        return redirect()
            ->route('admin.media.index')
            ->with('status', count($items) . ' file(s) uploaded.');
    }

public function picker(Request $request): View
    {
        $context = $request->query('context', 'general');
        
        $contextConfig = [
            'general' => ['accept' => 'image/*,.svg', 'mime' => 'image/%'],
            'favicon' => ['accept' => 'image/*,.ico,.svg', 'mime' => 'image/%'],
            'logo' => ['accept' => 'image/*,.svg', 'mime' => 'image/%'],
            'hero' => ['accept' => 'image/*', 'mime' => 'image/%'],
            'product' => ['accept' => 'image/*', 'mime' => 'image/%'],
            'icon' => ['accept' => '.svg', 'mime' => 'image/svg+xml'],
        ];
        
        $config = $contextConfig[$context] ?? $contextConfig['general'];
        
        $query = Media::query()->where('mime_type', 'like', $config['mime']);
        
        if ($search = trim((string) $request->query('search'))) {
            $query->where('file_name', 'like', "%{$search}%");
        }
        
        if ($brand = $request->query('brand')) {
            $query->where('brand', $brand);
        }
        
        if ($productType = $request->query('product_type')) {
            $query->where('product_type', $productType);
        }
        
        $media = $query->latest()->limit(60)->get();
        
        return view('admin.media.partials.library-rows', ['media' => $media, 'context' => $context]);
    }

    public function update(Request $request, Media $media): JsonResponse|RedirectResponse
    {
        $validated = $request->validate($this->metadataRules());

        $media->update(array_filter($validated, fn ($v) => $v !== null));

        if ($request->expectsJson()) {
            return response()->json(['media' => $this->serializeMedia($media->fresh())]);
        }

        return redirect()
            ->route('admin.media.index')
            ->with('status', "Media “{$media->file_name}” updated.");
    }

    /**
     * Save a cropped/edited variant of an image as a new media asset.
     * The original is preserved.
     */
    public function crop(Request $request, Media $media, MediaLibraryService $library): JsonResponse
    {
        $request->validate([
            'image' => ['required', 'string', 'max:12000000'],
        ]);

        $dataUrl = (string) $request->input('image');

        if (! str_starts_with($dataUrl, 'data:image/')) {
            return response()->json(['message' => 'Invalid image data.'], 422);
        }

        $decoded = $this->decodeDataUrl($dataUrl);

        if ($decoded === null) {
            return response()->json(['message' => 'Could not decode image.'], 422);
        }

        [$mime, $binary] = $decoded;

        $tmp = $this->makeTempFile('izone-crop-');
        file_put_contents($tmp, $binary);

        $extension = [
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/webp' => 'webp',
            'image/gif' => 'gif',
        ][$mime] ?? 'png';

        $name = pathinfo($media->file_name, PATHINFO_FILENAME) . '-edited.' . $extension;

        $file = new UploadedFile($tmp, $name, $mime, null, true);

        $newMedia = $library->store($file, null, 'library', [
            'source' => 'edit',
            'folder' => $media->folder,
            'purpose' => $media->purpose,
            'product_type' => $media->product_type,
            'brand' => $media->brand,
        ]);

        return response()->json(['media' => $this->serializeMedia($newMedia)]);
    }

    private function decodeDataUrl(string $dataUrl): ?array
    {
        if (! preg_match('#^data:(image/[a-z+]+);base64,(.*)$#is', $dataUrl, $matches)) {
            return null;
        }

        $mime = strtolower($matches[1]);

        if (! in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true)) {
            return null;
        }

        $binary = base64_decode($matches[2], true);

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

        return [$mime, $binary];
    }

    public function destroy(Media $media): RedirectResponse
    {
        Storage::disk($media->disk ?: 'public')->delete($media->file_path);
        $media->delete();

        return back()->with('status', "Media “{$media->file_name}” deleted.");
    }

    public function trash(Request $request): View
    {
        $query = Media::onlyTrashed();

        if ($search = trim((string) $request->query('search'))) {
            $query->where('file_name', 'like', "%{$search}%");
        }

        if ($type = $request->query('type')) {
            $query->where('mime_type', 'like', $type === 'image' ? 'image/%' : "$type/%");
        }

        $media = $query->latest()->paginate(24)->withQueryString();

        return view('admin.media.trash', [
            'media' => $media,
        ]);
    }

    public function restore(Media $media): RedirectResponse
    {
        $media->restore();

        return back()->with('status', "Media “{$media->file_name}” restored.");
    }

    public function forceDelete(Media $media): RedirectResponse
    {
        Storage::disk($media->disk ?: 'public')->delete($media->file_path);
        $media->forceDelete();

        return back()->with('status', "Media “{$media->file_name}” permanently deleted.");
    }

    public function destroy(Media $media): RedirectResponse
    {

    /**
     * Shared metadata validation rules.
     */
    private function metadataRules(): array
    {
        return [
            'folder' => ['nullable', 'string', 'max:255'],
            'purpose' => ['nullable', 'string', 'max:40'],
            'product_type' => ['nullable', 'string', 'max:40'],
            'brand' => ['nullable', 'string', 'max:80'],
        ];
    }

    /**
     * Extract the metadata payload from the request.
     */
    private function metadataPayload(Request $request): array
    {
        $payload = [];

        foreach (['folder', 'purpose', 'product_type', 'brand'] as $key) {
            $value = $request->input($key);
            if ($value !== null && $value !== '') {
                $payload[$key] = $value;
            }
        }

        return $payload;
    }

    private function serializeMedia(Media $media): array
    {
        return [
            'id' => $media->id,
            'url' => $media->url(),
            'file_name' => $media->file_name,
            'dimensions' => $media->dimensionsLabel(),
            'size' => $media->humanSize(),
            'mime_type' => $media->mime_type,
            'folder' => $media->folder,
            'purpose' => $media->purpose,
            'product_type' => $media->product_type,
            'brand' => $media->brand,
            'source' => $media->source,
        ];
    }

    private function uploadError(string $message, Request $request): JsonResponse|RedirectResponse
    {
        if ($request->expectsJson()) {
            return response()->json(['message' => $message], 422);
        }

        return redirect()->back()->withErrors(['images' => $message])->withInput();
    }

    /**
     * Safely fetch an image from a remote URL, guarding against SSRF.
     */
    private function fetchRemoteImage(string $url): ?UploadedFile
    {
        if (! filter_var($url, FILTER_VALIDATE_URL)) {
            throw new \InvalidArgumentException('Invalid URL.');
        }

        $scheme = parse_url($url, PHP_URL_SCHEME);
        $host = parse_url($url, PHP_URL_HOST);

        if (! in_array($scheme, ['http', 'https'], true) || ! $host) {
            throw new \InvalidArgumentException('Only http(s) URLs are allowed.');
        }

        // Resolve the host and reject private / loopback / reserved addresses.
        $ips = gethostbynamel($host);

        if ($ips === false || $ips === []) {
            throw new \InvalidArgumentException('Could not resolve host.');
        }

        foreach ($ips as $ip) {
            if ($this->isPrivateIp($ip)) {
                throw new \InvalidArgumentException('Private network addresses are not allowed.');
            }
        }

        $context = stream_context_create([
            'http' => [
                'timeout' => 15,
                'follow_location' => true,
                'max_redirects' => 3,
                'ignore_errors' => true,
            ],
            'ssl' => ['verify_peer' => true],
        ]);

        $contents = @file_get_contents($url, false, $context, 0, 8 * 1024 * 1024);

        if ($contents === false || $contents === '') {
            return null;
        }

        $tmp = $this->makeTempFile('izone-dl-');

        if ($tmp === false) {
            throw new \RuntimeException('Could not create a temporary file.');
        }

        file_put_contents($tmp, $contents);

        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mime = $finfo->file($tmp);

        if (! in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true)) {
            @unlink($tmp);
            throw new \InvalidArgumentException('The URL does not point to a supported image.');
        }

        $extension = [
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/webp' => 'webp',
            'image/gif' => 'gif',
        ][$mime];

        $basename = 'remote-' . Str::random(6) . '.' . $extension;

        return new UploadedFile($tmp, $basename, $mime, null, true);
    }

    private function isPrivateIp(string $ip): bool
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
            return true;
        }

        return false;
    }

    /**
     * Create a temp file in a guaranteed-writable project-local directory.
     */
    private function makeTempFile(string $prefix): string|false
    {
        $dir = storage_path('app/private/tmp');

        if (! is_dir($dir) && ! mkdir($dir, 0755, true) && ! is_dir($dir)) {
            return tempnam(sys_get_temp_dir(), $prefix);
        }

        $tmp = tempnam($dir, $prefix);

        if ($tmp === false) {
            return tempnam(sys_get_temp_dir(), $prefix);
        }

        return $tmp;
    }
}
