<?php

namespace App\Services;

use App\Enums\PredictionSource;
use App\Enums\TrendDirection;
use App\Models\BrentPrice;
use App\Models\PricePrediction;
use App\Services\LlmPrediction\OilPredictionProvider;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;

final class BrentPricePredictor
{
    private const float EWMA_THRESHOLD_PCT = 1.5;

    private const int EWMA_MAX_CONFIDENCE = 65;

    private const int EWMA_MIN_ROWS = 14;

    public function __construct(
        private readonly OilPredictionProvider $provider,
    ) {}

    /**
     * Return the latest BrentPrice row, or null if none exists.
     */
    public function latestPrice(): ?BrentPrice
    {
        return BrentPrice::orderBy('date', 'desc')->first();
    }

    /**
     * Try LLM first; persist EWMA only as a fallback when the LLM provider
     * returns null. The downstream OilSignal already prefers LLM
     * (llm_with_context > llm > ewma), so writing both rows on every run is
     * dead weight 95% of the time. EWMA still acts as the safety net.
     */
    public function generatePrediction(): ?PricePrediction
    {
        $prices = BrentPrice::orderBy('date', 'desc')->limit(30)->get();

        if ($prices->count() < self::EWMA_MIN_ROWS) {
            Log::warning('BrentPricePredictor: not enough price data', [
                'rows' => $prices->count(),
            ]);

            return null;
        }

        $llm = $this->provider->predict($prices);

        if ($llm !== null) {
            PricePrediction::create($llm->toArray());
            $prices->first()->forceFill(['prediction_generated_at' => now()])->save();

            return $llm;
        }

        $ewma = $this->generateEwmaPrediction($prices);

        if ($ewma !== null) {
            PricePrediction::create($ewma->toArray());
            $prices->first()->forceFill(['prediction_generated_at' => now()])->save();
        }

        return $ewma;
    }

    public function generateEwmaPrediction(Collection $prices): ?PricePrediction
    {
        $chronological = $prices->sortBy('date')->pluck('price_usd')->values()->all();

        if (count($chronological) < self::EWMA_MIN_ROWS) {
            return null;
        }

        $ewma3 = Ewma::compute(array_slice($chronological, -3));
        $ewma7 = Ewma::compute(array_slice($chronological, -7));

        $changePct = (($ewma3 - $ewma7) / $ewma7) * 100;

        [$direction, $confidence] = match (true) {
            $changePct >= self::EWMA_THRESHOLD_PCT => [
                TrendDirection::Rising,
                $this->ewmaConfidence($changePct),
            ],
            $changePct <= -self::EWMA_THRESHOLD_PCT => [
                TrendDirection::Falling,
                $this->ewmaConfidence(abs($changePct)),
            ],
            default => [TrendDirection::Flat, 50],
        };

        $reasoning = sprintf(
            '3-day EWMA ($%.2f) vs 7-day EWMA ($%.2f): %.2f%% %s.',
            $ewma3,
            $ewma7,
            abs($changePct),
            $direction === TrendDirection::Flat ? 'difference (flat)' : $direction->value,
        );

        return new PricePrediction([
            'predicted_for' => now()->toDateString(),
            'source' => PredictionSource::Ewma,
            'direction' => $direction,
            'confidence' => $confidence,
            'reasoning' => $reasoning,
            'generated_at' => now(),
        ]);
    }

    private function ewmaConfidence(float $changePct): int
    {
        $scaled = min($changePct / 5.0, 1.0) * self::EWMA_MAX_CONFIDENCE;

        return (int) round(max(30, $scaled));
    }
}
