<?php

namespace App\Http\Controllers;

use App\Models\Listing;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class ListingController extends Controller
{
    private const CATEGORIES = [
        'VILLA', 'CABIN', 'APARTMENT', 'BOUTIQUE',
        'UNIQUE', 'BEACHFRONT', 'COUNTRYSIDE', 'CITY',
    ];

    public function index(Request $request): JsonResponse
    {
        $input = $this->normalizedFilters($request);
        $filters = Validator::make($input, [
            'q' => ['nullable', 'string', 'max:200'],
            'city' => ['nullable', 'string', 'max:120'],
            'country' => ['nullable', 'string', 'max:120'],
            'category' => ['nullable', Rule::in(self::CATEGORIES)],
            'min_guests' => ['nullable', 'integer', 'min:1'],
            'max_guests' => ['nullable', 'integer', 'min:1', 'gte:min_guests'],
            'min_price' => ['nullable', 'numeric', 'min:0'],
            'max_price' => ['nullable', 'numeric', 'min:0', 'gte:min_price'],
            'latitude' => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude,radius_km'],
            'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude,radius_km'],
            'radius_km' => ['nullable', 'numeric', 'min:0.1', 'max:500', 'required_with:latitude,longitude'],
            'page' => ['required', 'integer', 'min:1'],
            'limit' => ['required', 'integer', 'min:1', 'max:100'],
        ])->validate();

        $query = Listing::query()
            ->where('status', 'PUBLISHED')
            ->with(['images', 'amenities', 'partner.user:id,first_name,last_name,avatar']);

        $this->applyTextAndRangeFilters($query, $filters);
        $nearby = $this->applyLocationFilter($query, $filters);

        if (! $nearby) {
            $query->orderByDesc('is_featured')->latest('created_at');
        }

        $paginator = $query->paginate($filters['limit'], ['*'], 'page', $filters['page']);

        return response()->json([
            'data' => $paginator->items(),
            'page' => $paginator->currentPage(),
            'limit' => $paginator->perPage(),
            'total' => $paginator->total(),
            'totalPages' => $paginator->lastPage(),
        ]);
    }

    public function featured(Request $request): JsonResponse
    {
        $data = $request->validate(['limit' => ['nullable', 'integer', 'min:1', 'max:50']]);

        $listings = Listing::query()
            ->where('status', 'PUBLISHED')
            ->where('is_featured', true)
            ->with([
                'images' => fn ($query) => $query->orderBy('order')->limit(1),
                'partner.user:id,first_name,last_name,avatar',
            ])
            ->latest('created_at')
            ->limit($data['limit'] ?? 10)
            ->get();

        return response()->json($listings);
    }

    public function show(string $id): JsonResponse
    {
        $listing = Listing::query()
            ->where('status', 'PUBLISHED')
            ->findOrFail($id);

        $listing->increment('views_count');
        $listing->load([
            'images',
            'amenities',
            'partner.user:id,first_name,last_name,avatar,created_at',
            'reviews' => fn ($query) => $query
                ->where('is_hidden', false)
                ->with('user:id,first_name,last_name,avatar')
                ->latest('created_at')
                ->limit(10),
        ]);

        return response()->json($listing);
    }

    private function normalizedFilters(Request $request): array
    {
        $category = $request->query('category');

        return [
            'q' => $request->query('q'),
            'city' => $request->query('city'),
            'country' => $request->query('country'),
            'category' => is_string($category) ? strtoupper($category) : $category,
            'min_guests' => $request->query('minGuests', $request->query('min_guests')),
            'max_guests' => $request->query('maxGuests', $request->query('max_guests')),
            'min_price' => $request->query('minPrice', $request->query('min_price')),
            'max_price' => $request->query('maxPrice', $request->query('max_price')),
            'latitude' => $request->query('latitude'),
            'longitude' => $request->query('longitude'),
            'radius_km' => $request->query('radiusKm', $request->query('radius_km')),
            'page' => $request->query('page', 1),
            'limit' => $request->query('limit', 20),
        ];
    }

    private function applyTextAndRangeFilters(Builder $query, array $filters): void
    {
        if (! empty($filters['q'])) {
            $term = '%'.mb_strtolower($filters['q']).'%';
            $query->where(function (Builder $query) use ($term): void {
                $query->whereRaw('LOWER(title) LIKE ?', [$term])
                    ->orWhereRaw('LOWER(description) LIKE ?', [$term])
                    ->orWhereRaw('LOWER(city) LIKE ?', [$term])
                    ->orWhereRaw('LOWER(address) LIKE ?', [$term]);
            });
        }

        foreach (['city', 'country'] as $field) {
            if (! empty($filters[$field])) {
                $query->whereRaw('LOWER('.$field.') LIKE ?', ['%'.mb_strtolower($filters[$field]).'%']);
            }
        }

        $query->when($filters['category'] ?? null, fn (Builder $q, string $value) => $q->where('category', $value));
        $query->when($filters['min_guests'] ?? null, fn (Builder $q, int $value) => $q->where('max_guests', '>=', $value));
        $query->when($filters['max_guests'] ?? null, fn (Builder $q, int $value) => $q->where('max_guests', '<=', $value));
        $query->when(isset($filters['min_price']), fn (Builder $q) => $q->where('base_price_usd', '>=', $filters['min_price']));
        $query->when(isset($filters['max_price']), fn (Builder $q) => $q->where('base_price_usd', '<=', $filters['max_price']));
    }

    private function applyLocationFilter(Builder $query, array $filters): bool
    {
        if (! isset($filters['latitude'], $filters['longitude'], $filters['radius_km'])) {
            return false;
        }

        $latitude = (float) $filters['latitude'];
        $longitude = (float) $filters['longitude'];
        $radius = (float) $filters['radius_km'];

        if (DB::connection()->getDriverName() === 'pgsql') {
            $point = 'ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography';
            $query->select('listings.*')
                ->selectRaw("ST_Distance(location, {$point}) / 1000 AS distance_km", [$longitude, $latitude])
                ->whereRaw("ST_DWithin(location, {$point}, ?)", [$longitude, $latitude, $radius * 1000])
                ->orderBy('distance_km');
        } else {
            $latitudeDelta = $radius / 111.045;
            $longitudeDelta = $radius / max(111.045 * cos(deg2rad($latitude)), 0.01);
            $query->whereBetween('latitude', [$latitude - $latitudeDelta, $latitude + $latitudeDelta])
                ->whereBetween('longitude', [$longitude - $longitudeDelta, $longitude + $longitudeDelta]);
        }

        return true;
    }
}