<?php

namespace App\Services\Forecasting;

use App\Models\BrentPrice;
use App\Models\LlmOverlay;
use App\Models\VolatilityRegime;
use App\Models\WatchedEvent;
use Illuminate\Support\Facades\DB;

/**
 * Layer 5 — sole owner of `volatility_regimes.active`. Hourly cron.
 *
 * OR-combines four triggers:
 *   1. Brent close-to-close move > 3% (FRED `DCOILBRENTEU`).
 *   2. Most recent `llm_overlays.major_impact_event = true` AND at
 *      least one verified URL.
 *   3. `station_prices` daily churn > 1.5× 30-day baseline. Gated
 *      until ≥ 180 days of polling — toggleable via config.
 *   4. `watched_events` row covering today.
 *
 * When the flag flips ON, an event-driven LLM refresh is queued
 * (Layer 4 enforces its own 4h cooldown). When OFF, the row is
 * closed with `flipped_off_at`.
 */
final class VolatilityRegimeService
{
    private const float BRENT_MOVE_PCT = 3.0;

    private const float STATION_CHURN_RATIO = 1.5;

    private const int STATION_CHURN_MIN_POLLING_DAYS = 180;

    public function __construct(
        private readonly LlmOverlayService $llmOverlay,
    ) {}

    public function evaluate(): ?VolatilityRegime
    {
        $trigger = $this->detectTrigger();
        $current = VolatilityRegime::currentlyActive();

        if ($trigger !== null && $current === null) {
            $row = $this->flipOn($trigger);
            $this->llmOverlay->run(eventDriven: true);

            return $row;
        }

        if ($trigger === null && $current !== null) {
            $this->flipOff($current);

            return null;
        }

        return $current;
    }

    /** @return array{type: string, detail: string}|null */
    private function detectTrigger(): ?array
    {
        return $this->brentMoveTrigger()
            ?? $this->llmEventTrigger()
            ?? $this->stationChurnTrigger()
            ?? $this->watchedEventTrigger();
    }

    /** @return array{type: string, detail: string}|null */
    private function brentMoveTrigger(): ?array
    {
        $rows = BrentPrice::query()
            ->orderByDesc('date')
            ->limit(2)
            ->get(['date', 'price_usd']);

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

        $latest = (float) $rows[0]->price_usd;
        $prior = (float) $rows[1]->price_usd;
        if ($prior === 0.0) {
            return null;
        }

        $pctMove = abs(($latest - $prior) / $prior) * 100;
        if ($pctMove <= self::BRENT_MOVE_PCT) {
            return null;
        }

        $direction = $latest > $prior ? '+' : '-';

        return [
            'type' => 'brent_move',
            'detail' => sprintf('Brent %s%.2f%% (%s → %s)', $direction, $pctMove, $rows[1]->date->toDateString(), $rows[0]->date->toDateString()),
        ];
    }

    /** @return array{type: string, detail: string}|null */
    private function llmEventTrigger(): ?array
    {
        $latest = LlmOverlay::query()->orderByDesc('ran_at')->first();

        if ($latest === null || ! $latest->major_impact_event) {
            return null;
        }

        $hasVerifiedUrl = collect((array) $latest->events_json)
            ->contains(fn ($e): bool => is_array($e) && ! empty($e['url']));

        if (! $hasVerifiedUrl) {
            return null;
        }

        $headline = collect((array) $latest->events_json)->pluck('headline')->filter()->first();

        return [
            'type' => 'llm_event',
            'detail' => sprintf('LLM major impact: %s', $headline ?? 'unspecified'),
        ];
    }

    /** @return array{type: string, detail: string}|null */
    private function stationChurnTrigger(): ?array
    {
        if (! $this->stationChurnEnabled()) {
            return null;
        }

        $oldest = DB::table('station_prices')->min('price_effective_at');
        if ($oldest === null) {
            return null;
        }

        $pollingDays = (int) abs(now()->diffInDays($oldest));
        if ($pollingDays < self::STATION_CHURN_MIN_POLLING_DAYS) {
            return null;
        }

        $last24h = (int) DB::table('station_prices')
            ->where('price_effective_at', '>=', now()->subDay())
            ->distinct('station_id')
            ->count('station_id');

        $baseline = (int) DB::table('station_prices')
            ->where('price_effective_at', '>=', now()->subDays(30))
            ->where('price_effective_at', '<', now()->subDay())
            ->distinct('station_id')
            ->count('station_id');

        if ($baseline === 0) {
            return null;
        }

        $dailyBaseline = $baseline / 29; // 29 days of history before yesterday
        if ($last24h <= $dailyBaseline * self::STATION_CHURN_RATIO) {
            return null;
        }

        return [
            'type' => 'station_churn',
            'detail' => sprintf('Station churn %d/24h vs %.1f baseline (%.2fx)', $last24h, $dailyBaseline, $last24h / $dailyBaseline),
        ];
    }

    /** @return array{type: string, detail: string}|null */
    private function watchedEventTrigger(): ?array
    {
        $row = WatchedEvent::query()
            ->where('starts_at', '<=', now())
            ->where('ends_at', '>=', now())
            ->orderBy('starts_at')
            ->first();

        if ($row === null) {
            return null;
        }

        return [
            'type' => 'manual',
            'detail' => sprintf('Watched event: %s', $row->label),
        ];
    }

    private function stationChurnEnabled(): bool
    {
        return (bool) config('services.forecasting.station_churn_enabled', false);
    }

    /** @param  array{type: string, detail: string}  $trigger */
    private function flipOn(array $trigger): VolatilityRegime
    {
        return VolatilityRegime::query()->create([
            'flipped_on_at' => now(),
            'flipped_off_at' => null,
            'trigger' => $trigger['type'],
            'trigger_detail' => $trigger['detail'],
            'active' => true,
        ]);
    }

    private function flipOff(VolatilityRegime $row): void
    {
        $row->update([
            'flipped_off_at' => now(),
            'active' => false,
        ]);
    }
}
