"""factor_scorer.py — Score all 21 trade features for profit correlation.

Phase 2: Computes per-feature importance by bucketing values and comparing
win rates. Produces a ranked leaderboard and auto-generates knowledge base pages.

Usage::

    from src.learning.factor_scorer import score_all_factors
    leaderboard = score_all_factors()
"""

from __future__ import annotations

import time as _time
from typing import Any


# All features tracked in trade_events (must match column names)
_NUMERIC_FACTORS = [
    "confidence", "adx_value", "atr_value", "vol_ratio", "spread",
    "agent_confidence", "strategy_win_rate_20", "strategy_profit_factor",
    "daily_loss_pct", "strategy_loss_streak",
]

_CATEGORICAL_FACTORS = [
    "side", "regime", "session", "agent_direction", "agent_aligned",
    "macro_authority", "risk_level", "strategy_id", "cooldown_active",
    "direction_quality",
]

# Bucket edges
_BUCKETS: dict[str, list[tuple[float, float]]] = {
    "confidence": [(0.0, 0.60), (0.60, 0.80), (0.80, 1.01)],
    "adx_value": [(0.0, 20.0), (20.0, 28.0), (28.0, 100.0)],
    "vol_ratio": [(0.0, 0.85), (0.85, 1.25), (1.25, 10.0)],
    "agent_confidence": [(0.0, 0.55), (0.55, 0.75), (0.75, 1.01)],
    "strategy_win_rate_20": [(0.0, 0.40), (0.40, 0.60), (0.60, 1.01)],
    "strategy_profit_factor": [(0.0, 1.0), (1.0, 2.0), (2.0, 100.0)],
    "strategy_loss_streak": [(0.0, 1.0), (1.0, 3.0), (3.0, 100.0)],
    "daily_loss_pct": [(0.0, 0.5), (0.5, 1.5), (1.5, 100.0)],
}


def _get_brain():
    try:
        from src.learning.trading_brain import get_brain
        return get_brain()
    except Exception:
        return None


def score_all_factors() -> list[dict[str, Any]]:
    """Score all 21 features and return a ranked leaderboard.

    Returns list of {feature, type, importance_score, buckets/stats, trend}.
    """
    brain = _get_brain()
    if brain is None:
        return []

    # Get baseline
    baseline = brain.query_win_rate()
    baseline_wr = baseline.get("win_rate", 0.5) if baseline.get("n", 0) >= 5 else 0.5
    results: list[dict[str, Any]] = []

    # ── Numeric factors ──
    for feature in _NUMERIC_FACTORS:
        buckets = _BUCKETS.get(feature)
        if buckets is None:
            buckets = [(0.0, 0.4), (0.4, 0.7), (0.7, 100.0)]

        bucket_stats = []
        wr_values = []
        for low, high in buckets:
            stats = brain.query_win_rate_by_bucket(feature, low, high)
            bucket_stats.append({
                "range": f"{low}-{high}",
                "n": stats["n"],
                "win_rate": stats["win_rate"],
            })
            if stats["n"] >= 3:
                wr_values.append(stats["win_rate"])

        if len(wr_values) >= 2:
            importance = round((max(wr_values) - min(wr_values)) * 100, 1)
        else:
            importance = 0.0

        # Determine the "good" direction
        best_bucket = max(bucket_stats, key=lambda b: b["win_rate"])
        worst_bucket = min(bucket_stats, key=lambda b: b["win_rate"])
        direction = f"higher={best_bucket['range']} (wr={best_bucket['win_rate']:.0%})"

        results.append({
            "feature": feature,
            "type": "numeric",
            "importance_score": importance,
            "buckets": bucket_stats,
            "direction": direction,
            "is_significant": importance >= 10.0,
            "trend": "stable",  # populated by factor_decay later
        })

    # ── Categorical factors ──
    for feature in _CATEGORICAL_FACTORS:
        best_wr = 0.0
        worst_wr = 1.0
        values_stats = []
        all_wr = []

        # Get distinct values
        all_trades = brain.query_recent_trades(1000)
        values: set[str] = set()
        for t in all_trades:
            v = str(t.get(feature, "") or "").strip()
            if v:
                values.add(v)

        for val in sorted(values):
            if not val:
                continue
            stats = brain.query_win_rate(**{feature: val})
            if stats["n"] >= 3:
                values_stats.append({
                    "value": val,
                    "n": stats["n"],
                    "win_rate": stats["win_rate"],
                })
                all_wr.append(stats["win_rate"])
                if stats["win_rate"] > best_wr:
                    best_wr = stats["win_rate"]
                if stats["win_rate"] < worst_wr:
                    worst_wr = stats["win_rate"]

        if len(all_wr) >= 2:
            importance = round((best_wr - worst_wr) * 100, 1)
        else:
            importance = 0.0

        results.append({
            "feature": feature,
            "type": "categorical",
            "importance_score": importance,
            "values": values_stats,
            "direction": f"best={max(values_stats, key=lambda v: v['win_rate']).get('value','?') if values_stats else '?'}",
            "is_significant": importance >= 10.0,
            "trend": "stable",
        })

    # Sort by importance
    results.sort(key=lambda r: r["importance_score"], reverse=True)
    return results


def compute_oos_validation(factors: list[dict[str, Any]], train_ratio: float = 0.7) -> list[dict[str, Any]]:
    """Validate factor importance on out-of-sample data.

    Splits trade_events by time: first 70% = train, last 30% = test.
    Computes importance on each subset. Flags factors that work in train but fail in test.
    """
    brain = _get_brain()
    if brain is None:
        return factors

    all_trades = brain.query_recent_trades(2000)
    if len(all_trades) < 20:
        for f in factors:
            f["oos_status"] = "insufficient_data"
        return factors

    split_idx = int(len(all_trades) * train_ratio)
    train_ids = [t["id"] for t in all_trades[:split_idx]]
    test_ids = [t["id"] for t in all_trades[split_idx:]]

    for f in factors:
        feature = f["feature"]
        ftype = f.get("type", "numeric")

        if ftype == "numeric":
            buckets = _BUCKETS.get(feature)
            if buckets is None:
                buckets = [(0.0, 0.4), (0.4, 0.7), (0.7, 100.0)]

            train_wrs = []
            test_wrs = []
            for low, high in buckets:
                train_stats = _query_by_ids(brain, feature, low, high, train_ids)
                test_stats = _query_by_ids(brain, feature, low, high, test_ids)
                if train_stats["n"] >= 3:
                    train_wrs.append(train_stats["win_rate"])
                if test_stats["n"] >= 3:
                    test_wrs.append(test_stats["win_rate"])

            if len(train_wrs) >= 2 and len(test_wrs) >= 2:
                train_imp = round((max(train_wrs) - min(train_wrs)) * 100, 1)
                test_imp = round((max(test_wrs) - min(test_wrs)) * 100, 1)

                if train_imp >= 10 and test_imp < 5:
                    f["oos_status"] = "overfit"
                elif train_imp >= 10 and test_imp >= 5:
                    f["oos_status"] = "validated"
                elif test_imp >= train_imp:
                    f["oos_status"] = "robust"
                else:
                    f["oos_status"] = "weak"
                f["train_importance"] = train_imp
                f["test_importance"] = test_imp
            else:
                f["oos_status"] = "insufficient_data"
        else:
            f["oos_status"] = "categorical_skip"

    return factors


def _query_by_ids(brain: Any, feature: str, low: float, high: float, ids: list[int]) -> dict:
    """Query win rate for a bucket, filtered by trade IDs."""
    try:
        if not ids:
            return {"n": 0, "win_rate": 0.0}
        # Use brain's raw connection for ID-filtered query
        placeholders = ",".join("?" for _ in ids)
        sql = f"""
            SELECT COUNT(*) AS n,
                   ROUND(AVG(CASE WHEN is_win=1 THEN 1.0 ELSE 0.0 END), 4) AS win_rate
            FROM trade_events
            WHERE id IN ({placeholders}) AND {feature} >= ? AND {feature} < ?
        """
        params = list(ids) + [low, high]
        row = brain._conn.execute(sql, params).fetchone()
        if row is None or row["n"] == 0:
            return {"n": 0, "win_rate": 0.0}
        return {"n": row["n"], "win_rate": round(row["win_rate"] or 0.0, 4)}
    except Exception:
        return {"n": 0, "win_rate": 0.0}


def generate_factor_leaderboard_md(factors: list[dict[str, Any]]) -> str | None:
    """Generate memory/factors/LEADERBOARD.md from factor scores."""
    try:
        from src.learning.knowledge_writer import _memory_dir, _ensure_subdir
        d = _ensure_subdir("factors")
        fp = d / "LEADERBOARD.md"

        lines = [
            "---",
            f"updated: {_time.strftime('%Y-%m-%dT%H:%M:%S')}",
            "tags: [factors, leaderboard]",
            "---",
            "",
            "# Factor Leaderboard",
            "",
            "> 按盈利贡献度排名 | 每20笔自动更新 | OOS = Out-of-Sample验证",
            "",
            "| Rank | Factor | Type | Score | Trend | OOS | Best Direction |",
            "|------|--------|------|-------|-------|-----|----------------|",
        ]

        for i, f in enumerate(factors[:15], 1):
            trend = f.get("trend", "stable")
            trend_icon = {"stable": "→", "improving": "↑", "decaying": "↓", "critical": "↓↓"}.get(trend, "→")
            oos = f.get("oos_status", "?")
            oos_icon = {"validated": "✅", "robust": "💪", "overfit": "⚠️", "weak": "❌", "insufficient_data": "?", "categorical_skip": "-"}.get(oos, "?")
            direction = str(f.get("direction", ""))[:40]
            lines.append(
                f"| {i} | [[factors/{f['feature']}]] | {f['type']} | "
                f"{f['importance_score']:.1f} | {trend_icon} | {oos_icon} | {direction} |"
            )

        lines.extend([
            "",
            "## Top 5 Profit Drivers",
            "",
        ])
        for f in factors[:5]:
            lines.append(f"- [[factors/{f['feature']}]] — score={f['importance_score']:.1f}")

        lines.extend([
            "",
            "## Bottom 5 Drag Factors",
            "",
        ])
        for f in factors[-5:]:
            lines.append(f"- [[factors/{f['feature']}]] — score={f['importance_score']:.1f}")

        fp.write_text("\n".join(lines), encoding="utf-8")

        # Also generate per-factor pages for top/bottom 5
        for f in factors[:5] + factors[-5:]:
            _write_factor_page(d, f)

        return str(fp)
    except Exception:
        return None


def _write_factor_page(d: Any, f: dict[str, Any]) -> None:
    feature = f["feature"]
    fp = d / f"{feature}.md"
    content = f"""---
feature: {feature}
type: {f['type']}
score: {f['importance_score']}
trend: {f.get('trend', 'stable')}
significant: {f['is_significant']}
updated: {_time.strftime('%Y-%m-%dT%H:%M:%S')}
tags: [factor, {feature}]
---

# {feature}

## Score
- Importance: {f['importance_score']:.1f}/100
- Type: {f['type']}
- Direction: {f.get('direction', 'N/A')}

## Related
- Back to [[factors/LEADERBOARD]]
- Back to [[INDEX]]
"""
    fp.write_text(content, encoding="utf-8")
