<?php

namespace App\Http\Controllers;

use App\Models\Badge;
use App\Models\Challenge;
use App\Models\ChallengeParticipant;
use App\Models\Trade;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\View\View;

class ChallengeController extends Controller
{
    public function index(Request $request): View
    {
        $challenge = Challenge::query()->where('is_active', true)->latest()->first() ?? Challenge::defaultChallenge();
        $participant = ChallengeParticipant::query()
            ->where('challenge_id', $challenge->id)
            ->where('user_id', $request->user()->id)
            ->first();

        if ($participant) {
            $this->refreshParticipant($participant);
        }

        return view('challenges.index', [
            'challenge' => $challenge,
            'participant' => $participant?->fresh(),
            'badges' => $this->badges($participant),
        ]);
    }

    public function join(Request $request, Challenge $challenge): RedirectResponse
    {
        abort_unless($challenge->is_active, 404);

        ChallengeParticipant::query()->firstOrCreate(
            ['challenge_id' => $challenge->id, 'user_id' => $request->user()->id],
            ['joined_at' => now()->toDateString()]
        );

        return redirect()->route('challenge.index')->with('success', 'You joined the 30-Day Trading Discipline Challenge.');
    }

    private function refreshParticipant(ChallengeParticipant $participant): void
    {
        $start = $participant->joined_at->startOfDay();
        $end = $start->copy()->addDays($participant->challenge->duration_days - 1)->endOfDay();

        $trades = Trade::query()
            ->where('user_id', $participant->user_id)
            ->whereBetween('created_at', [$start, min(now(), $end)])
            ->get();

        $journaledDays = $trades
            ->map(fn (Trade $trade): ?string => ($trade->opened_at ?? $trade->created_at)?->toDateString())
            ->filter()
            ->unique()
            ->values();

        $daysCompleted = min($participant->challenge->duration_days, $journaledDays->count());
        $currentStreak = $this->currentStreak($journaledDays);
        $completion = $participant->challenge->duration_days > 0
            ? round(($daysCompleted / $participant->challenge->duration_days) * 100, 2)
            : 0;

        $participant->update([
            'days_completed' => $daysCompleted,
            'total_trades_journaled' => $trades->count(),
            'current_streak' => $currentStreak,
            'completion_percentage' => $completion,
            'badges_earned' => $this->earnedBadges($daysCompleted, $currentStreak, $trades),
        ]);
    }

    private function currentStreak($journaledDays): int
    {
        $days = collect($journaledDays)->map(fn (string $date): string => Carbon::parse($date)->toDateString())->flip();
        $streak = 0;

        for ($date = now()->startOfDay(); $streak < 30; $date->subDay()) {
            if (! $days->has($date->toDateString())) {
                break;
            }

            $streak++;
        }

        return $streak;
    }

    private function earnedBadges(int $daysCompleted, int $currentStreak, $trades): array
    {
        return Badge::activeOrdered()
            ->filter(function (Badge $badge) use ($daysCompleted, $currentStreak, $trades): bool {
                return match ($badge->requirement_key) {
                    Badge::SEVEN_DAY_JOURNAL => $daysCompleted >= 7,
                    Badge::THIRTY_DAY_DISCIPLINE => $daysCompleted >= 30,
                    Badge::RISK_MANAGER => $trades->whereNotNull('stop_loss')->whereNotNull('take_profit')->count() >= 10,
                    Badge::CONSISTENT_TRADER => $currentStreak >= 10,
                    default => false,
                };
            })
            ->pluck('name')
            ->values()
            ->all();
    }

    private function badges(?ChallengeParticipant $participant): array
    {
        $earned = $participant?->badges_earned ?? [];

        return Badge::activeOrdered()
            ->map(fn (Badge $badge): array => [
                'name' => $badge->name,
                'description' => $badge->description,
                'icon' => $badge->icon,
                'earned' => in_array($badge->name, $earned, true),
            ])
            ->all();
    }
}
