<?php

namespace App\Support;

use App\Models\Lead;
use App\Models\LeadAdjustment;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

final class LeaderboardRanking
{
    public const PERIODS = ['daily', 'weekly', 'monthly', 'all_time'];

    public function players(string $period, int $limit, ?Carbon $at = null): Collection
    {
        $period = $this->normalizePeriod($period);
        $limit = max(25, min(50, $limit));
        $from = $to = null;
        if ($period === 'all_time') {
            $earnings = Lead::query()
                ->selectRaw('user_id, credited_points as earned_points, created_at')
                ->whereNotNull('user_id')
                ->where('credited_points', '>', 0);
        } else {
            [$from, $to] = $this->window($period, $at);
            $earnings = LeadAdjustment::query()
                ->join('leads', 'leads.id', '=', 'lead_adjustments.lead_id')
                ->selectRaw('leads.user_id, lead_adjustments.points as earned_points, lead_adjustments.created_at')
                ->whereNotNull('leads.user_id')
                ->whereBetween('lead_adjustments.created_at', [$from, $to]);
        }

        $referrals = DB::table('referral_events')
            ->selectRaw('referrer_id as user_id, reward_points as earned_points, created_at')
            ->where('reward_points', '!=', 0)
            ->when($period !== 'all_time', fn ($query) => $query->whereBetween('created_at', [$from, $to]));

        $bonuses = DB::table('bonus_histories')
            ->join('bonuses', 'bonuses.id', '=', 'bonus_histories.bonus_id')
            ->selectRaw('bonus_histories.user_id, bonuses.points as earned_points, bonus_histories.created_at')
            ->when($period !== 'all_time', fn ($query) => $query->whereBetween('bonus_histories.created_at', [$from, $to]));

        $earningRows = $earnings->unionAll($referrals)->unionAll($bonuses);
        $totals = DB::query()
            ->fromSub($earningRows, 'leaderboard_earnings')
            ->selectRaw('user_id, SUM(earned_points) as leaderboard_points, COUNT(*) as period_leads_count')
            ->groupBy('user_id');

        return User::query()
            ->joinSub($totals, 'leaderboard_totals', fn ($join) => $join->on('leaderboard_totals.user_id', '=', 'users.id'))
            ->where('users.is_admin', false)
            ->where('leaderboard_totals.leaderboard_points', '>', 0)
            ->select('users.*')
            ->addSelect([
                'leaderboard_totals.leaderboard_points',
                'leaderboard_totals.period_leads_count as leads_count',
            ])
            ->with('latestLead')
            ->orderByDesc('leaderboard_totals.leaderboard_points')
            ->orderByDesc('leaderboard_totals.period_leads_count')
            ->orderBy('users.id')
            ->limit($limit)
            ->get()
            ->values()
            ->map(function (User $user, int $index) {
                $user->leaderboard_rank = $index + 1;
                $user->period_points = (float) $user->leaderboard_points;

                return $user;
            });
    }

    public function limitFor(string $period): int
    {
        $period = $this->normalizePeriod($period);

        return max(25, min(50, site_int("leaderboard_{$period}_limit", 25)));
    }

    public function normalizePeriod(string $period): string
    {
        return in_array($period, self::PERIODS, true) ? $period : 'monthly';
    }

    /** @return array{0:Carbon,1:Carbon} */
    public function window(string $period, ?Carbon $at = null): array
    {
        $at = ($at ?: now())->copy();

        return match ($this->normalizePeriod($period)) {
            'daily' => [$at->copy()->startOfDay(), $at->copy()->endOfDay()],
            'weekly' => [$at->copy()->startOfWeek(), $at->copy()->endOfWeek()],
            default => [$at->copy()->startOfMonth(), $at->copy()->endOfMonth()],
        };
    }
}
