<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Kategori;
use App\Models\Wisata;
use Database\Seeders\DestinationSeeder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class WisataController extends Controller
{
    public function index(Request $request): View
    {
        $this->seedDefaultDestinationsIfEmpty();

        $wisatas = Wisata::query()
            ->with('kategori')
            ->when($request->search, function ($query, string $search): void {
                $query->where(function ($query) use ($search): void {
                    $query->where('title', 'like', "%{$search}%")
                        ->orWhere('location', 'like', "%{$search}%");
                });
            })
            ->latest()
            ->paginate(10)
            ->withQueryString();

        return view('admin.wisata.index', compact('wisatas'));
    }

    private function seedDefaultDestinationsIfEmpty(): void
    {
        if (Wisata::query()->exists()) {
            return;
        }

        Artisan::call('db:seed', [
            '--class' => DestinationSeeder::class,
            '--force' => true,
        ]);
    }

    public function create(): View
    {
        return view('admin.wisata.form', [
            'wisata' => new Wisata(),
            'kategoris' => Kategori::query()->orderBy('nama')->get(),
        ]);
    }

    public function show(Wisata $wisata): View
    {
        $wisata->load('kategori');

        return view('admin.wisata.show', compact('wisata'));
    }

    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate($this->rules(), $this->messages());
        $validated['slug'] = $this->uniqueSlug($validated['title']);
        $validated['is_video'] = $request->boolean('is_video');
        $validated = $this->storeUploads($request, $validated);

        Wisata::query()->create($validated);

        return redirect()->route('admin.wisata.index')->with('success', 'Data wisata berhasil ditambahkan.');
    }

    public function edit(Wisata $wisata): View
    {
        return view('admin.wisata.form', [
            'wisata' => $wisata,
            'kategoris' => Kategori::query()->orderBy('nama')->get(),
        ]);
    }

    public function update(Request $request, Wisata $wisata): RedirectResponse
    {
        $validated = $request->validate($this->rules($wisata), $this->messages());
        $validated['slug'] = $this->uniqueSlug($validated['title'], $wisata->id);
        $validated['is_video'] = $request->boolean('is_video');
        $validated = $this->storeUploads($request, $validated, $wisata);

        $wisata->update($validated);

        return redirect()->route('admin.wisata.index')->with('success', 'Data wisata berhasil diperbarui.');
    }

    public function destroy(Wisata $wisata): RedirectResponse
    {
        $this->deleteFiles($wisata);
        $wisata->delete();

        return back()->with('success', 'Data wisata berhasil dihapus.');
    }

    private function rules(?Wisata $wisata = null): array
    {
        return [
            'kategori_id' => ['required', 'exists:kategoris,id'],
            'title' => ['required', 'string', 'max:180', Rule::unique('wisatas', 'title')->ignore($wisata)],
            'location' => ['required', 'string', 'max:255'],
            'rating' => ['required', 'numeric', 'min:0', 'max:5'],
            'distance' => ['nullable', 'string', 'max:80'],
            'elevation' => ['nullable', 'string', 'max:80'],
            'tiket_parkir' => ['nullable', 'string', 'max:120'],
            'jam_operasional' => ['nullable', 'string', 'max:160'],
            'short_description' => ['nullable', 'string', 'max:500'],
            'overview' => ['nullable', 'string'],
            'image' => [$wisata ? 'nullable' : 'required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
            'model_path' => ['nullable', 'file', 'max:51200', $this->modelFileRule()],
            'video_path' => ['nullable', 'file', 'mimes:mp4,webm,mov', 'max:102400'],
            'is_video' => ['nullable', 'boolean'],
            'latitude' => ['nullable', 'numeric', 'between:-90,90'],
            'longitude' => ['nullable', 'numeric', 'between:-180,180'],
        ];
    }

    private function messages(): array
    {
        return [
            'model_path.file' => 'File model 3D tidak valid. Silakan pilih file .glb atau .gltf.',
            'model_path.max' => 'Ukuran file model 3D maksimal 50MB.',
            'image.required' => 'Gambar wisata wajib diunggah.',
            'image.image' => 'File gambar wisata harus berupa gambar yang valid.',
            'image.mimes' => 'Gambar wisata harus berformat JPG, JPEG, PNG, atau WEBP.',
            'image.max' => 'Ukuran gambar wisata maksimal 4MB.',
            'video_path.mimes' => 'Video wisata harus berformat MP4, WEBM, atau MOV.',
            'video_path.max' => 'Ukuran video wisata maksimal 100MB.',
        ];
    }

    private function modelFileRule(): \Closure
    {
        return function (string $attribute, mixed $value, \Closure $fail): void {
            unset($attribute);

            if (! $value) {
                return;
            }

            $extension = Str::lower($value->getClientOriginalExtension());

            if (! in_array($extension, ['glb', 'gltf'], true)) {
                $fail('File model 3D harus berformat .glb atau .gltf.');

                return;
            }

            $mimeType = Str::lower($value->getMimeType() ?: $value->getClientMimeType() ?: '');
            $allowedMimeTypes = [
                'model/gltf-binary',
                'model/gltf+json',
                'application/octet-stream',
                'application/json',
                'text/plain',
                'text/json',
            ];

            if ($mimeType && ! in_array($mimeType, $allowedMimeTypes, true)) {
                $fail('File model 3D tidak dikenali. Gunakan file .glb atau .gltf yang valid.');
            }
        };
    }

    private function storeUploads(Request $request, array $data, ?Wisata $wisata = null): array
    {
        foreach (['image' => 'wisata', 'model_path' => 'models', 'video_path' => 'videos'] as $field => $folder) {
            if ($request->hasFile($field)) {
                if ($wisata?->{$field}) {
                    Storage::disk('public')->delete($wisata->publicStoragePath($wisata->{$field}));
                }

                $data[$field] = $field === 'image'
                    ? Storage::disk('public')->putFile('wisata', $request->file('image'))
                    : $request->file($field)->storeAs(
                        $folder,
                        $this->uniqueUploadName($request->file($field)),
                        'public'
                    );
            }
        }

        return $data;
    }

    private function uniqueUploadName(\Illuminate\Http\UploadedFile $file): string
    {
        $extension = Str::lower($file->getClientOriginalExtension() ?: $file->extension());

        return Str::uuid().($extension ? ".{$extension}" : '');
    }

    private function deleteFiles(Wisata $wisata): void
    {
        Storage::disk('public')->delete(array_filter([
            $wisata->publicStoragePath($wisata->image),
            $wisata->publicStoragePath($wisata->model_path),
            $wisata->publicStoragePath($wisata->video_path),
        ]));
    }

    private function uniqueSlug(string $name, ?int $ignoreId = null): string
    {
        $slug = Str::slug($name);
        $original = $slug;
        $counter = 1;

        while (Wisata::query()->where('slug', $slug)->when($ignoreId, fn ($query) => $query->whereKeyNot($ignoreId))->exists()) {
            $slug = "{$original}-{$counter}";
            $counter++;
        }

        return $slug;
    }
}
