from __future__ import annotations

import json
from datetime import datetime, timezone
from typing import Any, cast
from urllib.parse import urlencode
from urllib.request import Request, urlopen

from investly.domain import Bar, Fundamentals, Market, Quote


class YahooProvider:
    name = "yahoo"
    base = "https://query1.finance.yahoo.com/v8/finance/chart"

    @staticmethod
    def _ticker(market: Market, symbol: str) -> str:
        if market == Market.TASI:
            if symbol in {"TASI", "^TASI.SR"}:
                return "^TASI.SR"
            return symbol if symbol.endswith(".SR") else f"{symbol}.SR"
        return symbol

    def _get(self, ticker: str, params: dict[str, str]) -> dict[str, Any]:
        url = f"{self.base}/{ticker}?{urlencode(params)}"
        req = Request(url, headers={"User-Agent": "Mozilla/5.0 Investly/0.2"})
        with urlopen(req, timeout=15) as response:
            payload = json.load(response)
        return cast(dict[str, Any], payload)

    @staticmethod
    def _bars(payload: dict[str, Any]) -> list[Bar]:
        result = payload["chart"]["result"][0]
        stamps = result.get("timestamp") or []
        q = result["indicators"]["quote"][0]
        bars: list[Bar] = []
        for i, ts in enumerate(stamps):
            vals = [q[k][i] for k in ("open", "high", "low", "close", "volume")]
            if any(value is None for value in vals):
                continue
            bars.append(
                Bar(
                    datetime.fromtimestamp(int(ts), tz=timezone.utc),
                    *(float(value) for value in vals),
                )
            )
        return bars

    def quote(self, market: Market, symbol: str) -> Quote:
        ticker = self._ticker(market, symbol)
        payload = self._get(ticker, {"range": "5d", "interval": "1d"})
        result = payload["chart"]["result"][0]
        meta = result["meta"]
        price = float(meta.get("regularMarketPrice") or meta.get("chartPreviousClose"))
        return Quote(
            market=market,
            symbol=symbol,
            name=meta.get("longName") or meta.get("shortName") or symbol,
            price=price,
            currency=meta.get("currency") or ("SAR" if market == Market.TASI else "USD"),
            as_of=datetime.fromtimestamp(
                int(meta.get("regularMarketTime") or datetime.now().timestamp()),
                tz=timezone.utc,
            ),
            source=self.name,
            delayed=True,
        )

    def history(self, market: Market, symbol: str, days: int = 260) -> list[Bar]:
        ticker = self._ticker(market, symbol)
        payload = self._get(
            ticker,
            {"range": f"{max(days, 30)}d", "interval": "1d", "events": "div,splits"},
        )
        return self._bars(payload)[-days:]

    def history_between(
        self,
        market: Market,
        symbol: str,
        start: datetime,
        end: datetime,
    ) -> list[Bar]:
        if start.tzinfo is None or end.tzinfo is None:
            raise ValueError("history_between requires timezone-aware boundaries")
        if end <= start:
            raise ValueError("history_between end must be after start")
        ticker = self._ticker(market, symbol)
        payload = self._get(
            ticker,
            {
                "period1": str(int(start.timestamp())),
                "period2": str(int(end.timestamp())),
                "interval": "1d",
                "events": "div,splits",
            },
        )
        return self._bars(payload)

    def fundamentals(self, market: Market, symbol: str) -> Fundamentals:
        return Fundamentals(symbol=symbol, source=self.name)
