"""Tiingo data provider.

Comprehensive market data with excellent historical coverage.
Free tier: 500 unique symbols/month. Premium tiers available.

Docs: https://api.tiingo.com/documentation/general/overview
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional

import requests

from ..models import Bar, Quote, TimeFrame


class TiingoProvider:
    """Tiingo data provider.

    Supports stocks, ETFs, mutual funds, crypto, and news.

    Usage:
        provider = TiingoProvider(api_key="your_key")
        bars = provider.get_bars("AAPL", start=datetime(2023, 1, 1))
    """

    name = "tiingo"
    supported_assets = ["stocks", "etfs", "crypto", "news"]
    BASE_URL = "https://api.tiingo.com"

    def __init__(self, api_key: Optional[str] = None) -> None:
        """Initialize Tiingo provider.

        Args:
            api_key: Tiingo API key. Get free key at api.tiingo.com
        """
        self._api_key = api_key or ""

        if not self._api_key:
            try:
                from ...common.cpz_ai import CPZAIClient
                cpz_client = CPZAIClient.from_env()
                creds = cpz_client.get_data_credentials(provider="tiingo")
                self._api_key = creds.get("tiingo_api_key", "")
            except Exception:
                pass

        if not self._api_key:
            print("[CPZ SDK] Tiingo API key not found. Get a free key at api.tiingo.com")

    def _request(self, url: str, params: Optional[Dict[str, Any]] = None) -> Any:
        """Make authenticated API request."""
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Token {self._api_key}",
        }
        response = requests.get(url, headers=headers, params=params, timeout=30)
        response.raise_for_status()
        return response.json()

    def _convert_timeframe(self, tf: TimeFrame) -> tuple[bool, str]:
        """Convert TimeFrame to Tiingo resample frequency.

        Returns:
            Tuple of (is_intraday, resampleFreq string)
        """
        intraday_map = {
            TimeFrame.MINUTE_1: "1min",
            TimeFrame.MINUTE_5: "5min",
            TimeFrame.MINUTE_15: "15min",
            TimeFrame.MINUTE_30: "30min",
            TimeFrame.HOUR_1: "1hour",
            TimeFrame.HOUR_4: "4hour",
        }
        if tf in intraday_map:
            return True, intraday_map[tf]
        return False, ""

    def get_bars(
        self,
        symbol: str,
        timeframe: TimeFrame | str = TimeFrame.DAY,
        start: Optional[datetime] = None,
        end: Optional[datetime] = None,
        limit: Optional[int] = None,
    ) -> List[Bar]:
        """Get historical bars from Tiingo.

        Args:
            symbol: Stock symbol (e.g., "AAPL")
            timeframe: Bar timeframe
            start: Start datetime
            end: End datetime
            limit: Maximum bars

        Returns:
            List of Bar objects
        """
        if isinstance(timeframe, str):
            timeframe = TimeFrame.from_string(timeframe)

        is_intraday, resample_freq = self._convert_timeframe(timeframe)

        try:
            if is_intraday:
                url = f"{self.BASE_URL}/iex/{symbol}/prices"
                params: Dict[str, Any] = {"resampleFreq": resample_freq}
                if start:
                    params["startDate"] = start.strftime("%Y-%m-%d")
                if end:
                    params["endDate"] = end.strftime("%Y-%m-%d")

                data = self._request(url, params)
                bars = self._parse_iex_bars(data, symbol)
            else:
                url = f"{self.BASE_URL}/tiingo/daily/{symbol}/prices"
                params = {}
                if start:
                    params["startDate"] = start.strftime("%Y-%m-%d")
                if end:
                    params["endDate"] = end.strftime("%Y-%m-%d")

                data = self._request(url, params)
                bars = self._parse_daily_bars(data, symbol)

            bars.sort(key=lambda x: x.timestamp)

            if limit and len(bars) > limit:
                bars = bars[-limit:]

            return bars

        except Exception as e:
            print(f"[CPZ SDK] Error fetching Tiingo data for {symbol}: {e}")
            return []

    def _parse_daily_bars(self, data: List[Dict], symbol: str) -> List[Bar]:
        """Parse daily endpoint response into Bar objects, preferring adjusted fields."""
        bars: List[Bar] = []
        for row in data:
            try:
                timestamp = datetime.strptime(row["date"][:10], "%Y-%m-%d")
                bars.append(Bar(
                    symbol=symbol,
                    timestamp=timestamp,
                    open=float(row.get("adjOpen", row.get("open", 0))),
                    high=float(row.get("adjHigh", row.get("high", 0))),
                    low=float(row.get("adjLow", row.get("low", 0))),
                    close=float(row.get("adjClose", row.get("close", 0))),
                    volume=float(row.get("adjVolume", row.get("volume", 0))),
                    vwap=None,
                ))
            except (ValueError, KeyError):
                continue
        return bars

    def _parse_iex_bars(self, data: List[Dict], symbol: str) -> List[Bar]:
        """Parse IEX endpoint response into Bar objects."""
        bars: List[Bar] = []
        for row in data:
            try:
                timestamp = datetime.strptime(row["date"][:19], "%Y-%m-%dT%H:%M:%S")
                bars.append(Bar(
                    symbol=symbol,
                    timestamp=timestamp,
                    open=float(row.get("open", 0)),
                    high=float(row.get("high", 0)),
                    low=float(row.get("low", 0)),
                    close=float(row.get("close", 0)),
                    volume=float(row.get("volume", 0)),
                    vwap=None,
                ))
            except (ValueError, KeyError):
                continue
        return bars

    def get_quote(self, symbol: str) -> Optional[Quote]:
        """Get latest quote from Tiingo IEX endpoint."""
        try:
            url = f"{self.BASE_URL}/iex/{symbol}"
            data = self._request(url)

            if not data:
                return None

            row = data[0] if isinstance(data, list) else data
            last = float(row.get("last", row.get("tngoLast", 0)))

            return Quote(
                symbol=symbol,
                timestamp=datetime.utcnow(),
                bid=float(row.get("bidPrice", last)),
                ask=float(row.get("askPrice", last)),
                bid_size=float(row.get("bidSize", 0)),
                ask_size=float(row.get("askSize", 0)),
            )

        except Exception as e:
            print(f"[CPZ SDK] Error fetching Tiingo quote for {symbol}: {e}")
            return None

    def get_quotes(self, symbols: List[str]) -> List[Quote]:
        """Get quotes for multiple symbols."""
        quotes: List[Quote] = []
        for symbol in symbols:
            quote = self.get_quote(symbol)
            if quote:
                quotes.append(quote)
        return quotes

    def get_news(
        self,
        symbol: str,
        limit: int = 50,
    ) -> List[Dict[str, Any]]:
        """Get news articles for a symbol.

        Args:
            symbol: Ticker symbol
            limit: Maximum articles to return

        Returns:
            List of news article dicts
        """
        try:
            url = f"{self.BASE_URL}/tiingo/news"
            data = self._request(url, {"tickers": symbol, "limit": limit})
            return data if isinstance(data, list) else []
        except Exception as e:
            print(f"[CPZ SDK] Error fetching Tiingo news for {symbol}: {e}")
            return []

    def get_crypto_bars(
        self,
        symbol: str,
        timeframe: TimeFrame | str = TimeFrame.DAY,
        start: Optional[datetime] = None,
        end: Optional[datetime] = None,
    ) -> List[Bar]:
        """Get cryptocurrency bar data.

        Args:
            symbol: Crypto symbol (e.g., "btcusd")
            timeframe: Bar timeframe
            start: Start datetime
            end: End datetime

        Returns:
            List of Bar objects
        """
        if isinstance(timeframe, str):
            timeframe = TimeFrame.from_string(timeframe)

        _, resample_freq = self._convert_timeframe(timeframe)

        try:
            url = f"{self.BASE_URL}/tiingo/crypto/prices"
            params: Dict[str, Any] = {"tickers": symbol}
            if resample_freq:
                params["resampleFreq"] = resample_freq
            if start:
                params["startDate"] = start.strftime("%Y-%m-%dT%H:%M:%S")
            if end:
                params["endDate"] = end.strftime("%Y-%m-%dT%H:%M:%S")

            data = self._request(url, params)

            if not data:
                return []

            price_data = data[0].get("priceData", []) if isinstance(data, list) else []

            bars: List[Bar] = []
            for row in price_data:
                try:
                    timestamp = datetime.strptime(row["date"][:19], "%Y-%m-%dT%H:%M:%S")
                    bars.append(Bar(
                        symbol=symbol,
                        timestamp=timestamp,
                        open=float(row.get("open", 0)),
                        high=float(row.get("high", 0)),
                        low=float(row.get("low", 0)),
                        close=float(row.get("close", 0)),
                        volume=float(row.get("volume", 0)),
                        vwap=None,
                    ))
                except (ValueError, KeyError):
                    continue

            bars.sort(key=lambda x: x.timestamp)
            return bars

        except Exception as e:
            print(f"[CPZ SDK] Error fetching Tiingo crypto data for {symbol}: {e}")
            return []
