"""urllib DailyBarClient — Tiingo native daily total-return closes. Drop-in for MassiveDailyClient on
the equity sleeves (sixtyforty/multistrat/gd), which compound daily TOTAL returns of dividend-paying
ETFs (SPY/IEF/GLD/PDBC/DBMF) plus crypto (BTC-USD in multistrat).

Why no reconstruction (unlike the Massive split-only adapter): Tiingo's equity/ETF `adjClose` is a
NATIVE total-return series (dividend AND split adjusted), so we read it directly. Tiingo carries ~33yr
of history; we still request a bounded RECENT window (default ~3yr) — far more than any sleeve's signal
lookback (≤126d warmup + forward) — to keep responses small.

Routing — crypto vs equity:
  Tiingo serves crypto from a DIFFERENT endpoint with a DIFFERENT response shape. Crypto has no
  dividends/splits, so its `close` IS the adjusted close (there is no `adjClose` field).
    equity/ETF: GET /tiingo/daily/{ticker}/prices?startDate&endDate
                → flat JSON list of {date, close, adjClose, divCash, splitFactor, ...}; read `adjClose`.
    crypto:     GET /tiingo/crypto/prices?tickers={btcusd}&startDate&resampleFreq=1day
                → JSON list [{ticker, baseCurrency, quoteCurrency, priceData: [{date, close, ...}]}];
                  read nested `priceData[].close`. Yahoo-style `BTC-USD` is mapped to `btcusd`.

Auth: header `Authorization: Token <key>` with TIINGO_API_KEY (env). The key is used ONLY in the
request header — never logged or interpolated into a URL.
"""
from __future__ import annotations

import datetime as dt
import json
import os
import time
import urllib.parse
import urllib.request
from typing import Any

_DAILY = "{base}/tiingo/daily/{sym}/prices?startDate={frm}&endDate={to}&format=json"
_CRYPTO = "{base}/tiingo/crypto/prices?tickers={sym}&startDate={frm}&resampleFreq=1day&format=json"

# Known crypto bases (Yahoo-style `<BASE>-USD`). Tiingo routes these to the crypto endpoint.
_CRYPTO_BASES = {"BTC", "ETH", "SOL", "XRP", "ADA", "DOGE", "LTC", "BCH", "DOT", "AVAX", "LINK", "MATIC"}


class TiingoDailyClient:
    def __init__(
        self,
        api_key: str | None = None,
        lookback_days: int = 1100,
        base_url: str = "https://api.tiingo.com",
    ) -> None:
        self._key = api_key if api_key is not None else os.environ["TIINGO_API_KEY"]
        self._lookback_days = lookback_days
        self._base = base_url.rstrip("/")

    def _get(self, url: str, tries: int = 4) -> Any:
        for a in range(tries):
            try:
                # The key lives ONLY in this header — never in the URL or any log line.
                req = urllib.request.Request(url, headers={"Authorization": f"Token {self._key}"})
                return json.loads(urllib.request.urlopen(req, timeout=30).read())
            except Exception:  # noqa: BLE001 — transient HTTP; retry then re-raise
                if a == tries - 1:
                    raise
                time.sleep(2 * (a + 1))
        raise RuntimeError("unreachable")

    def _window(self) -> tuple[str, str]:
        to = dt.date.today()
        frm = to - dt.timedelta(days=self._lookback_days)
        return frm.isoformat(), to.isoformat()

    @staticmethod
    def _is_crypto(symbol: str) -> bool:
        if "-" not in symbol:
            return False
        base, _, quote = symbol.partition("-")
        return quote.upper() == "USD" and base.upper() in _CRYPTO_BASES

    @staticmethod
    def _to_crypto_ticker(symbol: str) -> str:
        """Yahoo-style `BTC-USD` → Tiingo crypto ticker `btcusd`."""
        return symbol.replace("-", "").lower()

    def adj_closes(self, symbol: str) -> dict[str, float]:
        frm, to = self._window()
        if self._is_crypto(symbol):
            return self._crypto_closes(symbol, frm, to)
        return self._equity_closes(symbol, frm, to)

    def _equity_closes(self, symbol: str, frm: str, to: str) -> dict[str, float]:
        sym = urllib.parse.quote(symbol)
        rows = self._get(_DAILY.format(base=self._base, sym=sym, frm=frm, to=to))
        out: dict[str, float] = {}
        for r in rows or []:
            out[str(r["date"])[:10]] = float(r["adjClose"])
        return out

    def _crypto_closes(self, symbol: str, frm: str, to: str) -> dict[str, float]:
        sym = self._to_crypto_ticker(symbol)
        resp = self._get(_CRYPTO.format(base=self._base, sym=sym, frm=frm, to=to))
        out: dict[str, float] = {}
        for series in resp or []:
            for r in series.get("priceData") or []:
                out[str(r["date"])[:10]] = float(r["close"])
        return out
