<?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\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

class AnthropicPredictionProvider implements OilPredictionProvider
{
    private const int LLM_MAX_CONFIDENCE = 85;

    private const float EWMA_ALPHA = 0.3;

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

    /**
     * Tries web-search-enriched prediction first, falls back to basic tool use.
     */
    public function predict(Collection $prices): ?PricePrediction
    {
        if (! config('services.anthropic.api_key')) {
            return null;
        }

        $prediction = $this->predictWithWebContext($prices);

        return $prediction ?? $this->predictBasic($prices);
    }

    /**
     * Multi-turn web search phase, then a forced submit_prediction call.
     * Phase 1: Let the model search for recent oil/geopolitical news (pause_turn loop).
     * Phase 2: Force submit_prediction with the full conversation context.
     */
    private function predictWithWebContext(Collection $prices): ?PricePrediction
    {
        $messages = [['role' => 'user', 'content' => $this->contextPrompt($this->buildPriceList($prices))]];
        $url = 'https://api.anthropic.com/v1/messages';

        try {
            // Phase 1: web search loop
            for ($i = 0, $response = null; $i < 5; $i++) {
                $response = $this->apiLogger->send('anthropic', 'POST', $url, fn () => Http::timeout(30)
                    ->withHeaders($this->headers())
                    ->post($url, [
                        'model' => config('services.anthropic.model', 'claude-sonnet-4-6'),
                        'max_tokens' => 1024,
                        'tools' => [['type' => 'web_search_20250305', 'name' => 'web_search']],
                        'messages' => $messages,
                    ]));

                if (! $response->successful()) {
                    Log::error('AnthropicPredictionProvider: context search request failed', ['status' => $response->status()]);

                    return null;
                }

                if ($response->json('stop_reason') !== 'pause_turn') {
                    break;
                }

                $messages[] = ['role' => 'assistant', 'content' => $response->json('content')];
            }

            // Phase 2: forced submit with full context
            $messages[] = ['role' => 'assistant', 'content' => $response->json('content')];
            $messages[] = ['role' => 'user', 'content' => 'Now submit your prediction using the submit_prediction tool.'];

            $submitResponse = $this->apiLogger->send('anthropic', 'POST', $url, fn () => Http::timeout(15)
                ->withHeaders($this->headers())
                ->post($url, [
                    'model' => config('services.anthropic.model', 'claude-sonnet-4-6'),
                    'max_tokens' => 256,
                    'tools' => [$this->submitPredictionTool()],
                    'tool_choice' => ['type' => 'tool', 'name' => 'submit_prediction'],
                    'messages' => $messages,
                ]));

            if (! $submitResponse->successful()) {
                Log::error('AnthropicPredictionProvider: context submit request failed', ['status' => $submitResponse->status()]);

                return null;
            }

            $input = $this->extractToolInput($submitResponse->json('content') ?? []);

            if ($input === null) {
                Log::error('AnthropicPredictionProvider: no tool_use block in context submit response');

                return null;
            }

            return $this->buildPrediction($input, PredictionSource::LlmWithContext);
        } catch (Throwable $e) {
            Log::error('AnthropicPredictionProvider: predictWithWebContext failed', ['error' => $e->getMessage()]);

            return null;
        }
    }

    private function contextPrompt(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.

            First, search for recent news (last 48 hours) about:
            - Brent crude oil price movements
            - OPEC+ production decisions or announcements
            - Major geopolitical events affecting oil supply
            - Global demand signals (China economic data, US inventory reports)

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

            After searching, you will be asked to submit your prediction.
            PROMPT;
    }

    private function buildPriceList(Collection $prices): string
    {
        return $prices->sortBy('date')
            ->map(fn (BrentPrice $p) => $p->date->toDateString().': $'.$p->price_usd)
            ->implode("\n");
    }

    /** @return array<string, string> */
    private function headers(): array
    {
        return [
            'x-api-key' => config('services.anthropic.api_key'),
            'anthropic-version' => '2023-06-01',
        ];
    }

    /** @return array{name: string, description: string, input_schema: array<string, mixed>} */
    private function submitPredictionTool(): array
    {
        return [
            'name' => 'submit_prediction',
            'description' => 'Submit the final oil price direction prediction.',
            'input_schema' => [
                'type' => 'object',
                'properties' => [
                    'direction' => [
                        'type' => 'string',
                        'enum' => ['rising', 'falling', 'flat'],
                    ],
                    'confidence' => [
                        'type' => 'integer',
                        'minimum' => 0,
                        'maximum' => self::LLM_MAX_CONFIDENCE,
                    ],
                    'reasoning' => [
                        'type' => 'string',
                        'description' => 'One sentence explaining the prediction.',
                    ],
                ],
                'required' => ['direction', 'confidence', 'reasoning'],
            ],
        ];
    }

    /** @param  array<int, mixed>  $content */
    private function extractToolInput(array $content): ?array
    {
        $block = collect($content)->firstWhere('type', 'tool_use');

        return $block['input'] ?? null;
    }

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

        if ($direction === null) {
            Log::error('AnthropicPredictionProvider: invalid direction in tool input', ['input' => $input]);

            return null;
        }

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

    /**
     * Single-turn prediction using a forced submit_prediction tool call.
     * Guarantees structured output — no JSON parsing needed.
     */
    private function predictBasic(Collection $prices): ?PricePrediction
    {
        $chronological = $prices->sortBy('date');
        $ewma3 = $this->computeEwma($chronological->take(-3)->pluck('price_usd')->values()->all());
        $ewma7 = $this->computeEwma($chronological->take(-7)->pluck('price_usd')->values()->all());
        $ewma14 = $this->computeEwma($chronological->pluck('price_usd')->values()->all());

        $priceList = $this->buildPriceList($prices);

        $url = 'https://api.anthropic.com/v1/messages';

        try {
            $response = $this->apiLogger->send('anthropic', 'POST', $url, fn () => Http::timeout(15)
                ->withHeaders($this->headers())
                ->post($url, [
                    'model' => config('services.anthropic.model', 'claude-haiku-4-5-20251001'),
                    'max_tokens' => 256,
                    'tools' => [$this->submitPredictionTool()],
                    'tool_choice' => ['type' => 'tool', 'name' => 'submit_prediction'],
                    'messages' => [[
                        'role' => 'user',
                        'content' => $this->basicPrompt($priceList, $ewma3, $ewma7, $ewma14),
                    ]],
                ]));

            if (! $response->successful()) {
                Log::error('AnthropicPredictionProvider: basic request failed', ['status' => $response->status()]);

                return null;
            }

            $input = $this->extractToolInput($response->json('content') ?? []);

            if ($input === null) {
                Log::error('AnthropicPredictionProvider: no tool_use block in basic response');

                return null;
            }

            return $this->buildPrediction($input, PredictionSource::Llm);
        } catch (Throwable $e) {
            Log::error('AnthropicPredictionProvider: predictBasic failed', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * @param  float[]  $prices  Chronological order (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 basicPrompt(string $priceList, float $ewma3, float $ewma7, float $ewma14): 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}

            Pre-computed indicators:
            - 3-day EWMA:  \${$ewma3}
            - 7-day EWMA:  \${$ewma7}
            - 14-day EWMA: \${$ewma14}

            Use the submit_prediction tool to submit your answer.
            PROMPT;
    }
}
