"""strategy_reflector.py — Evaluation-driven strategy improvement engine.

Shared by both closed loops:
  Loop 1 (GUI): evaluate → improve_draft → re-evaluate (max 3 iterations)
  Loop 2 (Sandbox): sandbox fail → reflect_on_metrics → regenerate (max 5 iterations)

Provides:
  - Standard evaluation format (normalize_evaluation)
  - Draft improvement (improve_strategy_draft) — GUI loop
  - Sandbox reflection (reflect_on_sandbox_failure) — Sandbox loop
  - Root cause classifier (classify_failure_mode)
"""

from __future__ import annotations

import json
from typing import Any


# ═══════════════════════════════════════════════════════════════════════════════
# Standard Evaluation Format
# ═══════════════════════════════════════════════════════════════════════════════

def normalize_evaluation(
    source: str,
    raw_evaluation: dict[str, Any],
    raw_metrics: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Convert any evaluation source into the standard format consumable by
    the improvement and reflection engines.

    Args:
        source: "gui" | "sandbox" | "live_pnl" | "arena"
        raw_evaluation: Source-specific evaluation dict.
        raw_metrics: Optional raw numeric metrics (sandbox report, PnL stats).

    Returns a normalized dict with:
        verdict: "pass" | "needs_improve"
        weaknesses: list of dimension labels that need improvement
        dimensions: standardized per-dimension scores (0-100)
        hints: human-readable improvement suggestions
    """
    verdict = str(raw_evaluation.get("verdict", "needs_improve") or "needs_improve").strip().lower()
    dimensions: dict[str, dict[str, Any]] = {}
    weaknesses: list[str] = []
    hints: list[str] = []

    if source == "gui":
        score = raw_evaluation.get("score", {}) if isinstance(raw_evaluation.get("score", {}), dict) else {}
        for dim in ("executability", "robustness", "risk_control"):
            val = int(score.get(dim, 50) or 50)
            dimensions[dim] = {"score": val}
            if val < 55:
                weaknesses.append(dim)
        if verdict != "pass":
            hints.append(str(raw_evaluation.get("advice", "") or "草稿需要补充风控和入场/出场规则"))

    elif source == "sandbox":
        metrics = raw_metrics or {}
        trades = int(metrics.get("total_trades", 0) or 0)
        wr = float(metrics.get("win_rate", 0) or 0)
        pf = float(metrics.get("profit_factor", 0) or 0)
        dd = float(metrics.get("max_drawdown_pct", 100) or 100)
        sharpe = float(metrics.get("sharpe_ratio", 0) or 0)
        guard = bool(metrics.get("guards_tripped", False))

        dimensions["profitability"] = {"score": _scale(wr * 100, 40, 60, 100), "raw": {"win_rate": wr, "profit_factor": pf, "sharpe": sharpe}}
        dimensions["risk_control"] = {"score": _scale(100 - dd, 50, 85, 100), "raw": {"max_drawdown_pct": dd, "guards_tripped": guard}}
        dimensions["executability"] = {"score": _scale(trades, 10, 30, 100), "raw": {"total_trades": trades}}
        dimensions["robustness"] = {"score": _scale(pf, 1.0, 2.0, 100), "raw": {"profit_factor": pf}}

        if trades < 10:
            weaknesses.append("executability")
            hints.append(f"交易笔数不足 ({trades}/10)，信号触发频率过低")
        if wr < 40.0:
            weaknesses.append("profitability")
            hints.append(f"胜率过低 ({wr:.1%})，入场方向或时机需要调整")
        if pf < 1.0:
            weaknesses.append("profitability")
            hints.append(f"盈亏因子 < 1.0 ({pf:.2f})，止损/止盈比例失衡")
        if dd > 50.0:
            weaknesses.append("risk_control")
            hints.append(f"最大回撤过高 ({dd:.1f}%)，需增加仓位或止损约束")
        if guard:
            weaknesses.append("risk_control")
            hints.append("触发沙盒熔断，连亏或回撤超限")
        if not weaknesses:
            weaknesses.append("profitability")
            hints.append("多项指标处于临界值，需整体优化")

    elif source == "live_pnl":
        wr = float(raw_metrics.get("win_rate", 0) or 0) if raw_metrics else 0
        pf = float(raw_metrics.get("profit_factor", 0) or 0) if raw_metrics else 0
        dimensions["profitability"] = {"score": _scale(wr * 100, 40, 60, 100), "raw": raw_metrics or {}}
        dimensions["consistency"] = {"score": _scale(pf, 0.8, 1.5, 100), "raw": raw_metrics or {}}
        if wr < 0.35:
            weaknesses.append("profitability")
            hints.append("实盘胜率显著偏低，策略可能过拟合或市况不再匹配")
        if pf < 0.8:
            weaknesses.append("profitability")
            hints.append("实盘盈亏因子过低，离场规则需要调整")

    else:
        dimensions["overall"] = {"score": 50}
        weaknesses.append("profitability")
        hints.append("缺少评估数据，请先运行沙盒或AI评估")

    if not hints:
        hints.append("部分维度需要改进，请参考评估详情")

    return {
        "verdict": verdict,
        "source": source,
        "weaknesses": list(set(weaknesses)),
        "dimensions": dimensions,
        "hints": hints,
    }


def _scale(value: float, low: float, high: float, max_score: float = 100.0) -> int:
    """Linear scale: value between low and high maps to 0..max_score."""
    if value <= low:
        return 10
    if value >= high:
        return int(max_score)
    return max(5, int((value - low) / (high - low) * max_score))


# ═══════════════════════════════════════════════════════════════════════════════
# Failure Mode Classifier (no LLM — pure rule-based)
# ═══════════════════════════════════════════════════════════════════════════════

def classify_failure_mode(normalized: dict[str, Any]) -> str:
    """Classify the primary failure mode from normalized evaluation.

    Returns one of:
      SIGNAL_TOO_RARE  — not enough trades generated
      DIRECTION_WRONG  — win rate too low
      EXIT_INADEQUATE  — profit factor < 1.0
      RISK_TOO_HIGH    — drawdown too high or guards tripped
      OVERFITTING      — live PnL much worse than sandbox
      MULTI_FACTOR     — multiple dimensions weak
    """
    weaknesses = set(normalized.get("weaknesses", []))
    dims = normalized.get("dimensions", {})

    if len(weaknesses) >= 3:
        return "MULTI_FACTOR"

    if "executability" in weaknesses:
        trades = (dims.get("executability", {}).get("raw", {}).get("total_trades", 0) or 0)
        if trades < 5:
            return "SIGNAL_TOO_RARE"

    if "risk_control" in weaknesses and "profitability" not in weaknesses:
        return "RISK_TOO_HIGH"

    if "profitability" in weaknesses:
        raw = dims.get("profitability", {}).get("raw", {})
        wr = float(raw.get("win_rate", 0) or 0)
        pf = float(raw.get("profit_factor", 0) or 0)
        if pf < 1.0 and wr >= 0.40:
            return "EXIT_INADEQUATE"
        if wr < 0.40:
            return "DIRECTION_WRONG"

    return "MULTI_FACTOR"


# ═══════════════════════════════════════════════════════════════════════════════
# Improvement prompts (static templates — LLM fills in the details)
# ═══════════════════════════════════════════════════════════════════════════════

_IMPROVE_DRAFT_SYSTEM = (
    "你是黄金自动交易系统的 AI 策略开发师，负责根据评估反馈定向改进策略草稿。"
    "只返回一个 JSON 对象，不要解释。"
    "字段至少包含 strategy_name, market_hypothesis, entry_rule, exit_rule, "
    "risk_constraints, base_engine, params。"
    "risk_constraints 至少包含 max_position_size, max_open_positions, min_confidence。"
    "base_engine 只能从 ema_macd, wyckoff, composite 中选择最合适的。"
    "params 必须是扁平对象，保留 6-10 个关键参数。"
    "仔细针对反馈中指出的弱点维度进行定向改进，其他维度保持不变。"
)


def build_improve_draft_prompt(
    current_optimized: dict[str, Any],
    normalized_eval: dict[str, Any],
) -> str:
    """Build the user prompt for draft improvement (GUI loop)."""
    weaknesses = normalized_eval.get("weaknesses", [])
    hints = normalized_eval.get("hints", [])
    return json.dumps({
        "instruction": "根据以下评估反馈，定向改进策略草稿中的弱点维度",
        "weaknesses_to_fix": weaknesses,
        "improvement_hints": hints,
        "current_draft": current_optimized,
        "requirements": [
            "仅改进弱点相关字段，其他字段保持不变",
            "如 weakness 含 risk_control，必须在 risk_constraints 中增加具体的风控参数",
            "如 weakness 含 robustness，必须补充 invalid_condition 或失效场景",
            "如 weakness 含 executability，必须补充具体的入场价格计算逻辑",
        ],
    }, ensure_ascii=False, indent=2)


_REFLECT_SANDBOX_SYSTEM = (
    "你是黄金自动交易系统的策略反思专家，负责分析沙盒回测失败原因并输出改进方案。"
    "只返回一个 JSON 对象，不要解释。"
)


def build_reflect_sandbox_prompt(
    strategy_id: str,
    strategy_code: str,
    normalized_eval: dict[str, Any],
    raw_metrics: dict[str, Any],
) -> str:
    """Build the user prompt for sandbox failure reflection (Sandbox loop)."""
    failure_mode = classify_failure_mode(normalized_eval)
    weaknesses = normalized_eval.get("weaknesses", [])
    hints = normalized_eval.get("hints", [])

    mode_guidance = {
        "SIGNAL_TOO_RARE": "策略信号太少（交易笔数不足10笔）。请放宽入场条件、增加适用市况、降低信心度门槛，使策略在更多K线上产生信号。",
        "DIRECTION_WRONG": "策略方向判断错误（胜率过低）。请检查EMA方向逻辑、增加趋势过滤器、加入多时间框架确认、或反转入场方向条件。",
        "EXIT_INADEQUATE": "策略离场规则不合理（盈亏因子<1.0）。请调整止损/止盈ATR倍率、增加移动止损、收紧止损或放宽止盈目标。",
        "RISK_TOO_HIGH": "策略风险控制不足（回撤过大或触发熔断）。请降低单笔仓位、增加最大回撤限制、缩短持仓时间、增加冷却期。",
        "MULTI_FACTOR": "策略在多个维度表现不佳。请全面审视入场逻辑、离场规则、风控参数和市况过滤条件。",
        "OVERFITTING": "策略可能过拟合历史数据。请简化入场条件、减少参数数量、增加市况过滤、避免过度优化特定K线形态。",
    }

    return json.dumps({
        "instruction": "分析以下策略的沙盒回测失败原因，输出根因分析和改进后的 on_market_snapshot 方法体",
        "strategy_id": strategy_id,
        "failure_mode": failure_mode,
        "failure_guidance": mode_guidance.get(failure_mode, mode_guidance["MULTI_FACTOR"]),
        "weaknesses": weaknesses,
        "hints": hints,
        "sandbox_metrics": raw_metrics,
        "current_code": strategy_code,
        "output_requirements": {
            "root_cause": "1-2句话根因分析",
            "failure_mode": failure_mode,
            "improvements": "具体改进措施列表（3-5条）",
            "improved_signal_body": "改进后的 on_market_snapshot 方法体 Python 代码（不含类定义和装饰器）",
            "confidence": "改进后的预期改善程度 (0.0-1.0)",
        },
    }, ensure_ascii=False, indent=2)
