<?php

namespace App\Services\LlmPrediction;

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

abstract class AbstractLlmPredictionProvider implements OilPredictionProvider
{
    protected const int LLM_MAX_CONFIDENCE = 85;

    public function __construct(
        protected readonly ApiLogger $apiLogger,
    ) {}

    /**
     * Default flow: gate on API key, call the provider, normalise the payload
     * to a PricePrediction. Subclasses with multi-phase flows (e.g. Anthropic
     * web-search) override `predict()` directly and reuse the helper methods.
     */
    public function predict(Collection $prices): ?PricePrediction
    {
        $apiKey = $this->apiKey();

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

        try {
            $payload = $this->callProvider($apiKey, $this->buildPriceList($prices));

            return $payload === null ? null : $this->buildPrediction($payload);
        } catch (Throwable $e) {
            Log::error(static::class.': predict failed', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /** Returns the configured API key or null if not set. */
    abstract protected function apiKey(): ?string;

    /**
     * Make the provider HTTP call and return the normalised payload, or null
     * on failure (already logged by the implementer).
     *
     * @return array{direction: string, confidence: int, reasoning: string}|null
     */
    abstract protected function callProvider(string $apiKey, string $priceList): ?array;

    /** @param  Collection<int, BrentPrice>  $prices */
    protected function buildPriceList(Collection $prices): string
    {
        return $prices->sortBy('date')
            ->map(fn (BrentPrice $p) => $p->date->toDateString().': $'.$p->price_usd)
            ->implode("\n");
    }

    /** @param  array{direction: string, confidence: int, reasoning: string}  $input */
    protected function buildPrediction(array $input, PredictionSource $source = PredictionSource::Llm): ?PricePrediction
    {
        $direction = TrendDirection::tryFrom($input['direction'] ?? '');

        if ($direction === null) {
            Log::error(static::class.': invalid direction', ['input' => $input]);

            return null;
        }

        return new PricePrediction([
            'predicted_for' => now()->toDateString(),
            'source' => $source,
            'direction' => $direction,
            'confidence' => min((int) ($input['confidence'] ?? 0), self::LLM_MAX_CONFIDENCE),
            'reasoning' => $input['reasoning'] ?? '',
            'generated_at' => now(),
        ]);
    }

    protected function defaultPrompt(string $priceList): string
    {
        return <<<PROMPT
            You are analyzing Brent crude oil price data for a UK fuel price alert service.
            Predict the short-term direction over the next 3–5 days.

            Recent Brent crude prices (USD/barrel):
            {$priceList}

            Respond with direction (rising, falling, or flat), a confidence score (0–85),
            and a one-sentence reasoning.
            PROMPT;
    }
}
