<?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_ALPHA = 0.3;

    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();
    }

    /**
     * Generate EWMA + LLM predictions, store them, and flag the latest
     * brent_prices row as having a prediction generated.
     */
    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;
        }

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

        if ($ewma !== null) {
            PricePrediction::create($ewma->toArray());
        }

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

        if ($llm !== null) {
            PricePrediction::create($llm->toArray());
        }

        $result = $llm ?? $ewma;

        if ($result !== null) {
            $prices->first()->forceFill(['prediction_generated_at' => now()])->save();
        }

        return $result;
    }

    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 = $this->computeEwma(array_slice($chronological, -3));
        $ewma7 = $this->computeEwma(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(),
        ]);
    }

    /**
     * @param  float[]  $prices  Chronological (oldest first).
     */
    private function computeEwma(array $prices): float
    {
        $ema = $prices[0];

        foreach (array_slice($prices, 1) as $price) {
            $ema = self::EWMA_ALPHA * $price + (1 - self::EWMA_ALPHA) * $ema;
        }

        return round($ema, 4);
    }

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

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