<?php

namespace App\Http\Controllers;

use App\Models\Round;
use App\Models\RoundParticipant;
use App\Services\PlatformMetricsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;

class GameController extends Controller
{
    public function index(Request $request, PlatformMetricsService $metricsService)
    {
        $player = $request->user();
        $snapshot = $metricsService->snapshot($player?->city, $player?->state);
        $pixCheckout = $this->buildPixCheckout(
            (float) ($snapshot['lobby']['entry_price'] ?? 5),
            $player?->city,
            $player?->state
        );

        return view('game', [
            'lobby' => $snapshot['lobby'],
            'player' => $player,
            'pixCheckout' => $pixCheckout,
            'selectedAvatarId' => $this->normalizeAvatarId($player?->avatar_id),
            'arena' => $this->buildArenaData($player, $snapshot['lobby'], false),
        ]);
    }

    public function live(Request $request, PlatformMetricsService $metricsService): JsonResponse
    {
        $snapshot = $metricsService->snapshot($request->user()?->city, $request->user()?->state);

        return response()->json($snapshot['lobby']);
    }

    public function arenaLive(Request $request, PlatformMetricsService $metricsService): JsonResponse
    {
        $user = $request->user();

        if (! $user) {
            return response()->json([
                'ok' => false,
                'message' => 'Usuário não autenticado.',
            ], 401);
        }

        $snapshot = $metricsService->snapshot($user->city, $user->state);

        return response()->json([
            'ok' => true,
            'arena' => $this->buildArenaData($user, $snapshot['lobby'], true),
        ]);
    }

    public function guess(Request $request)
    {
        $data = $request->validate([
            'guess' => ['required', 'integer', 'min:1', 'max:100'],
        ]);

        $this->bootstrapGame($request);

        $guess = (int) $data['guess'];
        $secret = (int) $request->session()->get('game.secret');
        $attempts = (int) $request->session()->get('game.attempts', 0) + 1;
        $request->session()->put('game.attempts', $attempts);

        if ($guess === $secret) {
            $best = $request->session()->get('game.best');

            if ($best === null || $attempts < (int) $best) {
                $request->session()->put('game.best', $attempts);
            }

            $request->session()->put('game.secret', random_int(1, 100));
            $request->session()->put('game.attempts', 0);
            $request->session()->forget('game.last_hint');

            return redirect()
                ->route('game.index')
                ->with('status', "Acertou em {$attempts} tentativa(s)! Novo número gerado.");
        }

        $hint = $guess < $secret ? 'maior' : 'menor';
        $request->session()->put('game.last_hint', $hint);

        return redirect()
            ->route('game.index')
            ->with('status', "Ainda não foi. Dica: o número secreto é {$hint} que {$guess}.");
    }

    public function reset(Request $request)
    {
        $request->session()->put('game.secret', random_int(1, 100));
        $request->session()->put('game.attempts', 0);
        $request->session()->forget('game.last_hint');

        return redirect()
            ->route('game.index')
            ->with('status', 'Partida reiniciada.');
    }

    public function saveAvatar(Request $request): JsonResponse
    {
        if (! Schema::hasColumn('users', 'avatar_id')) {
            return response()->json([
                'ok' => false,
                'message' => 'Campo de avatar indisponível. Rode as migrações e tente novamente.',
            ], 503);
        }

        $data = $request->validate([
            'avatar_id' => ['required', 'string', Rule::in($this->supportedAvatarIds())],
        ]);

        $user = $request->user();

        if (! $user) {
            return response()->json([
                'ok' => false,
                'message' => 'Usuário não autenticado.',
            ], 401);
        }

        $user->avatar_id = $data['avatar_id'];
        $user->save();

        $snapshot = app(PlatformMetricsService::class)->snapshot($user->city, $user->state);
        $arena = $this->buildArenaData($user, $snapshot['lobby'], false);

        return response()->json([
            'ok' => true,
            'avatar_id' => $user->avatar_id,
            'message' => 'Avatar salvo com sucesso.',
            'arena' => $arena,
        ]);
    }

    private function bootstrapGame(Request $request): void
    {
        if (! $request->session()->has('game.secret')) {
            $request->session()->put('game.secret', random_int(1, 100));
            $request->session()->put('game.attempts', 0);
            $request->session()->put('game.last_hint', null);
        }
    }

    private function buildPixCheckout(float $entryPrice, ?string $city, ?string $state): array
    {
        $settings = [
            'enabled' => false,
            'pix_key' => '',
            'pix_key_holder' => 'SURVIVE JOGO',
        ];

        if (Schema::hasTable('payment_pix_settings')) {
            $row = DB::table('payment_pix_settings')->orderBy('id')->first();

            if ($row) {
                $settings = [
                    'enabled' => (bool) $row->pix_enabled,
                    'pix_key' => (string) ($row->pix_key ?? ''),
                    'pix_key_holder' => (string) ($row->pix_key_holder ?? 'SURVIVE JOGO'),
                ];
            }
        }

        $holder = trim($settings['pix_key_holder']) !== '' ? trim($settings['pix_key_holder']) : 'SURVIVE JOGO';
        $location = trim((string) ($city ?? ''));

        if ($location === '') {
            $location = trim((string) ($state ?? 'RECIFE'));
        }

        if ($location === '') {
            $location = 'RECIFE';
        }

        return [
            'status' => 'Aguardando pagamento',
            'enabled' => (bool) $settings['enabled'],
            'pix_key' => trim($settings['pix_key']),
            'pix_key_holder' => $holder,
            'amount' => round(max(0.01, $entryPrice), 2),
            'location' => $location,
        ];
    }

    private function buildArenaData($player, array $lobby, bool $advanceRound = false): array
    {
        $gridSize = 64;
        $stageTotal = 10;
        $supportedAvatars = $this->supportedAvatarIds();
        $selfAvatar = $this->normalizeAvatarId($player?->avatar_id) ?? $supportedAvatars[0];

        if (! $player || ! Schema::hasTable('rounds') || ! Schema::hasTable('round_participants')) {
            $players = [];

            for ($slot = 1; $slot <= $gridSize; $slot++) {
                $avatarId = $supportedAvatars[($slot - 1) % count($supportedAvatars)];
                $players[] = [
                    'slot_number' => $slot,
                    'user_id' => null,
                    'name' => 'Jogador',
                    'avatar_id' => $avatarId,
                    'is_self' => false,
                    'is_alive' => true,
                    'is_bot' => true,
                ];
            }

            return [
                'round_id' => null,
                'round_status' => 'open',
                'round_label' => 'Mapa - Rodada #1',
                'round_stage' => 1,
                'round_stage_total' => $stageTotal,
                'remaining_players' => (int) ($lobby['players_online'] ?? $gridSize),
                'grid_size' => $gridSize,
                'current_user_id' => $player?->id ? (int) $player->id : null,
                'winner_user_id' => null,
                'winner_avatar_id' => null,
                'self_avatar_id' => $selfAvatar,
                'players' => $players,
            ];
        }

        $city = $player?->city ? trim((string) $player->city) : null;
        $state = $player?->state ? strtoupper(trim((string) $player->state)) : null;
        $entryPrice = (float) ($lobby['entry_price'] ?? 5);
        $estimatedPrize = (float) ($lobby['estimated_prize'] ?? 0);

        $round = $this->resolveOpenRound($city, $state, $entryPrice, $estimatedPrize, $stageTotal);
        $this->registerPlayerInRound($round, $player, $selfAvatar);
        $this->seedRoundParticipants($round, $gridSize);

        if ($advanceRound) {
            $this->advanceRoundIfNeeded($round);
            $round->refresh();
        }

        $players = $this->getRoundParticipantsSnapshot($round, $player, $gridSize);
        $remainingPlayers = count(array_filter($players, static fn (array $row) => (bool) ($row['is_alive'] ?? false)));

        if ($remainingPlayers < 1) {
            $remainingPlayers = 1;
        }

        return [
            'round_id' => (int) $round->id,
            'round_status' => (string) $round->status,
            'round_label' => "Mapa - Rodada #{$round->id}",
            'round_stage' => max(1, (int) ($round->stage_current ?? 1)),
            'round_stage_total' => max(1, (int) ($round->stage_total ?? $stageTotal)),
            'remaining_players' => $remainingPlayers,
            'grid_size' => $gridSize,
            'current_user_id' => (int) $player->id,
            'winner_user_id' => $round->winner_user_id ? (int) $round->winner_user_id : null,
            'winner_avatar_id' => $this->normalizeAvatarId($round->winner_avatar_id),
            'self_avatar_id' => $selfAvatar,
            'players' => $players,
        ];
    }

    private function resolveOpenRound(?string $city, ?string $state, float $entryPrice, float $estimatedPrize, int $stageTotal): Round
    {
        $query = Round::query()
            ->where('status', 'open')
            ->orderBy('starts_at');

        $this->applyRoundLocationFilter($query, $city, $state);

        $existing = $query->first();

        if ($existing) {
            return $existing;
        }

        $data = [
            'status' => 'open',
            'starts_at' => now(),
            'entry_price' => max(0.01, $entryPrice),
            'total_entries' => 0,
            'prize_total' => max(0, $estimatedPrize),
            'stage_current' => 1,
            'stage_total' => max(1, $stageTotal),
            'last_elimination_at' => now(),
        ];

        if (Schema::hasColumn('rounds', 'city')) {
            $data['city'] = $city;
        }

        if (Schema::hasColumn('rounds', 'state')) {
            $data['state'] = $state;
        }

        return Round::create($data);
    }

    private function applyRoundLocationFilter($query, ?string $city, ?string $state): void
    {
        if ($city !== null && $city !== '' && Schema::hasColumn('rounds', 'city')) {
            $query->where('city', $city);
        }

        if ($state !== null && $state !== '' && Schema::hasColumn('rounds', 'state')) {
            $query->where('state', $state);
        }
    }

    private function registerPlayerInRound(Round $round, $player, string $avatarId): void
    {
        if (! $player || ! Schema::hasTable('round_participants')) {
            return;
        }

        $participant = RoundParticipant::query()
            ->where('round_id', $round->id)
            ->where('user_id', $player->id)
            ->first();

        if ($participant) {
            $participant->display_name = $this->firstName((string) ($player->name ?? 'Jogador'));
            $participant->avatar_id = $avatarId;
            $participant->is_bot = false;

            if (! $participant->slot_number) {
                $participant->slot_number = $this->findFirstFreeSlot((int) $round->id, 64) ?? 1;
            }

            if (! $participant->joined_at) {
                $participant->joined_at = now();
            }

            $participant->save();
            $this->updateRoundEntryCount($round);

            return;
        }

        RoundParticipant::create([
            'round_id' => $round->id,
            'user_id' => $player->id,
            'display_name' => $this->firstName((string) ($player->name ?? 'Jogador')),
            'avatar_id' => $avatarId,
            'slot_number' => $this->findFirstFreeSlot((int) $round->id, 64) ?? 1,
            'is_bot' => false,
            'is_alive' => true,
            'joined_at' => now(),
        ]);

        $this->updateRoundEntryCount($round);
    }

    private function seedRoundParticipants(Round $round, int $limit): void
    {
        if (! Schema::hasTable('round_participants') || ! Schema::hasTable('users') || ! Schema::hasTable('sessions')) {
            return;
        }

        $existingCount = (int) RoundParticipant::query()
            ->where('round_id', $round->id)
            ->count();

        if ($existingCount >= $limit) {
            return;
        }

        $knownUserIds = RoundParticipant::query()
            ->where('round_id', $round->id)
            ->whereNotNull('user_id')
            ->pluck('user_id')
            ->map(static fn ($value) => (int) $value)
            ->all();

        $city = Schema::hasColumn('rounds', 'city') ? ($round->city ? trim((string) $round->city) : null) : null;
        $state = Schema::hasColumn('rounds', 'state') ? ($round->state ? strtoupper(trim((string) $round->state)) : null) : null;

        $activeQuery = DB::table('sessions')
            ->join('users', 'users.id', '=', 'sessions.user_id')
            ->whereNotNull('sessions.user_id')
            ->where('sessions.last_activity', '>=', now()->subMinutes(5)->getTimestamp())
            ->select('users.id', 'users.name', 'users.avatar_id', DB::raw('MAX(sessions.last_activity) as last_activity'))
            ->groupBy('users.id', 'users.name', 'users.avatar_id')
            ->orderByDesc('last_activity')
            ->orderBy('users.id')
            ->limit($limit * 3);

        if ($city !== null && $city !== '' && Schema::hasColumn('users', 'city')) {
            $activeQuery->where('users.city', $city);
        }

        if ($state !== null && $state !== '' && Schema::hasColumn('users', 'state')) {
            $activeQuery->where('users.state', $state);
        }

        foreach ($activeQuery->get() as $row) {
            if ($existingCount >= $limit) {
                break;
            }

            $userId = (int) $row->id;

            if (in_array($userId, $knownUserIds, true)) {
                continue;
            }

            $slot = $this->findFirstFreeSlot((int) $round->id, $limit);

            if ($slot === null) {
                break;
            }

            RoundParticipant::create([
                'round_id' => $round->id,
                'user_id' => $userId,
                'display_name' => $this->firstName((string) ($row->name ?? 'Jogador')),
                'avatar_id' => $this->normalizeAvatarId($row->avatar_id) ?? $this->supportedAvatarIds()[($slot - 1) % count($this->supportedAvatarIds())],
                'slot_number' => $slot,
                'is_bot' => false,
                'is_alive' => true,
                'joined_at' => now(),
            ]);

            $knownUserIds[] = $userId;
            $existingCount++;
        }

        if ($existingCount < $limit) {
            $backupQuery = DB::table('users')
                ->select('id', 'name', 'avatar_id')
                ->orderBy('id')
                ->limit($limit * 5);

            if ($city !== null && $city !== '' && Schema::hasColumn('users', 'city')) {
                $backupQuery->where('city', $city);
            }

            if ($state !== null && $state !== '' && Schema::hasColumn('users', 'state')) {
                $backupQuery->where('state', $state);
            }

            if (! empty($knownUserIds)) {
                $backupQuery->whereNotIn('id', $knownUserIds);
            }

            foreach ($backupQuery->get() as $row) {
                if ($existingCount >= $limit) {
                    break;
                }

                $slot = $this->findFirstFreeSlot((int) $round->id, $limit);

                if ($slot === null) {
                    break;
                }

                RoundParticipant::create([
                    'round_id' => $round->id,
                    'user_id' => (int) $row->id,
                    'display_name' => $this->firstName((string) ($row->name ?? 'Jogador')),
                    'avatar_id' => $this->normalizeAvatarId($row->avatar_id) ?? $this->supportedAvatarIds()[($slot - 1) % count($this->supportedAvatarIds())],
                    'slot_number' => $slot,
                    'is_bot' => false,
                    'is_alive' => true,
                    'joined_at' => now(),
                ]);

                $existingCount++;
            }
        }

        if ($existingCount < $limit) {
            $this->fillRoundWithBots($round, $existingCount, $limit);
        }

        $this->updateRoundEntryCount($round);
    }

    private function fillRoundWithBots(Round $round, int $existingCount, int $limit): void
    {
        $avatars = $this->supportedAvatarIds();

        for ($index = $existingCount; $index < $limit; $index++) {
            $slot = $this->findFirstFreeSlot((int) $round->id, $limit);

            if ($slot === null) {
                break;
            }

            RoundParticipant::create([
                'round_id' => $round->id,
                'user_id' => null,
                'display_name' => 'Bot ' . str_pad((string) $slot, 2, '0', STR_PAD_LEFT),
                'avatar_id' => $avatars[$index % count($avatars)],
                'slot_number' => $slot,
                'is_bot' => true,
                'is_alive' => true,
                'joined_at' => now(),
            ]);
        }
    }

    private function updateRoundEntryCount(Round $round): void
    {
        $entries = (int) RoundParticipant::query()
            ->where('round_id', $round->id)
            ->count();

        if ((int) $round->total_entries !== $entries) {
            $round->total_entries = $entries;
            $round->save();
        }
    }

    private function findFirstFreeSlot(int $roundId, int $limit): ?int
    {
        $usedSlots = RoundParticipant::query()
            ->where('round_id', $roundId)
            ->whereNotNull('slot_number')
            ->pluck('slot_number')
            ->map(static fn ($value) => (int) $value)
            ->all();

        for ($slot = 1; $slot <= $limit; $slot++) {
            if (! in_array($slot, $usedSlots, true)) {
                return $slot;
            }
        }

        return null;
    }

    private function advanceRoundIfNeeded(Round $round): void
    {
        if (! Schema::hasTable('round_participants') || $round->status !== 'open') {
            return;
        }

        $now = now();
        $cooldownSeconds = 6;

        if ($round->last_elimination_at && $round->last_elimination_at->diffInSeconds($now) < $cooldownSeconds) {
            return;
        }

        $aliveQuery = RoundParticipant::query()
            ->where('round_id', $round->id)
            ->where('is_alive', true);

        $aliveCount = (int) $aliveQuery->count();

        if ($aliveCount <= 1) {
            $this->closeRoundWithWinner($round);
            return;
        }

        $stageCurrent = max(1, (int) ($round->stage_current ?? 1));
        $stageTotal = max($stageCurrent, (int) ($round->stage_total ?? 10));

        $minEliminate = max(1, (int) floor($aliveCount * 0.10));
        $maxEliminate = max($minEliminate, (int) ceil($aliveCount * 0.30));
        $eliminateCount = min($aliveCount - 1, random_int($minEliminate, $maxEliminate));

        if ($stageCurrent >= $stageTotal) {
            $eliminateCount = $aliveCount - 1;
        }

        $aliveIds = $aliveQuery->pluck('id')->all();
        shuffle($aliveIds);

        $eliminatedIds = array_slice($aliveIds, 0, $eliminateCount);

        if (! empty($eliminatedIds)) {
            RoundParticipant::query()
                ->whereIn('id', $eliminatedIds)
                ->update([
                    'is_alive' => false,
                    'eliminated_stage' => $stageCurrent,
                    'eliminated_at' => $now,
                    'updated_at' => $now,
                ]);
        }

        $aliveAfter = $aliveCount - count($eliminatedIds);

        $round->stage_current = min($stageTotal, $stageCurrent + 1);
        $round->last_elimination_at = $now;
        $round->save();

        if ($aliveAfter <= 1 || $round->stage_current >= $stageTotal) {
            $this->closeRoundWithWinner($round);
        }
    }

    private function closeRoundWithWinner(Round $round): void
    {
        $now = now();
        $stageCurrent = max(1, (int) ($round->stage_current ?? 1));
        $stageTotal = max($stageCurrent, (int) ($round->stage_total ?? 10));

        $alive = RoundParticipant::query()
            ->where('round_id', $round->id)
            ->where('is_alive', true)
            ->orderBy('slot_number')
            ->get();

        if ($alive->count() > 1) {
            $winnerOffset = random_int(0, $alive->count() - 1);
            $winnerId = (int) $alive[$winnerOffset]->id;

            RoundParticipant::query()
                ->where('round_id', $round->id)
                ->where('is_alive', true)
                ->where('id', '<>', $winnerId)
                ->update([
                    'is_alive' => false,
                    'eliminated_stage' => $stageCurrent,
                    'eliminated_at' => $now,
                    'updated_at' => $now,
                ]);

            $alive = RoundParticipant::query()
                ->where('round_id', $round->id)
                ->where('id', $winnerId)
                ->get();
        }

        if ($alive->isEmpty()) {
            $fallbackWinner = RoundParticipant::query()
                ->where('round_id', $round->id)
                ->orderBy('slot_number')
                ->orderBy('id')
                ->first();

            if ($fallbackWinner) {
                $fallbackWinner->is_alive = true;
                $fallbackWinner->eliminated_stage = null;
                $fallbackWinner->eliminated_at = null;
                $fallbackWinner->save();

                $alive = collect([$fallbackWinner]);
            }
        }

        $winner = $alive->first();

        $round->status = 'closed';
        $round->ends_at = $now;
        $round->stage_current = $stageTotal;
        $round->stage_total = $stageTotal;
        $round->last_elimination_at = $now;
        $round->winner_user_id = $winner?->user_id ? (int) $winner->user_id : null;
        $round->winner_avatar_id = $winner?->avatar_id ? (string) $winner->avatar_id : null;
        $round->save();
    }

    private function getRoundParticipantsSnapshot(Round $round, $player, int $limit): array
    {
        $avatars = $this->supportedAvatarIds();

        $participants = RoundParticipant::query()
            ->where('round_id', $round->id)
            ->orderByRaw('CASE WHEN slot_number IS NULL THEN 1 ELSE 0 END')
            ->orderBy('slot_number')
            ->orderBy('id')
            ->limit($limit)
            ->get();

        $bySlot = [];

        foreach ($participants as $row) {
            $slot = $row->slot_number ? (int) $row->slot_number : null;

            if ($slot === null || $slot < 1 || $slot > $limit || isset($bySlot[$slot])) {
                continue;
            }

            $avatarId = $this->normalizeAvatarId($row->avatar_id) ?? $avatars[($slot - 1) % count($avatars)];
            $displayName = $row->display_name ? trim((string) $row->display_name) : '';

            if ($displayName === '') {
                $displayName = $row->is_bot ? 'Bot' : 'Jogador';
            }

            $bySlot[$slot] = [
                'slot_number' => $slot,
                'user_id' => $row->user_id ? (int) $row->user_id : null,
                'name' => $displayName,
                'avatar_id' => $avatarId,
                'is_self' => $player?->id && $row->user_id && (int) $row->user_id === (int) $player->id,
                'is_alive' => (bool) $row->is_alive,
                'is_bot' => (bool) $row->is_bot,
            ];
        }

        $result = [];

        for ($slot = 1; $slot <= $limit; $slot++) {
            if (isset($bySlot[$slot])) {
                $result[] = $bySlot[$slot];
                continue;
            }

            $result[] = [
                'slot_number' => $slot,
                'user_id' => null,
                'name' => 'Bot',
                'avatar_id' => $avatars[($slot - 1) % count($avatars)],
                'is_self' => false,
                'is_alive' => true,
                'is_bot' => true,
            ];
        }

        return $result;
    }

    private function firstName(string $name): string
    {
        $trimmed = trim($name);

        if ($trimmed === '') {
            return 'Jogador';
        }

        $parts = preg_split('/\s+/', $trimmed);

        return $parts[0] ?? 'Jogador';
    }

    private function supportedAvatarIds(): array
    {
        return [
            'bombeira',
            'operario',
            'enfermeira',
            'medico',
            'ninja',
            'pirata',
            'astronauta',
            'vampiro',
            'fantasma',
            'rei',
        ];
    }

    private function normalizeAvatarId(?string $avatarId): ?string
    {
        if ($avatarId === null) {
            return null;
        }

        $value = trim($avatarId);

        return in_array($value, $this->supportedAvatarIds(), true) ? $value : null;
    }
}
