from __future__ import annotations

import csv
import io
import json
from dataclasses import dataclass
from typing import Any, cast
from urllib.parse import urlencode
from urllib.request import Request, urlopen


@dataclass(frozen=True)
class Instrument:
    symbol: str
    name: str
    market: str
    security_type: str


@dataclass
class SahmkUniverseProvider:
    api_key: str
    base: str = "https://api.sahmk.sa/api/v1/companies/"

    def _page(self, *, market: str, limit: int, offset: int) -> dict[str, Any]:
        url = f"{self.base}?{urlencode({'market': market, 'limit': limit, 'offset': offset})}"
        request = Request(
            url,
            headers={"X-API-Key": self.api_key, "User-Agent": "Investly/1.0"},
        )
        with urlopen(request, timeout=20) as response:
            payload = json.load(response)
        return cast(dict[str, Any], payload)

    def equities(self, market: str = "TASI") -> list[Instrument]:
        limit = 100
        offset = 0
        instruments: list[Instrument] = []
        total = 1
        while offset < total:
            page = self._page(market=market, limit=limit, offset=offset)
            total = int(page.get("total") or 0)
            rows = cast(list[dict[str, Any]], page.get("results") or [])
            for row in rows:
                if str(row.get("status", "active")).lower() != "active":
                    continue
                if str(row.get("security_type", "")).lower() != "equity":
                    continue
                instruments.append(
                    Instrument(
                        symbol=str(row["symbol"]),
                        name=str(row.get("name_en") or row.get("name_ar") or row["symbol"]),
                        market=str(row.get("market") or market),
                        security_type=str(row.get("security_type") or "Equity"),
                    )
                )
            offset += limit
        unique = {instrument.symbol: instrument for instrument in instruments}
        return sorted(unique.values(), key=lambda instrument: instrument.symbol)


@dataclass
class SP500UniverseProvider:
    """Independent developed-market control universe from the DataHub S&P 500 dataset."""

    url: str = "https://datahub.io/core/s-and-p-500-companies/r/constituents.csv"

    def equities(self) -> list[Instrument]:
        request = Request(self.url, headers={"User-Agent": "Investly/1.0"})
        with urlopen(request, timeout=20) as response:
            text = response.read().decode("utf-8-sig")
        rows = list(csv.DictReader(io.StringIO(text)))
        instruments: list[Instrument] = []
        for row in rows:
            raw_symbol = (row.get("Symbol") or row.get("symbol") or "").strip()
            if not raw_symbol:
                continue
            # Yahoo uses BRK-B/BF-B notation where index constituent lists use dots.
            symbol = raw_symbol.replace(".", "-")
            name = (row.get("Name") or row.get("Security") or raw_symbol).strip()
            instruments.append(Instrument(symbol, name, "SP500", "Equity"))
        unique = {instrument.symbol: instrument for instrument in instruments}
        result = sorted(unique.values(), key=lambda instrument: instrument.symbol)
        if len(result) < 490:
            raise RuntimeError(f"S&P 500 universe failed quality gate: only {len(result)} symbols")
        return result
