"""
In-memory TTL result cache for akshare API calls.

Caches function results to avoid redundant network requests.
Each entry has a configurable time-to-live (TTL) in seconds.
"""

import hashlib
import json
import time
from typing import Any, Dict, Optional

# ---------------------------------------------------------------------------
# TTL Configuration
# ---------------------------------------------------------------------------

# Functions that should NEVER be cached (real-time data)
NO_CACHE: set = {
    "stock_zh_a_spot_em",
    "stock_sh_a_spot_em",
    "stock_sz_a_spot_em",
    "stock_bj_a_spot_em",
    "stock_hk_spot_em",
    "stock_zh_a_spot",
    "stock_us_spot_em",
    "stock_bid_ask_em",
    "stock_intraday_em",
    "forex_spot_em",
    "fund_etf_spot_em",
    "index_global_spot_em",
    "stock_zh_index_spot_em",
}

# Per-function TTL overrides (seconds)
CACHE_TTL: Dict[str, int] = {
    # Contract / instrument lists — rarely change
    "futures_hist_table_em": 86400,         # 24h
    "futures_contract_info_gfex": 86400,    # 24h
    "futures_fees_info": 86400,             # 24h
    "option_contract_info_ctp": 86400,      # 24h
    # Macro — updated monthly at most
    "macro_china_gdp_yearly": 86400,        # 24h
    "macro_china_cpi_yearly": 86400,
    "macro_china_cpi_monthly": 86400,
    "macro_china_ppi_yearly": 86400,
    "macro_china_pmi_yearly": 86400,
    "macro_china_m2_yearly": 86400,
    # Bond yield curves — updated daily
    "bond_china_yield": 3600,               # 1h
    "bond_zh_us_rate": 3600,
}

DEFAULT_TTL: int = 1800  # 30 minutes


def get_ttl(func_name: str) -> int:
    """Return the cache TTL for a function. 0 means do not cache."""
    if func_name in NO_CACHE:
        return 0
    return CACHE_TTL.get(func_name, DEFAULT_TTL)


# ---------------------------------------------------------------------------
# ResultCache
# ---------------------------------------------------------------------------

class ResultCache:
    """Simple in-memory cache with per-entry TTL."""

    def __init__(self) -> None:
        self._store: Dict[str, tuple] = {}  # key → (data, expire_timestamp)
        self.enabled: bool = True
        self.hits: int = 0
        self.misses: int = 0

    # -- key helpers --

    @staticmethod
    def _make_key(func_name: str, kwargs: dict) -> str:
        raw = json.dumps({"f": func_name, "k": kwargs}, sort_keys=True, default=str)
        return hashlib.sha256(raw.encode()).hexdigest()[:16]

    # -- public API --

    def get(self, func_name: str, kwargs: dict) -> Optional[Any]:
        """Retrieve a cached result.  Returns None on miss or expiry."""
        if not self.enabled:
            return None
        key = self._make_key(func_name, kwargs)
        entry = self._store.get(key)
        if entry is None:
            self.misses += 1
            return None
        data, expires = entry
        if time.time() > expires:
            del self._store[key]
            self.misses += 1
            return None
        self.hits += 1
        return data

    def put(self, func_name: str, kwargs: dict, data: Any, ttl: int) -> None:
        """Store a result with TTL (seconds)."""
        if not self.enabled or ttl <= 0:
            return
        key = self._make_key(func_name, kwargs)
        self._store[key] = (data, time.time() + ttl)

    def clear(self) -> None:
        """Remove all entries and reset counters."""
        self._store.clear()
        self.hits = 0
        self.misses = 0

    def stats(self) -> dict:
        total = self.hits + self.misses
        rate = f"{self.hits / total * 100:.1f}%" if total > 0 else "N/A"
        return {
            "enabled": self.enabled,
            "entries": len(self._store),
            "hits": self.hits,
            "misses": self.misses,
            "hit_rate": rate,
        }


# Global singleton
_result_cache = ResultCache()
