<?php

namespace App\Http\Controllers;

use App\Enums\HuntDifficulty;
use App\Enums\ParticipateStatus;
use App\Http\Resources\HuntResource;
use App\Models\Hunts;
use App\Models\HuntSteps;
use App\Notifications\HuntParticipantJoinedNotification;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;

class HuntsController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        try {
            $searchTerm = $this->resolveSearchTerm($request);
            $filters = $this->buildMeilisearchFilters($request);

            $search = Hunts::search($searchTerm ?? '')
                ->query(fn ($query) => $query
                    ->with('participants:id,avatar_uri')
                    ->withSum('steps', 'xp')
                )
                ->options(['filter' => $filters]);

            $result = $search->paginate(10);
            $this->appendListMetadataToHunts($result->getCollection(), $request->user()?->id);

            Log::info('HUNTS_SEARCH_SUCCESS', [
                'total' => $result->total(),
                'count' => count($result->items()),
            ]);

            return response()->json($result);
        } catch (\Throwable $e) {

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function show(Hunts $hunt): JsonResponse
    {
        try {
            // Charger les relations nécessaires
            $hunt->load([
                'participants:id,username,avatar_uri,xp,level_id',
                'participants.currentLevel',
                'steps',
            ]);

            Log::info('HUNT_SHOW_SUCCESS', [
                'hunt_id' => $hunt->id,
                'hunt_title' => $hunt->title,
            ]);

            return HuntResource::make($hunt)->response();
        } catch (\Throwable $e) {
            Log::error('HUNT_SHOW_ERROR', [
                'hunt_id' => $hunt->id ?? null,
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function store(Request $request): JsonResponse
    {
        try {
            $validated = $request->validate([
                'title' => 'required|string|max:255',
                'description' => 'nullable|string',
                'city' => 'required|string|max:255',
                'difficulty' => 'required|string',
                'latitude' => 'nullable|numeric',
                'longitude' => 'nullable|numeric',
                'start_radius_m' => 'nullable|integer|min:1',
                'is_public' => 'boolean',
                'estimated_duration_min' => 'nullable|integer',
                'start_at' => 'nullable|date',
                'end_at' => 'nullable|date|after:start_at',
                'image' => 'nullable|string',
            ]);

            $validated['creator_id'] = auth('sanctum')->id();
            $validated['slug'] = \Illuminate\Support\Str::slug($validated['title']);

            // Ensure slug is unique
            $originalSlug = $validated['slug'];
            $counter = 1;
            while (Hunts::where('slug', $validated['slug'])->exists()) {
                $validated['slug'] = $originalSlug.'-'.$counter;
                $counter++;
            }

            $hunt = Hunts::create($validated);

            Log::info('HUNT_CREATED', [
                'hunt_id' => $hunt->id,
                'creator_id' => $validated['creator_id'],
            ]);

            return response()->json($hunt, 201);
        } catch (\Illuminate\Validation\ValidationException $e) {
            return response()->json([
                'error' => 'Validation Error',
                'messages' => $e->errors(),
            ], 422);
        } catch (\Throwable $e) {
            Log::error('HUNT_STORE_ERROR', [
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function update(Request $request, Hunts $hunt): JsonResponse
    {
        try {
            // Vérifier que l'utilisateur est le créateur ou admin
            $user = auth('sanctum')->user();
            if ($hunt->creator_id !== $user->id && ! $user->isAdmin()) {
                return response()->json([
                    'error' => 'Forbidden',
                    'message' => 'You are not authorized to update this hunt',
                ], 403);
            }

            $validated = $request->validate([
                'title' => 'sometimes|string|max:255',
                'description' => 'nullable|string',
                'city' => 'sometimes|string|max:255',
                'difficulty' => 'sometimes|string',
                'latitude' => 'nullable|numeric',
                'longitude' => 'nullable|numeric',
                'start_radius_m' => 'nullable|integer|min:1',
                'is_public' => 'boolean',
                'estimated_duration_min' => 'nullable|integer',
                'start_at' => 'nullable|date',
                'end_at' => 'nullable|date|after:start_at',
                'image' => 'nullable|string',
            ]);

            // Update slug if title changed
            if (isset($validated['title']) && $validated['title'] !== $hunt->title) {
                $validated['slug'] = \Illuminate\Support\Str::slug($validated['title']);

                // Ensure slug is unique
                $originalSlug = $validated['slug'];
                $counter = 1;
                while (Hunts::where('slug', $validated['slug'])->where('id', '!=', $hunt->id)->exists()) {
                    $validated['slug'] = $originalSlug.'-'.$counter;
                    $counter++;
                }
            }

            $hunt->update($validated);

            Log::info('HUNT_UPDATED', [
                'hunt_id' => $hunt->id,
                'updated_by' => $user->id,
            ]);

            return response()->json($hunt);
        } catch (\Illuminate\Validation\ValidationException $e) {
            return response()->json([
                'error' => 'Validation Error',
                'messages' => $e->errors(),
            ], 422);
        } catch (\Throwable $e) {
            Log::error('HUNT_UPDATE_ERROR', [
                'hunt_id' => $hunt->id ?? null,
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function destroy(Hunts $hunt): JsonResponse
    {
        try {
            // Vérifier que l'utilisateur est le créateur ou admin
            $user = auth('sanctum')->user();
            if ($hunt->creator_id !== $user->id && ! $user->isAdmin()) {
                return response()->json([
                    'error' => 'Forbidden',
                    'message' => 'You are not authorized to delete this hunt',
                ], 403);
            }

            $huntId = $hunt->id;
            $hunt->delete();

            Log::info('HUNT_DELETED', [
                'hunt_id' => $huntId,
                'deleted_by' => $user->id,
            ]);

            return response()->json([
                'message' => 'Hunt deleted successfully',
            ]);
        } catch (\Throwable $e) {
            Log::error('HUNT_DELETE_ERROR', [
                'hunt_id' => $hunt->id ?? null,
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function participate(Hunts $hunt): JsonResponse
    {
        try {
            $user = auth('sanctum')->user();

            // Vérifier si l'utilisateur participe déjà
            if ($hunt->participants()->where('user_id', $user->id)->exists()) {
                return response()->json([
                    'error' => 'Already Participating',
                    'message' => 'You are already participating in this hunt',
                ], 409);
            }

            // Ajouter l'utilisateur comme participant
            $hunt->participants()->attach($user->id, [
                'current_step_number' => 0,
                'status' => ParticipateStatus::Registered->value,
            ]);

            $this->refreshSearchIndex($hunt);

            $hunt->loadMissing('creator');

            if ($hunt->creator !== null && $hunt->creator->getKey() !== $user->getKey()) {
                $hunt->creator->notify(new HuntParticipantJoinedNotification($hunt, $user));
            }

            Log::info('HUNT_PARTICIPATE', [
                'hunt_id' => $hunt->id,
                'user_id' => $user->id,
            ]);

            return response()->json([
                'message' => 'Successfully joined the hunt',
                'participation_status' => [
                    'user_id' => $user->id,
                    'hunt_id' => $hunt->id,
                    'current_step_number' => 0,
                    'status' => ParticipateStatus::Registered->value,
                ],
                'hunt' => $hunt->load('participants:id,username,avatar_uri'),
            ]);
        } catch (\Throwable $e) {
            Log::error('HUNT_PARTICIPATE_ERROR', [
                'hunt_id' => $hunt->id ?? null,
                'user_id' => auth('sanctum')->id(),
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function start(Request $request, Hunts $hunt): JsonResponse
    {
        $validated = $request->validate([
            'latitude' => 'required|numeric',
            'longitude' => 'required|numeric',
        ]);

        $user = $request->user();
        $participation = $user->participations()->where('hunt_id', $hunt->id)->first();

        if (! $participation) {
            return response()->json(['message' => 'Not participating in this hunt'], 403);
        }

        if ($participation->pivot->status === ParticipateStatus::Completed->value) {
            return response()->json([
                'completed' => true,
                'participation_status' => $this->participationPayloadFromPivot($participation->pivot),
                'message' => 'Hunt already completed',
            ]);
        }

        if (
            $participation->pivot->status === ParticipateStatus::InProgress->value
            && (int) $participation->pivot->current_step_number > 0
        ) {
            $currentStep = $hunt->steps()
                ->where('step_number', (int) $participation->pivot->current_step_number)
                ->first();

            return response()->json([
                'message' => 'Hunt already started',
                'participation_status' => $this->participationPayloadFromPivot($participation->pivot),
                'current_step' => $currentStep ? $this->stepTargetPayload($currentStep) : null,
            ]);
        }

        if ($hunt->latitude === null || $hunt->longitude === null) {
            return response()->json(['message' => 'Hunt start location is not configured'], 422);
        }

        $distance = $this->distanceMeters(
            (float) $validated['latitude'],
            (float) $validated['longitude'],
            (float) $hunt->latitude,
            (float) $hunt->longitude,
        );
        $radius = $hunt->start_radius_m ?? 50;

        if ($distance > $radius) {
            return response()->json([
                'success' => false,
                'message' => 'You are too far from the hunt start.',
                'distance_m' => round($distance),
                'radius_m' => $radius,
            ], 400);
        }

        $firstStep = $hunt->steps()->orderBy('step_number')->first();

        if (! $firstStep) {
            return response()->json(['message' => 'Hunt has no steps'], 409);
        }

        $hunt->participants()->updateExistingPivot($user->id, [
            'current_step_number' => $firstStep->step_number,
            'status' => ParticipateStatus::InProgress->value,
        ]);

        $participation->pivot->current_step_number = $firstStep->step_number;
        $participation->pivot->status = ParticipateStatus::InProgress->value;

        return response()->json([
            'message' => 'Hunt started',
            'participation_status' => $this->participationPayloadFromPivot($participation->pivot),
            'current_step' => $this->stepTargetPayload($firstStep),
        ]);
    }

    public function currentStep(Request $request, Hunts $hunt): JsonResponse
    {
        $user = $request->user();
        $participation = $user->participations()->where('hunt_id', $hunt->id)->first();

        if (! $participation) {
            return response()->json(['message' => 'Not participating in this hunt'], 403);
        }

        if ($participation->pivot->status === ParticipateStatus::Completed->value) {
            return response()->json([
                'completed' => true,
                'participation_status' => $this->participationPayloadFromPivot($participation->pivot),
            ]);
        }

        if (
            $participation->pivot->status !== ParticipateStatus::InProgress->value
            || (int) $participation->pivot->current_step_number < 1
        ) {
            return response()->json([
                'message' => 'Hunt is joined but not started',
                'participation_status' => $this->participationPayloadFromPivot($participation->pivot),
            ], 409);
        }

        $currentStep = $hunt->steps()
            ->where('step_number', (int) $participation->pivot->current_step_number)
            ->first();

        if (! $currentStep) {
            return response()->json(['message' => 'Current step not found'], 404);
        }

        return response()->json([
            'participation_status' => $this->participationPayloadFromPivot($participation->pivot),
            'current_step' => $this->stepTargetPayload($currentStep),
        ]);
    }

    public function unparticipate(Hunts $hunt): JsonResponse
    {
        try {
            $user = auth('sanctum')->user();

            // Vérifier si l'utilisateur participe
            if (! $hunt->participants()->where('user_id', $user->id)->exists()) {
                return response()->json([
                    'error' => 'Not Participating',
                    'message' => 'You are not participating in this hunt',
                ], 404);
            }

            // Retirer l'utilisateur des participants
            $hunt->participants()->detach($user->id);
            $this->refreshSearchIndex($hunt);

            Log::info('HUNT_UNPARTICIPATE', [
                'hunt_id' => $hunt->id,
                'user_id' => $user->id,
            ]);

            return response()->json([
                'message' => 'Successfully left the hunt',
            ]);
        } catch (\Throwable $e) {
            Log::error('HUNT_UNPARTICIPATE_ERROR', [
                'hunt_id' => $hunt->id ?? null,
                'user_id' => auth('sanctum')->id(),
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'error' => 'Server Error',
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    private function buildMeilisearchFilters(Request $request): string
    {
        $filters = [];

        // participating
        if ($request->has('participating')) {
            $participating = filter_var(
                $request->query('participating'),
                FILTER_VALIDATE_BOOLEAN,
                FILTER_NULL_ON_FAILURE
            );

            $userId = auth('sanctum')->id(); // chez toi => UUID string

            if ($userId) {
                $escapedUserId = $this->escapeMeiliString((string) $userId);

                if ($participating === true) {
                    // ✅ UUID => string
                    $filters[] = "participant_ids = \"{$escapedUserId}\"";
                } elseif ($participating === false) {
                    $filters[] = "participant_ids != \"{$escapedUserId}\"";
                }
            } elseif ($participating === true) {
                $filters[] = 'id < 0';
            }
        }

        // active
        if ($request->has('active')) {
            $now = Carbon::now('UTC')->timestamp;
            $active = filter_var($request->query('active'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

            if ($active === true) {
                // Hunt actif = démarré ET (pas terminé OU pas de date de fin)
                $filters[] = "start_at_timestamp <= {$now}";
                // Meilisearch : la syntaxe est 'field EXISTS' et non 'EXISTS field'
                // Un hunt est actif si end_at_timestamp n'existe pas OU >= now
                $filters[] = "(end_at_timestamp >= {$now} OR NOT end_at_timestamp EXISTS)";
            } elseif ($active === false) {
                // Hunt non actif = pas encore commencé OU (déjà terminé ET a une date de fin)
                // Si pas de end_at_timestamp, le hunt ne peut pas être terminé
                $filters[] = "(start_at_timestamp > {$now} OR (end_at_timestamp < {$now} AND end_at_timestamp EXISTS))";
            }
        }

        // city
        if ($request->filled('city')) {
            $city = $request->query('city');
            if (is_string($city)) {
                $city = $this->escapeMeiliString($city);
                $filters[] = "city = \"{$city}\"";
            }
        }

        // difficulty
        if ($request->filled('difficulty')) {
            $difficulties = $request->query('difficulty');

            if (is_string($difficulties)) {
                $difficulties = explode(',', $difficulties);
            }

            if (is_array($difficulties)) {
                $validDifficulties = array_values(array_intersect(
                    HuntDifficulty::values(),
                    array_map('strtolower', $difficulties)
                ));

                if (! empty($validDifficulties)) {
                    $difficultyFilters = array_map(
                        fn ($d) => 'difficulty = "'.$this->escapeMeiliString($d).'"',
                        $validDifficulties
                    );
                    $filters[] = '('.implode(' OR ', $difficultyFilters).')';
                }
            }
        }

        // dates (UTC)
        $startFrom = $this->parseDateFilterUtc($request->query('start_from'));
        $startTo = $this->parseDateFilterUtc($request->query('start_to'));
        $endFrom = $this->parseDateFilterUtc($request->query('end_from'));
        $endTo = $this->parseDateFilterUtc($request->query('end_to'));

        if ($startFrom) {
            // start_at_timestamp est toujours présent (created_at par défaut), donc pas besoin de EXISTS
            $filters[] = "start_at_timestamp >= {$startFrom->timestamp}";
        }
        if ($startTo) {
            // start_at_timestamp est toujours présent
            $filters[] = "start_at_timestamp <= {$startTo->timestamp}";
        }
        if ($endFrom) {
            // end_at_timestamp peut être null, donc on vérifie qu'il existe avant de comparer
            $filters[] = "(end_at_timestamp EXISTS AND end_at_timestamp >= {$endFrom->timestamp})";
        }
        if ($endTo) {
            // end_at_timestamp peut être null, donc on vérifie qu'il existe avant de comparer
            $filters[] = "(end_at_timestamp EXISTS AND end_at_timestamp <= {$endTo->timestamp})";
        }

        // Filtre par défaut : si aucun filtre n'est spécifié, afficher uniquement les hunts publics
        // Cela évite l'erreur Meilisearch avec une chaîne de filtres vide
        if (empty($filters)) {
            // En Meilisearch, les booléens peuvent être comparés avec true/false (sans guillemets)
            $filters[] = 'is_public = true';
        }

        $finalFilters = implode(' AND ', $filters);

        Log::info('BUILD_FILTERS_COMPLETE', [
            'final_filters' => $finalFilters,
        ]);

        return $finalFilters;
    }

    private function resolveSearchTerm(Request $request): ?string
    {
        $searchTerm = $request->query('search') ?? $request->query('q');

        if (! is_string($searchTerm)) {
            return null;
        }

        $searchTerm = trim($searchTerm);

        return $searchTerm !== '' ? $searchTerm : null;
    }

    /**
     * @param  Collection<int, Hunts>  $hunts
     */
    private function appendListMetadataToHunts(Collection $hunts, string|int|null $userId): void
    {
        $hunts->each(function (Hunts $hunt) use ($userId): void {
            $hunt->setAttribute('xp', $hunt->totalXp());
            $hunt->setAttribute('is_participating', false);
            $hunt->setAttribute('participation_status', null);
            $hunt->makeHidden('steps_sum_xp');

            if ($userId === null || ! $hunt->relationLoaded('participants')) {
                return;
            }

            $participant = $hunt->participants->firstWhere('id', $userId);

            if ($participant === null || $participant->pivot === null) {
                return;
            }

            $hunt->setAttribute('is_participating', true);
            $hunt->setAttribute('participation_status', $this->participationPayloadFromPivot($participant->pivot));
        });
    }

    /**
     * @return array<string, mixed>
     */
    private function stepTargetPayload(HuntSteps $step): array
    {
        return [
            'id' => $step->id,
            'hunt_id' => $step->hunt_id,
            'step_number' => $step->step_number,
            'title' => $step->title,
            'latitude' => $step->latitude,
            'longitude' => $step->longitude,
            'radius_m' => $step->radius_m,
            'ar_model' => 'chest',
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function participationPayloadFromPivot(mixed $pivot): array
    {
        return [
            'user_id' => $pivot->user_id,
            'hunt_id' => $pivot->hunt_id,
            'current_step_number' => (int) $pivot->current_step_number,
            'status' => $pivot->status,
            'created_at' => $pivot->created_at?->toJSON(),
            'updated_at' => $pivot->updated_at?->toJSON(),
        ];
    }

    private function distanceMeters(float $latitudeFrom, float $longitudeFrom, float $latitudeTo, float $longitudeTo): float
    {
        $earthRadius = 6371000;

        $latFrom = deg2rad($latitudeFrom);
        $lonFrom = deg2rad($longitudeFrom);
        $latTo = deg2rad($latitudeTo);
        $lonTo = deg2rad($longitudeTo);

        $latDelta = $latTo - $latFrom;
        $lonDelta = $lonTo - $lonFrom;

        $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) +
            cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));

        return $angle * $earthRadius;
    }

    private function refreshSearchIndex(Hunts $hunt): void
    {
        try {
            $hunt->searchable();
        } catch (\Throwable $e) {
            Log::warning('HUNT_SEARCH_INDEX_REFRESH_FAILED', [
                'hunt_id' => $hunt->id,
                'message' => $e->getMessage(),
            ]);
        }
    }

    private function parseDateFilterUtc(mixed $value): ?Carbon
    {
        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        try {
            return Carbon::parse(trim($value))->utc();
        } catch (\Throwable $e) {
            return null;
        }
    }

    private function escapeMeiliString(string $value): string
    {
        // Échappe les backslashes et les guillemets pour une string Meilisearch
        $value = str_replace('\\', '\\\\', $value);

        return str_replace('"', '\"', $value);
    }
}
