<?php

namespace App\Services\Forecasting;

use App\Models\WeeklyForecast;
use Illuminate\Support\Facades\DB;

/**
 * Pairs a `weekly_forecasts` row with the actual ULSP move once BEIS
 * publishes the matching week. Writes idempotent rows to
 * `forecast_outcomes` so trailing-13-week accuracy is honest, not
 * inferred.
 */
final class OutcomeResolver
{
    private const float FLAT_THRESHOLD_PENCE_X100 = 20.0;

    public function resolvePending(): int
    {
        $resolved = 0;

        $existing = DB::table('forecast_outcomes')
            ->select(['forecast_for', 'model_version'])
            ->get()
            ->mapWithKeys(fn ($r): array => [$r->forecast_for.'|'.$r->model_version => true])
            ->all();

        $candidates = WeeklyForecast::query()
            ->where('forecast_for', '<=', now()->toDateString())
            ->orderBy('forecast_for')
            ->get();

        foreach ($candidates as $forecast) {
            $key = $forecast->forecast_for->toDateString().'|'.$forecast->model_version;
            if (isset($existing[$key])) {
                continue;
            }

            $actualDelta = $this->actualDeltaPence($forecast->forecast_for->toDateString());
            if ($actualDelta === null) {
                continue;
            }

            $actualClass = $this->classifyDirection($actualDelta);
            $absError = (int) round(abs($forecast->magnitude_pence - $actualDelta));

            DB::table('forecast_outcomes')->insert([
                'forecast_for' => $forecast->forecast_for->toDateString(),
                'model_version' => $forecast->model_version,
                'predicted_class' => $forecast->direction,
                'actual_class' => $actualClass,
                'correct' => $forecast->direction === $actualClass,
                'abs_error_pence' => $absError,
                'resolved_at' => now(),
            ]);

            $resolved++;
        }

        return $resolved;
    }

    private function actualDeltaPence(string $targetDate): ?float
    {
        $current = DB::table('weekly_pump_prices')
            ->where('date', $targetDate)
            ->value('ulsp_pence');
        $previous = DB::table('weekly_pump_prices')
            ->where('date', date('Y-m-d', strtotime($targetDate.' -7 days')))
            ->value('ulsp_pence');

        if ($current === null || $previous === null) {
            return null;
        }

        return (float) ($current - $previous);
    }

    private function classifyDirection(float $deltaPence): string
    {
        return match (true) {
            $deltaPence > self::FLAT_THRESHOLD_PENCE_X100 => 'rising',
            $deltaPence < -self::FLAT_THRESHOLD_PENCE_X100 => 'falling',
            default => 'flat',
        };
    }
}
