from __future__ import annotations

import hashlib
import json
from typing import Any

from backend.features.screener.catalog import CatalogError, factor_catalog, validate_formula

PROMPT_VERSION = "screener:formula-compiler:v1"


def compile_messages(description: str) -> list[dict[str, str]]:
    factors = factor_catalog()["factors"]
    schema = {
        "universe": {"exclude_st": True, "listed_days_min": 120},
        "filters": [{"field": "amount_billion", "op": ">=", "value": 1}],
        "score": [{"field": "return_20d", "weight": 1.0, "direction": "desc"}],
        "limit": 30,
        "min_score": 0.5,
    }
    return [
        {
            "role": "system",
            "content": (
                "你是受控选股公式编译器。只输出一个JSON对象，不得输出Markdown、解释、股票或代码。"
                "只能使用给定因子；filters运算符仅限 >、>=、<、<=、==、!=、between、in；"
                "score权重必须为0至1小数且总和等于1；direction仅限asc或desc；"
                "limit为1至50，min_score为0至1。无法完全表达时选择最接近的已知因子，不得创造字段。"
                f"\nJSON结构：{json.dumps(schema, ensure_ascii=False, separators=(',', ':'))}"
                f"\n可用因子：{json.dumps(factors, ensure_ascii=False, separators=(',', ':'))}"
            ),
        },
        {"role": "user", "content": description},
    ]


def description_key(description: str) -> str:
    digest = hashlib.sha256(description.encode("utf-8")).hexdigest()[:16]
    return f"formula:{digest}"


def parse_compiled_formula(content: str) -> dict[str, Any]:
    text = content.strip()
    if text.startswith("```"):
        lines = text.splitlines()
        if len(lines) < 3 or lines[-1].strip() != "```":
            raise CatalogError("模型返回的公式格式无效")
        text = "\n".join(lines[1:-1]).strip()
    try:
        raw = json.loads(text)
    except json.JSONDecodeError as exc:
        raise CatalogError("模型未返回有效JSON公式") from exc
    if not isinstance(raw, dict):
        raise CatalogError("模型返回的公式必须是对象")
    return normalize_custom_formula(raw)


def normalize_custom_formula(raw: dict[str, Any]) -> dict[str, Any]:
    universe = raw.get("universe")
    filters = raw.get("filters")
    scores = raw.get("score")
    if not isinstance(universe, dict) or not isinstance(filters, list) or not isinstance(
        scores, list
    ):
        raise CatalogError("模型返回的公式结构不完整")
    exclude_st = universe.get("exclude_st", True)
    listed_days = universe.get("listed_days_min", 120)
    if not isinstance(exclude_st, bool) or not isinstance(listed_days, int):
        raise CatalogError("股票范围设置无效")
    normalized_scores = []
    for score in scores:
        if not isinstance(score, dict):
            raise CatalogError("评分因子结构无效")
        normalized_scores.append(
            {
                "field": score.get("field"),
                "weight": score.get("weight"),
                "direction": score.get("direction", "desc"),
            }
        )
    if any(not isinstance(item, dict) for item in filters):
        raise CatalogError("筛选条件结构无效")
    numeric_weights = [item["weight"] for item in normalized_scores]
    if all(
        isinstance(value, (int, float)) and not isinstance(value, bool)
        for value in numeric_weights
    ):
        total = sum(float(value) for value in numeric_weights)
        if abs(total - 100) <= 0.0001:
            for item in normalized_scores:
                item["weight"] = float(item["weight"]) / 100
    minimum = raw.get("min_score")
    if isinstance(minimum, (int, float)) and not isinstance(minimum, bool) and 1 < minimum <= 100:
        minimum = float(minimum) / 100
    formula = {
        "universe": {
            "exclude_st": exclude_st,
            "listed_days_min": listed_days,
        },
        "filters": [
            {
                "field": item.get("field"),
                "op": item.get("op"),
                "value": item.get("value"),
            }
            for item in filters
        ],
        "score": normalized_scores,
        "limit": raw.get("limit"),
        "min_score": minimum,
    }
    validate_formula(formula)
    return formula
