"""节点 0/6：动态种子词（seed）。

在 fetch 之前运行：收集「trending 派生 + 历史 safe 热点 + 月份/节日」上下文，
按 seed_provider 策略（static / mock / LLM）生成/合并种子词，注入 country_config 的
style.seeds / related.seed_keywords，供后续 fetch 的 related_queries 展开使用。

关键机制：动态种子词按 (国家, provider, 日期) 缓存（.cache/seeds/）。
同一天内多次运行使用同一套种子词 → related_queries 的 24h 缓存稳定命中，
避免「history 每次跑完都变 → 种子词震荡 → Google 反复全量重抓 → 429 限流」。

带 with_fallback：任何异常都降级为"仅用 yaml 静态种子"，不阻塞整图。
"""
import datetime
import hashlib
import json
import os
from pathlib import Path
from typing import Any, Dict

from graph.llms import get_backend
from graph.llms.mock_backend import COMMON_RISK_WORDS
from graph.paths import runtime_root
from graph.scoring import filter_person_names, filter_query_noise
from graph.seeds import get_seed_strategy
from graph.seeds.holidays import build_holiday_context
from graph.sources.google_trends_source import fetch_trending
from graph.validate import with_fallback

_CACHE_DIR = runtime_root() / ".cache" / "seeds"


def _cache_key(country: str, provider: str, cfg: Dict[str, Any]) -> str:
    """缓存键含「配置指纹」：改了种子相关参数（数量/上下文上限）即换新键重新生成，
    避免命中旧参数生成的种子；旧文件保留（不删缓存，取最新）。"""
    fp = hashlib.md5(
        json.dumps(
            {k: cfg.get(k) for k in ("max_seeds", "max_style_seeds", "max_related_seeds",
                                     "trending_context_limit", "history_limit")},
            sort_keys=True, ensure_ascii=False,
        ).encode("utf-8")
    ).hexdigest()[:8]
    return f"{country}-{provider}-{fp}-{datetime.date.today().isoformat()}"


def _cache_get(key: str):
    try:
        p = _CACHE_DIR / f"{key}.json"
        if p.exists():
            return json.loads(p.read_text(encoding="utf-8"))
    except Exception:
        pass
    return None


def _cache_set(key: str, val: Dict[str, Any]):
    try:
        _CACHE_DIR.mkdir(parents=True, exist_ok=True)
        (_CACHE_DIR / f"{key}.json").write_text(
            json.dumps(val, ensure_ascii=False), encoding="utf-8")
    except Exception:
        pass


@with_fallback("seed")
def seed_node(state: Dict[str, Any]) -> Dict[str, Any]:
    country = state["country"]
    config = state["config"]
    cc = dict(state.get("country_config") or {})
    errors = list(state.get("errors") or [])

    provider = (config.get("seed_provider") or "mock").strip().lower()
    cfg = config.get("seed_provider_cfg") or {}
    trending_limit = int(cfg.get("trending_context_limit", 15))
    history_limit = int(cfg.get("history_limit", 20))
    max_seeds = int(cfg.get("max_seeds", 0))
    max_style = int(cfg.get("max_style_seeds", 12))
    max_related = int(cfg.get("max_related_seeds", 12))
    guard = COMMON_RISK_WORDS + [b.lower() for b in (config.get("blacklist") or [])]

    ckey = _cache_key(country, provider, cfg)
    cached = _cache_get(ckey) if provider != "static" else None
    from_cache = cached is not None

    if cached is not None:
        res = cached
        context: Dict[str, Any] = {
            "country": country,
            "max_seeds": max_seeds,
            "max_style_seeds": max_style,
            "max_related_seeds": max_related,
        }
    else:
        # 1) 收集上下文
        context: Dict[str, Any] = {
            "country": country,
            "max_seeds": max_seeds,
            "max_style_seeds": max_style,
            "max_related_seeds": max_related,
        }
        try:
            tl = int((cc.get("trending") or {}).get("limit", 40))
            rows = fetch_trending(geo=country, limit=min(tl, 40))
            # 保留 rows 自带的 source=gt_trending，filter_person_names 的人名模式仅对该源生效
            kept, _ = filter_query_noise(rows, enabled=True)
            kept, _ = filter_person_names(kept)
            kept = [r for r in kept if not any(w and w in r["topic"].lower() for w in guard)]
            context["trending_seeds"] = [r["topic"] for r in kept][:trending_limit]
        except Exception as e:  # noqa: BLE001
            print(f"[seed] trending 上下文收集失败(跳过): {e}")
            context["trending_seeds"] = []

        try:
            p = os.path.join(state.get("output_dir", ""), "design_briefs.json")
            if os.path.exists(p):
                data = json.load(open(p, encoding="utf-8")).get("design_briefs", [])
                safe = [d for d in data if d.get("risk_level") == "safe"]
                safe.sort(key=lambda d: -(d.get("score") or 0))
                hrows = [{"topic": d["topic"], "source": "history"} for d in safe]
                hrows, _ = filter_person_names(hrows, pattern_sources={"history"})
                hrows = [r for r in hrows if not any(w and w in r["topic"].lower() for w in guard)]
                context["history_hotspots"] = [r["topic"] for r in hrows][:history_limit]
            else:
                context["history_hotspots"] = []
        except Exception as e:  # noqa: BLE001
            print(f"[seed] 历史热点读取失败(跳过): {e}")
            context["history_hotspots"] = []

        # 月份/节日（按国家：各国节日表不同）
        hol = build_holiday_context(country)
        context["season"] = hol["season"]
        context["year"] = hol["year"]
        context["month"] = hol["month"]
        context["date"] = hol["date"]
        context["month_themes"] = hol["month_themes"]
        context["upcoming_holidays"] = hol["upcoming_holidays"]

        # 2) 选策略 + LLM 后端
        strategy = get_seed_strategy(provider)
        llm_backend = None
        if provider != "static":
            llm_backend = get_backend(provider)
            # 注入 llm_screen 配置（api_key/base_url/model），否则 has_key 永远 False 降级 mock
            try:
                llm_backend.bind_config(config.get("llm_screen") or {})
            except Exception as e:  # noqa: BLE001
                print(f"[seed] LLM 配置绑定失败: {e}")
            if provider not in ("mock",) and not getattr(llm_backend, "has_key", False):
                print(f"[seed] {provider} 未配置 API key，降级 mock 规则生成种子词")
                llm_backend = get_backend("mock")

        # 3) 生成/合并种子词
        try:
            res = strategy.resolve(country, cc, context, llm_backend)
        except Exception as e:  # noqa: BLE001
            print(f"[seed] 策略解析失败，回退静态种子: {e}")
            res = {
                "style_seeds": list((cc.get("style", {}) or {}).get("seeds", []) or []),
                "related_seeds": list((cc.get("related", {}) or {}).get("seed_keywords", []) or []),
                "dynamic": False,
            }
        if provider != "static":
            _cache_set(ckey, res)

    # 4) 注入 cc
    style_block = dict(cc.get("style") or {})
    related_block = dict(cc.get("related") or {})
    style_block["seeds"] = res["style_seeds"]
    related_block["seed_keywords"] = res["related_seeds"]
    cc["style"] = style_block
    cc["related"] = related_block

    stats = dict(state.get("stats") or {})
    stats["seed"] = {
        "provider": provider,
        "dynamic": res.get("dynamic", False),
        "from_cache": from_cache,
        "style_count": len(res["style_seeds"]),
        "related_count": len(res["related_seeds"]),
        "trending_ctx": len(context.get("trending_seeds", [])),
        "history_ctx": len(context.get("history_hotspots", [])),
        "holidays": context.get("upcoming_holidays", []),
    }
    print(
        f"[seed] provider={provider}{'（当日缓存命中）' if from_cache else ''} "
        f"种子词 style={len(res['style_seeds'])} related={len(res['related_seeds'])}"
    )

    return {
        "country_config": cc,
        "seed_words": res,
        "stats": stats,
        "errors": errors,
    }
