<?php

namespace App\Services\BrentPriceSources;

use App\Services\ApiLogger;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Throwable;

final class FredBrentPriceSource
{
    private const string URL = 'https://api.stlouisfed.org/fred/series/observations';

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

    /**
     * @return array{date: string, price_usd: float}[]|null null only when the response carried no usable rows
     *
     * @throws BrentPriceFetchException on network failure or non-2xx response after retries
     */
    public function fetch(): ?array
    {
        return $this->call([
            'sort_order' => 'desc',
            'limit' => 30,
        ]);
    }

    /**
     * Backfill range (inclusive). FRED's `observation_start` /
     * `observation_end` parameters expect ISO dates (YYYY-MM-DD).
     * Returns null when the range is empty (e.g. all weekends/holidays).
     *
     * @return array{date: string, price_usd: float}[]|null
     *
     * @throws BrentPriceFetchException
     */
    public function fetchRange(string $from, string $to): ?array
    {
        return $this->call([
            'observation_start' => $from,
            'observation_end' => $to,
            'sort_order' => 'asc',
            'limit' => 100000,
        ]);
    }

    /**
     * @param  array<string, scalar>  $extraParams
     * @return array{date: string, price_usd: float}[]|null
     *
     * @throws BrentPriceFetchException
     */
    private function call(array $extraParams): ?array
    {
        $params = array_merge([
            'series_id' => 'DCOILBRENTEU',
            'api_key' => config('services.fred.api_key'),
            'file_type' => 'json',
        ], $extraParams);

        try {
            $response = $this->apiLogger->send('fred', 'GET', self::URL, fn () => Http::timeout(60)
                ->retry(3, 200, fn (Throwable $e) => $this->shouldRetry($e))
                ->throw()
                ->get(self::URL, $params));
        } catch (ConnectionException $e) {
            throw new BrentPriceFetchException("FRED connection failed: {$e->getMessage()}", previous: $e);
        } catch (RequestException $e) {
            throw new BrentPriceFetchException("FRED returned HTTP {$e->response->status()}", previous: $e);
        }

        $rows = collect($response->json('observations') ?? [])
            ->filter(fn (array $obs) => $obs['value'] !== '.')
            ->map(fn (array $obs) => [
                'date' => $obs['date'],
                'price_usd' => (float) $obs['value'],
            ])
            ->all();

        return $rows === [] ? null : $rows;
    }

    private function shouldRetry(Throwable $e): bool
    {
        return $e instanceof ConnectionException
            || ($e instanceof RequestException && $e->response->serverError());
    }
}
