<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\LeaderboardAward;
use App\Models\Notification;
use App\Models\SiteSetting;
use App\Models\User;
use App\Support\LeaderboardRanking;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class LeaderboardController extends Controller
{
    public function index(Request $request)
    {
        $ranking = app(LeaderboardRanking::class);
        $period = $ranking->normalizePeriod($request->query('period', site_setting('leaderboard_period', 'monthly')));
        $limit = max(25, min(50, (int) $request->query('limit', $ranking->limitFor($period))));

        $players = $ranking->players($period, $limit);
        $prizes = $this->prizeConfig();
        $periodLabel = $this->periodLabel($period);

        $alreadyAwarded = LeaderboardAward::where('period_label', $periodLabel)
            ->pluck('user_id')
            ->all();

        $recentAwards = LeaderboardAward::with('user')->latest()->take(20)->get();

        return view('admin.leaderboard', compact(
            'players', 'prizes', 'period', 'periodLabel', 'limit', 'alreadyAwarded', 'recentAwards'
        ));
    }

    public function savePrizes(Request $request)
    {
        $data = $request->validate([
            'prizes' => ['required', 'array'],
            'prizes.*' => ['nullable', 'numeric', 'min:0', 'max:100000000'],
        ]);

        $prizes = collect($data['prizes'])
            ->map(fn ($value) => (float) $value)
            ->filter(fn ($value) => $value > 0)
            ->all();

        SiteSetting::put('leaderboard_prizes', json_encode($prizes));
        Cache::flush();

        return redirect()->route('admin.leaderboard', $request->only('period', 'limit'))
            ->with('success', 'Leaderboard prizes saved.');
    }

    public function distribute(Request $request)
    {
        $period = $request->input('period', site_setting('leaderboard_period', 'monthly'));
        $ranking = app(LeaderboardRanking::class);
        $period = $ranking->normalizePeriod($period);
        $limit = max(25, min(50, (int) $request->input('limit', $ranking->limitFor($period))));
        $periodLabel = $this->periodLabel($period);

        $players = $ranking->players($period, $limit);
        $prizes = $this->prizeConfig();

        if (empty($prizes)) {
            return redirect()->back()->with('error', 'Configure at least one rank prize before distributing.');
        }

        $awarded = 0;

        DB::transaction(function () use ($players, $prizes, $periodLabel, &$awarded) {
            foreach ($players as $player) {
                $rank = $player->leaderboard_rank;
                $prize = $prizes[$rank] ?? null;

                if (!$prize || $prize <= 0) {
                    continue;
                }

                // One prize per user per period.
                $exists = LeaderboardAward::where('period_label', $periodLabel)
                    ->where('user_id', $player->id)
                    ->exists();

                if ($exists) {
                    continue;
                }

                $user = User::find($player->id);
                if (!$user) {
                    continue;
                }

                $user->addPoints((int) round($prize));

                LeaderboardAward::create([
                    'user_id' => $user->id,
                    'rank' => $rank,
                    'points' => $prize,
                    'period_label' => $periodLabel,
                    'awarded_by' => auth()->id(),
                ]);

                Notification::create([
                    'user_id' => $user->id,
                    'title' => 'Leaderboard prize awarded!',
                    'message' => "You placed #{$rank} on the {$periodLabel} leaderboard and earned ".number_format($prize)." coins.",
                ]);

                $awarded++;
            }
        });

        return redirect()->route('admin.leaderboard', ['period' => $period, 'limit' => $limit])
            ->with('success', $awarded > 0
                ? "Distributed prizes to {$awarded} player(s)."
                : 'No new prizes distributed (already awarded for this period, or no matching rank prizes).');
    }

    private function periodLabel(string $period): string
    {
        return match ($period) {
            'daily' => 'daily-'.now()->format('Y-m-d'),
            'weekly' => 'weekly-'.now()->format('o-\WW'),
            'all_time' => 'all_time-'.now()->format('Y-m-d'),
            default => 'monthly-'.now()->format('Y-m'),
        };
    }

    /**
     * @return array<int,float>  rank => prize points
     */
    private function prizeConfig(): array
    {
        $raw = site_setting('leaderboard_prizes', '');
        $decoded = json_decode((string) $raw, true);

        if (!is_array($decoded)) {
            return [];
        }

        return collect($decoded)
            ->mapWithKeys(fn ($value, $rank) => [(int) $rank => (float) $value])
            ->all();
    }
}
