from __future__ import annotations

from typing import Any

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

FACTOR_MINIMUM_COVERAGE = {
    "valuation": 0.95,
    "financial": 0.90,
    "moneyflow": 0.90,
    "industry": 0.95,
    "auction": 0.90,
    "popularity": 0.95,
    "institutions": 0.95,
    "earnings": 0.95,
    "market": 0.98,
}

FACTOR_DATASET = {
    **dict.fromkeys(
        {
            "pe_ttm",
            "pb",
            "ps_ttm",
            "dividend_yield_ttm",
            "circ_mv_billion",
            "total_mv_billion",
            "turnover_rate",
            "turnover_5d",
        },
        "valuation",
    ),
    **dict.fromkeys(
        {
            "dividend_years",
            "roe",
            "roa",
            "roic",
            "gross_margin",
            "netprofit_yoy",
            "revenue_yoy",
            "ocf_to_opincome",
            "financial_risk",
            "factor_value_score",
            "factor_growth_score",
            "factor_quality_score",
            "multi_factor_composite",
        },
        "financial",
    ),
    **dict.fromkeys(
        {
            "earnings_surprise_pct",
            "earnings_days_since_announce",
            "earnings_event_quality",
        },
        "earnings",
    ),
    **dict.fromkeys(
        {
            "net_flow_million",
            "large_flow_million",
            "net_flow_5d_million",
            "flow_to_circ_mv_5d",
            "sector_net_flow_5d_million",
            "sector_flow_rank",
        },
        "moneyflow",
    ),
    **dict.fromkeys(
        {
            "sector_strength",
            "sector_return_5d",
            "sector_return_20d",
            "sector_momentum_rank",
            "sector_stock_momentum_rank",
            "sector_prosperity_rank",
            "sector_trend_rank",
            "sector_crowding_rank",
            "sector_composite_score",
            "sector_limit_count",
            "sector_up_count",
            "sector_breadth_ma20",
        },
        "industry",
    ),
    **dict.fromkeys(
        {
            "auction_change",
            "auction_amount_million",
            "auction_turnover_rate",
            "auction_volume_ratio",
        },
        "auction",
    ),
    **dict.fromkeys(
        {"popularity_score", "popularity_rank_change", "popularity_dual_source"},
        "popularity",
    ),
    **dict.fromkeys(
        {"institution_net_buy_million", "institution_seat_count"},
        "institutions",
    ),
}


def execute_formula(
    rows: list[dict[str, Any]],
    formula: dict[str, Any],
    coverage: dict[str, float],
) -> dict[str, Any]:
    validate_formula(formula)
    required = sorted({str(item["field"]) for item in [*formula["filters"], *formula["score"]]})
    universe = formula.get("universe") or {}
    universe_rows = [
        row
        for row in rows
        if not (universe.get("exclude_st", True) and bool(row.get("is_st")))
        and int(row.get("listed_days") or 0) >= int(universe.get("listed_days_min") or 0)
    ]
    missing_datasets = sorted(
        {
            dataset
            for field in required
            if (dataset := FACTOR_DATASET.get(field, "market"))
            and coverage.get(dataset, 0) < FACTOR_MINIMUM_COVERAGE[dataset]
        }
    )
    field_coverage = {
        field: (
            sum(row.get(field) is not None for row in universe_rows) / len(universe_rows)
            if universe_rows
            else 0.0
        )
        for field in required
    }
    missing_fields = sorted(
        field
        for field in required
        if field_coverage[field] < FACTOR_MINIMUM_COVERAGE[FACTOR_DATASET.get(field, "market")]
    )
    if missing_datasets or missing_fields:
        return {
            "status": "data_incomplete",
            "items": [],
            "missing_datasets": missing_datasets,
            "missing_fields": missing_fields,
            "field_coverage": field_coverage,
            "eligible_count": 0,
        }

    eligible = []
    for row in universe_rows:
        if any(row.get(field) is None for field in required):
            continue
        if all(
            _matches(row[item["field"]], item["op"], item["value"]) for item in formula["filters"]
        ):
            eligible.append(row)
    if not eligible:
        return {
            "status": "no_signal",
            "items": [],
            "missing_datasets": [],
            "missing_fields": [],
            "field_coverage": field_coverage,
            "eligible_count": 0,
        }

    percentiles = {
        item["field"]: _percentiles(eligible, item["field"], item["direction"])
        for item in formula["score"]
    }
    labels = factor_catalog()["factors"]
    results = []
    for row in eligible:
        contributions = []
        total = 0.0
        identifier = str(row["identifier"])
        for item in formula["score"]:
            points = percentiles[item["field"]][identifier] * float(item["weight"])
            total += points
            contributions.append(
                {
                    "field": item["field"],
                    "label": labels[item["field"]],
                    "value": row[item["field"]],
                    "points": round(points * 100, 1),
                }
            )
        if total < float(formula["min_score"]):
            continue
        contributions.sort(key=lambda item: (-item["points"], item["field"]))
        results.append(
            {
                "identifier": identifier,
                "code": row["code"],
                "name": row["name"],
                "sector": row.get("sector") or "",
                "close": row.get("close"),
                "pct_chg": row.get("pct_chg"),
                "amount_billion": row.get("amount_billion"),
                "score": round(total, 6),
                "score_display": round(total * 100, 1),
                "contributions": contributions,
                "reason": "、".join(item["label"] for item in contributions[:3]),
                "risk_flags": _risk_flags(row),
            }
        )
    results.sort(key=lambda item: (-item["score"], item["identifier"]))
    results = results[: int(formula["limit"])]
    return {
        "status": "completed" if results else "no_signal",
        "items": results,
        "missing_datasets": [],
        "missing_fields": [],
        "field_coverage": field_coverage,
        "eligible_count": len(eligible),
    }


def _percentiles(rows: list[dict[str, Any]], field: str, direction: str) -> dict[str, float]:
    ordered = sorted(
        rows,
        key=lambda row: (
            float(row[field]) if direction == "asc" else -float(row[field]),
            str(row["identifier"]),
        ),
    )
    if len(ordered) == 1:
        return {str(ordered[0]["identifier"]): 1.0}
    return {
        str(row["identifier"]): 1 - index / (len(ordered) - 1) for index, row in enumerate(ordered)
    }


def _matches(value: Any, operator: str, expected: Any) -> bool:
    if operator == "between":
        return expected[0] <= value <= expected[1]
    if operator == "in":
        return value in expected
    return {
        ">": value > expected,
        ">=": value >= expected,
        "<": value < expected,
        "<=": value <= expected,
        "==": value == expected,
        "!=": value != expected,
    }[operator]


def _risk_flags(row: dict[str, Any]) -> list[str]:
    flags = []
    if row.get("financial_risk"):
        flags.append("存在已确认财务风险")
    if row.get("volatility_10d") is not None and float(row["volatility_10d"]) >= 6:
        flags.append("近期波动偏高")
    if row.get("pct_chg") is not None and float(row["pct_chg"]) >= 9:
        flags.append("当日涨幅较高")
    return flags
