<?php

namespace App\Services;

use App\Models\DisciplineScoreRule;
use App\Models\Trade;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

class DisciplineScoreService
{
    public function weeklyScore(User $user): array
    {
        return $this->scoreForRange($user, now()->startOfWeek(), now()->endOfWeek());
    }

    public function monthlyScore(User $user): array
    {
        return $this->scoreForRange($user, now()->startOfMonth(), now()->endOfMonth());
    }

    public function scoreForRange(User $user, Carbon $start, Carbon $end): array
    {
        $trades = Trade::query()
            ->where('user_id', $user->id)
            ->where(function (Builder $query) use ($start, $end): void {
                $query
                    ->whereBetween('opened_at', [$start, $end])
                    ->orWhere(function (Builder $query) use ($start, $end): void {
                        $query->whereNull('opened_at')->whereBetween('created_at', [$start, $end]);
                    });
            })
            ->get();

        return $this->scoreTrades($trades);
    }

    /**
     * @param  Collection<int, Trade>  $trades
     * @return array{score:int,total_trades:int,checks:array<string,int>,tips:array<int,string>}
     */
    public function scoreTrades(Collection $trades): array
    {
        if ($trades->isEmpty()) {
            return [
                'score' => 0,
                'total_trades' => 0,
                'checks' => DisciplineScoreRule::activeOrdered()->pluck('name', 'key')->map(fn () => 0)->all(),
                'tips' => ['Start by journaling your next trade with stop loss, take profit, and a psychology note.'],
            ];
        }

        $total = max(1, $trades->count());
        $rules = DisciplineScoreRule::activeOrdered();

        if ($rules->isEmpty()) {
            return [
                'score' => 0,
                'total_trades' => $trades->count(),
                'checks' => [],
                'tips' => ['Discipline scoring is currently disabled by the admin.'],
            ];
        }

        $checks = $rules
            ->mapWithKeys(fn (DisciplineScoreRule $rule): array => [
                $rule->key => $this->rulePercentage($rule->key, $trades, $total),
            ])
            ->all();
        $maxPoints = max(1, (int) $rules->sum('points'));
        $earnedPoints = $rules->sum(fn (DisciplineScoreRule $rule): float => (($checks[$rule->key] ?? 0) / 100) * $rule->points);
        $score = (int) round(($earnedPoints / $maxPoints) * 100);

        return [
            'score' => $score,
            'total_trades' => $trades->count(),
            'checks' => $checks,
            'tips' => $this->tips($checks),
        ];
    }

    private function percentage(int $passed, int $total): int
    {
        return (int) round(($passed / max(1, $total)) * 100);
    }

    /**
     * @param  Collection<int, Trade>  $trades
     */
    private function rulePercentage(string $key, Collection $trades, int $total): int
    {
        return match ($key) {
            DisciplineScoreRule::STOP_LOSS => $this->percentage($trades->filter(fn (Trade $trade): bool => filled($trade->stop_loss))->count(), $total),
            DisciplineScoreRule::TAKE_PROFIT => $this->percentage($trades->filter(fn (Trade $trade): bool => filled($trade->take_profit))->count(), $total),
            DisciplineScoreRule::RISK_REWARD => $this->percentage($trades->filter(fn (Trade $trade): bool => (bool) $trade->followed_risk_reward)->count(), $total),
            DisciplineScoreRule::PSYCHOLOGY_NOTE => $this->percentage($trades->filter(fn (Trade $trade): bool => filled($trade->psychology_note))->count(), $total),
            DisciplineScoreRule::AVOIDED_OVERTRADING => $this->percentage($trades->filter(fn (Trade $trade): bool => (bool) $trade->avoided_overtrading)->count(), $total),
            DisciplineScoreRule::JOURNALED_CONSISTENTLY => $this->consistencyPercentage($trades),
            default => 0,
        };
    }

    /**
     * @param  Collection<int, Trade>  $trades
     */
    private function consistencyPercentage(Collection $trades): int
    {
        $journaledDays = $trades
            ->map(fn (Trade $trade): ?string => ($trade->opened_at ?? $trade->created_at)?->toDateString())
            ->filter()
            ->unique()
            ->count();

        return min(100, $this->percentage($journaledDays, 5));
    }

    /**
     * @param  array<string,int>  $checks
     * @return array<int,string>
     */
    private function tips(array $checks): array
    {
        $tips = [];

        if (($checks['stop_loss'] ?? 100) < 80) {
            $tips[] = 'Add a stop loss before entering each trade.';
        }

        if (($checks['take_profit'] ?? 100) < 80) {
            $tips[] = 'Define take profit before the trade starts.';
        }

        if (($checks['psychology_note'] ?? 100) < 80) {
            $tips[] = 'Write what you felt before or during the trade.';
        }

        if (($checks['avoided_overtrading'] ?? 100) < 80) {
            $tips[] = 'Limit unnecessary trades and mark overtrading honestly.';
        }

        if (($checks['journaled_consistently'] ?? 100) < 80) {
            $tips[] = 'Journal on more trading days to build a consistent routine.';
        }

        return $tips ?: ['Great discipline. Keep following your plan and reviewing your data.'];
    }
}
