"""SP-A2 — NAS stock의 /api/webai/* 엔드포인트 in-memory TTLCache.

web-ai 측 캐시(stock_client._TTL)가 miss됐을 때도 NAS에서 같은 데이터를
KIS·LLM 재호출 없이 즉시 반환하기 위한 2-layer 캐시의 server 측.
V1+V2가 동시 호출해도 NAS는 1회만 계산.

TTL 정책 (spec §10 SP-A2):
- portfolio:  120s (web-ai TTL 180s 보다 짧게 — 변경 감지 가능)
- news:       600s (sentiment는 일 단위)
- screener:   180s
"""
from __future__ import annotations

import hashlib
import json
from typing import Any, Optional

from cachetools import TTLCache


PORTFOLIO_CACHE: TTLCache = TTLCache(maxsize=1, ttl=120.0)
NEWS_CACHE: TTLCache = TTLCache(maxsize=10, ttl=600.0)
SCREENER_CACHE: TTLCache = TTLCache(maxsize=10, ttl=180.0)


# ----- portfolio -----

def cache_get_portfolio() -> Optional[Any]:
    return PORTFOLIO_CACHE.get("result")


def cache_set_portfolio(value: Any) -> None:
    PORTFOLIO_CACHE["result"] = value


# ----- news-sentiment -----

def _news_key(date: Optional[str]) -> str:
    return date if date else "latest"


def cache_get_news(date: Optional[str]) -> Optional[Any]:
    return NEWS_CACHE.get(_news_key(date))


def cache_set_news(date: Optional[str], value: Any) -> None:
    NEWS_CACHE[_news_key(date)] = value


# ----- screener -----

def _screener_key(mode: str, top_n: int, weights: Optional[dict]) -> str:
    """mode + top_n + weights canonical hash. weights 객체 동등성을 키로."""
    if weights is None:
        w_repr = "none"
    else:
        # canonical: sorted keys → md5 hex (긴 weights도 짧은 키로)
        canon = json.dumps(weights, sort_keys=True, ensure_ascii=False)
        w_repr = hashlib.md5(canon.encode("utf-8")).hexdigest()[:12]
    return f"{mode}:{top_n}:{w_repr}"


def cache_get_screener(mode: str, top_n: int, weights: Optional[dict]) -> Optional[Any]:
    return SCREENER_CACHE.get(_screener_key(mode, top_n, weights))


def cache_set_screener(mode: str, top_n: int, weights: Optional[dict], value: Any) -> None:
    SCREENER_CACHE[_screener_key(mode, top_n, weights)] = value
