from __future__ import annotations

from typing import Any


FREQUENCY_VALIDITY_DAYS = {
    "每日": 1,
    "每日9:25": 1,
    "每周": 5,
    "双周": 10,
    "月度": 20,
    "事件驱动": 5,
}


def signal_validity(mode: str, formula: dict[str, Any] | None) -> dict[str, Any]:
    if mode == "smart":
        return {
            "type": "until_regime_change",
            "label": "当前阶段不变时有效",
        }
    meta = (formula or {}).get("meta") or {}
    frequency = str(meta.get("frequency") or "每日")
    days = FREQUENCY_VALIDITY_DAYS.get(frequency, 1)
    return {
        "type": "trading_days",
        "days": days,
        "label": f"{days}个交易日",
    }


def attach_strategy_validity(strategy: dict[str, Any]) -> None:
    formula = strategy.get("formula") or {}
    meta = formula.setdefault("meta", {})
    mode = "curated" if meta.get("library") == "curated" else "smart"
    meta["signal_validity"] = signal_validity(mode, formula)


def build_candidate_archive(
    runs: list[dict[str, Any]],
    strategies: list[dict[str, Any]],
    trading_dates: list[str],
    as_of_date: str,
    as_of_regime: str,
    history_limit: int = 1200,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    strategy_formulas = {
        str(item.get("name") or ""): item.get("formula") or {}
        for item in strategies
    }
    date_positions = {trade_date: index for index, trade_date in enumerate(trading_dates)}
    as_of_position = date_positions.get(as_of_date, len(trading_dates) - 1)
    history: dict[tuple[str, str, str], dict[str, Any]] = {}
    active: dict[tuple[str, str], dict[str, Any]] = {}

    ordered_runs = sorted(
        runs,
        key=lambda item: (
            str((item.get("meta") or {}).get("trade_date") or ""),
            int((item.get("meta") or {}).get("run_id") or 0),
        ),
        reverse=True,
    )
    for result in ordered_runs:
        meta = result.get("meta") or {}
        mode = str(meta.get("mode") or "smart")
        if mode not in {"smart", "curated", "quant"}:
            continue
        selection_date = str(meta.get("trade_date") or "").replace("-", "")
        strategy_name = str(meta.get("strategy_name") or "未命名策略")
        regime = str(meta.get("regime") or "")
        formula = result.get("formula") or strategy_formulas.get(strategy_name) or {}
        validity = signal_validity(mode, formula)
        valid, valid_until, remaining = _signal_state(
            validity,
            selection_date,
            regime,
            trading_dates,
            date_positions,
            as_of_position,
            as_of_regime,
        )
        hit = {
            "selection_date": selection_date,
            "strategy_name": strategy_name,
            "regime": regime,
            "run_id": int(meta.get("run_id") or 0),
            "validity": validity,
            "valid_until": valid_until,
            "remaining_trading_days": remaining,
            "active": valid,
        }
        for candidate in result.get("candidates") or []:
            code = str(candidate.get("code") or "")
            if not code:
                continue
            history_key = (mode, selection_date, code)
            history_row = history.setdefault(
                history_key,
                _archive_row(candidate, mode, selection_date),
            )
            candidate_hit = {**hit, "score_display": candidate.get("score_display")}
            _append_hit(history_row, candidate_hit)
            if valid:
                active_key = (mode, code)
                active_row = active.get(active_key)
                if active_row is None:
                    active_row = _archive_row(candidate, mode, selection_date)
                    active[active_key] = active_row
                _append_hit(active_row, candidate_hit)

    history_rows = sorted(
        history.values(),
        key=lambda item: (item["selection_date"], _numeric_score(item["score_display"])),
        reverse=True,
    )[: max(1, int(history_limit))]
    active_rows = sorted(
        active.values(),
        key=lambda item: (item["selection_date"], _numeric_score(item["score_display"])),
        reverse=True,
    )
    for row in [*history_rows, *active_rows]:
        _finalize_archive_row(row)
    return active_rows, history_rows


def _signal_state(
    validity: dict[str, Any],
    selection_date: str,
    regime: str,
    trading_dates: list[str],
    date_positions: dict[str, int],
    as_of_position: int,
    as_of_regime: str,
) -> tuple[bool, str, int | None]:
    if validity.get("type") == "until_regime_change":
        return regime == as_of_regime, "", None
    days = max(1, int(validity.get("days") or 1))
    selected_position = date_positions.get(selection_date)
    if selected_position is None or as_of_position < selected_position:
        return False, "", 0
    elapsed = as_of_position - selected_position
    valid = elapsed < days
    valid_position = selected_position + days - 1
    valid_until = (
        trading_dates[valid_position]
        if 0 <= valid_position < len(trading_dates)
        else ""
    )
    return valid, valid_until, max(0, days - elapsed) if valid else 0


def _archive_row(
    candidate: dict[str, Any], mode: str, selection_date: str
) -> dict[str, Any]:
    return {
        "mode": mode,
        "selection_date": selection_date,
        "code": str(candidate.get("code") or ""),
        "name": str(candidate.get("name") or ""),
        "sector": str(candidate.get("sector") or ""),
        "score_display": candidate.get("score_display"),
        "pct_chg": candidate.get("pct_chg"),
        "return_5d": candidate.get("return_5d"),
        "hits": [],
    }


def _append_hit(row: dict[str, Any], hit: dict[str, Any]) -> None:
    identity = (hit["strategy_name"], hit["regime"], hit["run_id"])
    existing = {
        (item["strategy_name"], item["regime"], item["run_id"])
        for item in row["hits"]
    }
    if identity not in existing:
        row["hits"].append(dict(hit))


def _finalize_archive_row(row: dict[str, Any]) -> None:
    hits = row.get("hits") or []
    active_hits = [item for item in hits if item.get("active")]
    row["matched_strategies"] = list(
        dict.fromkeys(item["strategy_name"] for item in hits)
    )
    row["regimes"] = list(dict.fromkeys(item["regime"] for item in hits if item["regime"]))
    row["active"] = bool(active_hits)
    row["status"] = "持续有效" if active_hits else "已到期"
    labels = list(
        dict.fromkeys(item["validity"]["label"] for item in (active_hits or hits))
    )
    row["validity_label"] = " / ".join(labels)


def _numeric_score(value: Any) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return -1.0
