<?php

namespace App\Services\Prediction\Signals;

use Illuminate\Support\Facades\DB;

final class TrendSignal extends AbstractSignal
{
    private const float R_SQUARED_THRESHOLD = 0.5;

    private const float SLOPE_THRESHOLD_PENCE = 0.3;

    private const float SLOPE_SATURATION_PENCE = 0.5;

    private const int PREDICTION_HORIZON_DAYS = 7;

    /** @return array{score: float, confidence: float, direction: string, detail: string, data_points: int, enabled: bool, slope: float, r_squared: float} */
    public function compute(SignalContext $context): array
    {
        foreach ([5, 14] as $lookbackDays) {
            $rows = DB::table('station_prices')
                ->where('fuel_type', $context->fuelType->value)
                ->where('price_effective_at', '>=', now()->subDays($lookbackDays))
                ->selectRaw('DATE(price_effective_at) as day, AVG(price_pence) as avg_price')
                ->groupBy('day')
                ->orderBy('day')
                ->get();

            if ($rows->count() < 2) {
                continue;
            }

            $regression = $this->linearRegression($rows->pluck('avg_price')->map(fn ($v) => (float) $v / 100)->values()->all());

            if ($regression['r_squared'] >= self::R_SQUARED_THRESHOLD) {
                $slope = $regression['slope'];
                $direction = match (true) {
                    $slope >= self::SLOPE_THRESHOLD_PENCE => 'up',
                    $slope <= -self::SLOPE_THRESHOLD_PENCE => 'down',
                    default => 'stable',
                };
                $absSlope = abs($slope);
                $score = $direction === 'stable' ? 0.0 : min(1.0, $absSlope / self::SLOPE_SATURATION_PENCE) * ($slope > 0 ? 1 : -1);
                $projected = round($slope * $lookbackDays, 1);
                $detail = $direction === 'stable'
                    ? "Prices flat over {$lookbackDays} days (slope: {$slope}p/day, R²={$regression['r_squared']})"
                    : sprintf(
                        '%s at %sp/day over %d days (R²=%s, ~%s%sp in %dd)',
                        $slope > 0 ? 'Rising' : 'Falling',
                        abs(round($slope, 2)),
                        $lookbackDays,
                        round($regression['r_squared'], 2),
                        $projected > 0 ? '+' : '',
                        $projected,
                        self::PREDICTION_HORIZON_DAYS,
                    );

                if ($lookbackDays === 5) {
                    $detail .= ' [Adaptive lookback active]';
                }

                return [
                    'score' => $score,
                    'confidence' => min(1.0, $regression['r_squared']),
                    'direction' => $direction,
                    'detail' => $detail,
                    'data_points' => $rows->count(),
                    'enabled' => true,
                    'slope' => round($slope, 3),
                    'r_squared' => round($regression['r_squared'], 3),
                ];
            }
        }

        return [
            'score' => 0.0,
            'confidence' => 0.0,
            'direction' => 'stable',
            'detail' => 'Insufficient price history or noisy data (R² below threshold)',
            'data_points' => 0,
            'enabled' => false,
            'slope' => 0.0,
            'r_squared' => 0.0,
        ];
    }
}
