"""Stock universe helpers.

Provides ticker lists to screen. Uses the S&P 500 pulled from Wikipedia
(free, no key) with an on-disk cache and a bundled fallback so the tool
still works offline.
"""
from __future__ import annotations

import io
import json
import os
from datetime import datetime, timedelta

import pandas as pd
import requests

_CACHE_DIR = os.path.join(os.path.dirname(__file__), ".cache")
_SP500_CACHE = os.path.join(_CACHE_DIR, "sp500.json")
_CACHE_TTL_DAYS = 7

# Small hand-picked fallback so the app never comes up empty even with no
# network. A mix of the names each of our four investors would recognise.
FALLBACK_TICKERS = [
    "AAPL", "MSFT", "GOOGL", "AMZN", "META", "BRK-B", "JPM", "V", "JNJ",
    "PG", "KO", "PEP", "WMT", "HD", "BAC", "XOM", "CVX", "UNH", "PFE",
    "T", "VZ", "INTC", "CSCO", "ORCL", "IBM", "GME", "AMC", "F", "GM",
    "DIS", "NKE", "MCD", "COST", "TGT", "LOW", "CAT", "DE", "MMM", "GE",
]


def _ensure_cache_dir() -> None:
    os.makedirs(_CACHE_DIR, exist_ok=True)


def _cache_fresh(path: str) -> bool:
    if not os.path.exists(path):
        return False
    mtime = datetime.fromtimestamp(os.path.getmtime(path))
    return datetime.now() - mtime < timedelta(days=_CACHE_TTL_DAYS)


def get_sp500(force_refresh: bool = False) -> list[str]:
    """Return S&P 500 tickers, normalised for yfinance (dots -> dashes)."""
    _ensure_cache_dir()
    if not force_refresh and _cache_fresh(_SP500_CACHE):
        try:
            with open(_SP500_CACHE, "r", encoding="utf-8") as fh:
                return json.load(fh)
        except (json.JSONDecodeError, OSError):
            pass

    try:
        url = "https://en.wikipedia.org/wiki/List_of_S%26P_500_companies"
        # Wikipedia 403s pandas' default urllib User-Agent; fetch via requests.
        html = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=30).text
        tables = pd.read_html(io.StringIO(html))
        tickers = tables[0]["Symbol"].astype(str).tolist()
        # yfinance uses '-' where Wikipedia uses '.' (e.g. BRK.B -> BRK-B)
        tickers = [t.replace(".", "-").strip() for t in tickers]
        with open(_SP500_CACHE, "w", encoding="utf-8") as fh:
            json.dump(tickers, fh)
        return tickers
    except Exception:
        # Offline / blocked: fall back to bundled list.
        return FALLBACK_TICKERS


UNIVERSES = {
    "Fallback (40 liquid names)": lambda: FALLBACK_TICKERS,
    "S&P 500": get_sp500,
}
