from __future__ import annotations

import json
from decimal import Decimal, InvalidOperation
from urllib import error, parse, request

from jarvis_finance.market_data.catalog import _runtime_secret_value


class HistoricalFxUnavailable(RuntimeError):
    def __init__(self, message: str, *, status: str = "manual_override_required") -> None:
        super().__init__(message)
        self.status = status


class MockFxProvider:
    name = "mock"

    def __init__(self, rates: dict[tuple[str, str, str], Decimal | str | None], *, unavailable: set[tuple[str, str, str]] | None = None) -> None:
        self.rates = {(a.upper(), b.upper(), d): (Decimal(str(v)) if v is not None else None) for (a, b, d), v in rates.items()}
        self.unavailable = {(a.upper(), b.upper(), d) for a, b, d in (unavailable or set())}

    def get_rate(self, base_currency: str, quote_currency: str, rate_date: str | None = None) -> Decimal | None:
        date = rate_date or "latest"
        key = (base_currency.upper(), quote_currency.upper(), date)
        if key in self.unavailable:
            raise HistoricalFxUnavailable("historical FX unavailable/paywalled")
        return self.rates.get(key)


class FrankfurterFxProvider:
    name = "frankfurter"
    base_url = "https://api.frankfurter.dev"

    def __init__(self, *, timeout_seconds: float = 8.0) -> None:
        self.timeout_seconds = timeout_seconds

    def get_rate(self, base_currency: str, quote_currency: str, rate_date: str | None = None) -> Decimal | None:
        base = base_currency.upper(); quote = quote_currency.upper()
        if base == quote:
            return Decimal("1")
        params = {"base": base, "quotes": quote}
        if rate_date and rate_date != "latest":
            params["date"] = rate_date
        url = f"{self.base_url}/v2/rates?" + parse.urlencode(params)
        try:
            req = request.Request(url, headers={"Accept": "application/json"})
            with request.urlopen(req, timeout=self.timeout_seconds) as response:  # noqa: S310 - explicit CLI FX update
                payload = json.loads(response.read().decode("utf-8"))
        except error.HTTPError as exc:
            if exc.code == 429:
                raise HistoricalFxUnavailable("frankfurter_rate_limited", status="rate_limited") from exc
            raise HistoricalFxUnavailable("frankfurter_provider_error") from exc
        except error.URLError as exc:
            raise HistoricalFxUnavailable("frankfurter_network_error") from exc
        try:
            if isinstance(payload, list) and payload:
                first = payload[0]
                value = first.get("rate") if isinstance(first, dict) else None
            elif isinstance(payload, dict):
                value = payload.get("rates", {}).get(quote)
                if value in {None, ""}:
                    value = payload.get("amount") or payload.get("rate")
            else:
                value = None
            return Decimal(str(value)) if value not in {None, ""} else None
        except (InvalidOperation, ValueError, AttributeError):
            return None


class TwelveDataFxProvider:
    name = "twelvedata"

    def __init__(self, *, api_key: str | None = None, timeout_seconds: float = 8.0) -> None:
        self.api_key = api_key or _runtime_secret_value(("TWELVEDATA_API_KEY", "TWELVE_DATA_API_KEY", "JARVIS_TWELVEDATA_API_KEY", "JARVIS_TWELVE_DATA_API_KEY"))
        self.timeout_seconds = timeout_seconds

    def get_rate(self, base_currency: str, quote_currency: str, rate_date: str | None = None) -> Decimal | None:
        base = base_currency.upper(); quote = quote_currency.upper()
        if base == quote:
            return Decimal("1")
        if not self.api_key:
            raise HistoricalFxUnavailable("twelvedata_api_key_missing", status="manual_override_required")
        symbol = f"{base}/{quote}"
        endpoint = "exchange_rate" if not rate_date or rate_date == "latest" else "time_series"
        params: dict[str, str] = {"symbol": symbol, "apikey": self.api_key}
        if endpoint == "time_series":
            params.update({"interval": "1day", "start_date": rate_date, "end_date": rate_date, "outputsize": "1"})
        url = "https://api.twelvedata.com/" + endpoint + "?" + parse.urlencode(params)
        try:
            req = request.Request(url, headers={"Accept": "application/json"})
            with request.urlopen(req, timeout=self.timeout_seconds) as response:  # noqa: S310 - explicit CLI FX update
                payload = json.loads(response.read().decode("utf-8"))
        except error.HTTPError as exc:
            if exc.code in {401, 403}:
                raise HistoricalFxUnavailable("twelvedata_auth_failed", status="forbidden") from exc
            if exc.code == 429:
                raise HistoricalFxUnavailable("twelvedata_rate_limited", status="rate_limited") from exc
            raise HistoricalFxUnavailable("twelvedata_provider_error") from exc
        except error.URLError as exc:
            raise HistoricalFxUnavailable("twelvedata_network_error") from exc
        if isinstance(payload, dict) and str(payload.get("status") or "").lower() == "error":
            raise HistoricalFxUnavailable("twelvedata_provider_error")
        value = None
        if endpoint == "exchange_rate" and isinstance(payload, dict):
            value = payload.get("rate")
        elif isinstance(payload, dict):
            values = payload.get("values", [])
            if values and isinstance(values[0], dict):
                value = values[0].get("close")
        try:
            return Decimal(str(value)) if value not in {None, ""} else None
        except (InvalidOperation, ValueError):
            return None


def fx_provider_by_name(name: str):
    key = (name or "").lower().replace("_", "").replace("-", "")
    if key == "frankfurter":
        return FrankfurterFxProvider()
    if key == "twelvedata":
        return TwelveDataFxProvider()
    if key == "mock":
        return MockFxProvider({})
    raise ValueError("unknown fx provider")
