from __future__ import annotations

import copy
from typing import Any

from backend.features.screener.catalog import (
    ALLOWED_OPERATORS,
    BUILTIN_STRATEGIES,
    FACTOR_FIELDS,
    REGIMES,
)
from backend.features.screener.indicators import _matches, _percentile_map, _risk_flags


class FormulaEvaluator:
    def validate_formula(self, formula: dict[str, Any]) -> dict[str, Any]:
        if not isinstance(formula, dict):
            raise ValueError("选股公式必须是 JSON 对象。")
        result = copy.deepcopy(formula)
        universe = result.setdefault("universe", {})
        universe["exclude_st"] = bool(universe.get("exclude_st", True))
        universe["listed_days_min"] = max(0, min(5000, int(universe.get("listed_days_min", 120))))
        filters = result.setdefault("filters", [])
        if not isinstance(filters, list) or len(filters) > 20:
            raise ValueError("筛选条件必须是列表，且不能超过 20 条。")
        for condition in filters:
            field = condition.get("field")
            operator = condition.get("op")
            if field not in FACTOR_FIELDS:
                raise ValueError(f"不支持的选股因子：{field}")
            if operator not in ALLOWED_OPERATORS:
                raise ValueError(f"不支持的运算符：{operator}")
            if "value" not in condition:
                raise ValueError(f"因子 {field} 缺少比较值。")
        scores = result.setdefault("score", [])
        if not isinstance(scores, list) or not scores or len(scores) > 12:
            raise ValueError("评分因子应为 1 至 12 条。")
        for item in scores:
            if item.get("field") not in FACTOR_FIELDS:
                raise ValueError(f"不支持的评分因子：{item.get('field')}")
            item["weight"] = float(item.get("weight", 0))
            if item["weight"] <= 0 or item["weight"] > 1:
                raise ValueError("评分权重必须大于 0 且不超过 1。")
            if item.get("direction", "desc") not in {"asc", "desc"}:
                raise ValueError("评分方向只能是 asc 或 desc。")
            item["direction"] = item.get("direction", "desc")
        result["limit"] = max(1, min(50, int(result.get("limit", 15))))
        result["min_score"] = max(0, min(1, float(result.get("min_score", 0))))
        return result

    def apply_formula(
        self, rows: list[dict[str, Any]], formula: dict[str, Any], regime: str
    ) -> list[dict[str, Any]]:
        universe = formula["universe"]
        eligible = []
        score_fields = [item["field"] for item in formula["score"]]
        for row in rows:
            name = str(row.get("name") or "")
            if universe.get("exclude_st") and ("ST" in name.upper() or "退" in name):
                continue
            if row.get("listed_days", 0) < universe.get("listed_days_min", 0):
                continue
            if any(row.get(field) is None for field in score_fields):
                continue
            if all(_matches(row.get(item["field"]), item["op"], item["value"]) for item in formula["filters"]):
                eligible.append(row)
        if not eligible:
            return []

        percentiles = {
            item["field"]: _percentile_map(eligible, item["field"], item["direction"])
            for item in formula["score"]
        }
        weight_total = sum(item["weight"] for item in formula["score"])
        results = []
        for row in eligible:
            contributions = []
            score = 0.0
            for item in formula["score"]:
                percentile = percentiles[item["field"]].get(row["ts_code"], 0.5)
                points = percentile * item["weight"] / weight_total
                score += points
                contributions.append(
                    {
                        "field": item["field"],
                        "label": FACTOR_FIELDS[item["field"]],
                        "value": row.get(item["field"], 0),
                        "points": round(points * 100, 1),
                    }
                )
            if score < formula["min_score"]:
                continue
            contributions.sort(key=lambda item: item["points"], reverse=True)
            item = dict(row)
            item["score"] = round(score, 4)
            item["score_display"] = round(score * 100, 1)
            item["contributions"] = contributions
            item["reason"] = "、".join(entry["label"] for entry in contributions[:3])
            include_regime_risk = formula.get("meta", {}).get("library") != "curated"
            item["risk_flags"] = _risk_flags(row, regime, include_regime_risk)
            results.append(item)
        results.sort(key=lambda item: item["score"], reverse=True)
        return results[: formula["limit"]]


def compile_local_strategy(prompt: str, regime: str) -> dict[str, Any]:
    base = next((item for item in BUILTIN_STRATEGIES if regime in item["regimes"]), BUILTIN_STRATEGIES[1])
    formula = copy.deepcopy(base["formula"])
    description = prompt.strip() or base["description"]
    lowered = description.lower()
    if "低吸" in description:
        formula["filters"] = [item for item in formula["filters"] if item["field"] != "pct_chg"]
        formula["filters"].append({"field": "pct_chg", "op": "between", "value": [-3, 3]})
    if "放量" in description:
        formula["filters"].append({"field": "volume_ratio_5d", "op": ">=", "value": 1.2})
    if "强势" in description or "突破" in description:
        formula["filters"].append({"field": "return_5d", "op": ">=", "value": 5})
    if "低波" in description or "稳健" in description:
        formula["score"].append({"field": "volatility_10d", "weight": 0.18, "direction": "asc"})
    if "资金" in description or "主力" in description:
        formula["score"].append({"field": "net_flow_million", "weight": 0.18, "direction": "desc"})
    if "小市值" in description or "小盘" in description:
        formula["score"].append({"field": "circ_mv_billion", "weight": 0.15, "direction": "asc"})
    if "竞价" in description:
        formula["filters"].extend(
            [
                {"field": "auction_change", "op": "between", "value": [0.5, 8]},
                {"field": "auction_amount_million", "op": ">=", "value": 2},
            ]
        )
        formula["score"].extend(
            [
                {"field": "auction_volume_ratio", "weight": 0.20, "direction": "desc"},
                {"field": "auction_amount_million", "weight": 0.18, "direction": "desc"},
            ]
        )
    if "少量" in description or "精选" in description:
        formula["limit"] = min(formula["limit"], 8)
    formula["score"] = formula["score"][:12]
    return {
        "name": f"{REGIMES.get(regime, regime)}自定义策略",
        "description": description,
        "regimes": [regime],
        "formula": formula,
        "compiler": "local_template",
    }
