<?php

namespace Functional\Streaks\Services;

use Carbon\CarbonImmutable;
use Functional\Routines\Models\Routine;
use Functional\Streaks\Enums\GoalType;
use Functional\Streaks\Models\Achievement;
use Functional\Streaks\Models\Goal;
use Functional\Streaks\Models\Streak;
use Functional\Streaks\Models\StreakDay;
use Functional\Tracking\Enums\WorkoutSessionStatus;
use Functional\Tracking\Models\WorkoutSession;
use Functional\Users\Models\User;

/**
 * Construit le view-model "prêt-à-afficher" du dashboard (GET /api/dashboard) et la liste
 * des achievements (GET /api/achievements). Toute la logique de présentation du streak vit ici,
 * ni dans le contrôleur, ni dans le front.
 */
class DashboardService
{
    private const RECENT_SESSIONS_LIMIT = 5;

    private const RECENT_ACHIEVEMENTS_LIMIT = 10;

    private const DEFAULT_WEEKLY_GOAL = 3;

    private const DASHBOARD_WINDOW_DAYS = 7;

    /** Paliers du current streak vers un flame_level 0..5 (index dans ce tableau = niveau). */
    private const FLAME_THRESHOLDS = [0, 1, 3, 7, 30, 100];

    public function build(User $user): array
    {
        return [
            'streak' => $this->streakOverview($user),
            'week' => $this->weekOverview($user),
            'recent_sessions' => $this->recentSessions($user),
            'achievements' => $this->achievementsList($user, self::RECENT_ACHIEVEMENTS_LIMIT),
            'suggestion' => $this->suggestion($user),
        ];
    }

    /** Liste complète (ou limitée) des badges de l'utilisateur, avec leur libellé localisé. */
    public function achievementsList(User $user, ?int $limit = null): array
    {
        $query = Achievement::query()->where('user_id', $user->id)->orderByDesc('earned_at');

        if ($limit) {
            $query->limit($limit);
        }

        return $query->get()
            ->map(fn (Achievement $achievement) => $this->formatAchievement($achievement, $user))
            ->values()
            ->all();
    }

    private function streakOverview(User $user): array
    {
        $streak = Streak::query()->where('user_id', $user->id)->first();
        $currentCount = $streak->current_count ?? 0;

        return [
            'current' => $currentCount,
            'longest' => $streak->longest_count ?? 0,
            'freezes_available' => $streak->freezes_available ?? 0,
            'last_active_date' => optional($streak?->last_active_date)->toDateString(),
            'flame_level' => $this->flameLevel($currentCount),
            'active_today' => $this->isActiveToday($streak),
        ];
    }

    private function weekOverview(User $user): array
    {
        $today = CarbonImmutable::today();
        $windowStart = $today->subDays(self::DASHBOARD_WINDOW_DAYS - 1);

        $streakDaysByDate = StreakDay::query()
            ->where('user_id', $user->id)
            ->whereBetween('date', [$windowStart->toDateString(), $today->toDateString()])
            ->get()
            ->keyBy(fn (StreakDay $streakDay) => $streakDay->date->toDateString());

        $days = [];

        for ($offset = self::DASHBOARD_WINDOW_DAYS - 1; $offset >= 0; $offset--) {
            $date = $today->subDays($offset);
            $streakDay = $streakDaysByDate->get($date->toDateString());

            $days[] = [
                'date' => $date->toDateString(),
                'done' => $streakDay !== null,
                'source' => $streakDay?->source->value,
            ];
        }

        $activeGoal = Goal::query()
            ->where('user_id', $user->id)
            ->where('type', GoalType::WeeklySessions)
            ->where('is_active', true)
            ->first();

        $doneSessions = WorkoutSession::query()
            ->where('user_id', $user->id)
            ->where('status', WorkoutSessionStatus::Completed)
            ->whereBetween('started_at', [$windowStart->startOfDay(), $today->endOfDay()])
            ->count();

        return [
            'goal_sessions' => $activeGoal->target ?? self::DEFAULT_WEEKLY_GOAL,
            'done_sessions' => $doneSessions,
            'days' => $days,
        ];
    }

    private function recentSessions(User $user): array
    {
        return WorkoutSession::query()
            ->where('user_id', $user->id)
            ->where('status', WorkoutSessionStatus::Completed)
            ->withCount('setLogs')
            ->orderByDesc('started_at')
            ->limit(self::RECENT_SESSIONS_LIMIT)
            ->get()
            ->map(fn (WorkoutSession $session) => [
                'id' => $session->id,
                'title' => $session->title,
                'started_at' => $session->started_at,
                'total_volume' => $session->total_volume,
                'set_count' => $session->set_logs_count,
            ])
            ->values()
            ->all();
    }

    /** Proposition de prochaine séance : repos si déjà actif aujourd'hui, sinon une routine du user. */
    private function suggestion(User $user): array
    {
        $locale = $user->locale ?? 'fr';
        $streak = Streak::query()->where('user_id', $user->id)->first();

        if ($this->isActiveToday($streak)) {
            return [
                'type' => 'rest',
                'label' => __('dashboard.suggestion_rest', [], $locale),
            ];
        }

        $routine = Routine::query()->where('user_id', $user->id)->orderBy('name')->first();

        if (! $routine) {
            return [
                'type' => 'rest',
                'label' => __('dashboard.suggestion_no_routine', [], $locale),
            ];
        }

        return [
            'type' => 'routine',
            'routine_id' => $routine->id,
            'label' => __('dashboard.suggestion_routine', ['name' => $routine->name], $locale),
        ];
    }

    private function isActiveToday(?Streak $streak): bool
    {
        if (! $streak?->last_active_date) {
            return false;
        }

        return $streak->last_active_date->isSameDay(CarbonImmutable::today());
    }

    private function flameLevel(int $currentStreak): int
    {
        $level = 0;

        foreach (self::FLAME_THRESHOLDS as $index => $threshold) {
            if ($currentStreak >= $threshold) {
                $level = $index;
            }
        }

        return $level;
    }

    private function formatAchievement(Achievement $achievement, User $user): array
    {
        return [
            'key' => $achievement->key,
            'earned_at' => $achievement->earned_at,
            'label' => __("achievements.{$achievement->key}", [], $user->locale ?? 'fr'),
        ];
    }
}
