"""lesson_learner.py — Mine loss patterns and generate avoidance rules.

Phase 3: discovers conditions that consistently produce losses, then generates
avoidance rules that the risk manager can consume to block or penalize signals.

Usage::

    from src.learning.lesson_learner import learn_lessons, get_active_avoidance_rules
    lessons = learn_lessons(min_samples=5, max_win_rate=0.35)
    rules = get_active_avoidance_rules()
"""

from __future__ import annotations

from typing import Any


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


_LESSON_TYPES = {
    "COUNTER_AGENT_TREND": {
        "label": "逆势交易失败",
        "conditions": [{"feature": "agent_aligned", "op": "=", "value": 0},
                       {"feature": "regime", "op": "=", "value": "TRENDING"}],
        "rule": "当 AI 方向为 LONG/SHORT 且市场为 TRENDING 时, 禁止逆势信号",
        "action": "require_alignment_or_skip",
    },
    "HIGH_CONF_LOW_ADX": {
        "label": "高信心低ADX陷阱",
        "conditions": [{"feature": "confidence", "op": ">=", "value": 0.75},
                       {"feature": "adx_value", "op": "<", "value": 20}],
        "rule": "ADX<20 时高信心信号不可靠, 降低信心度或跳过",
        "action": "cap_confidence_at_0.70",
    },
    "ASIA_HIGH_VOL": {
        "label": "亚洲盘高波动陷阱",
        "conditions": [{"feature": "session", "op": "=", "value": "ASIA"},
                       {"feature": "vol_ratio", "op": ">=", "value": 1.5}],
        "rule": "亚洲盘 + 高波动时信号质量差, 限制入场或缩小仓位",
        "action": "limit_only_or_reduce_size",
    },
    "LOSS_STREAK_OVERTRADE": {
        "label": "连亏后过度交易",
        "conditions": [{"feature": "strategy_loss_streak", "op": ">=", "value": 3}],
        "rule": "连亏 >= 3 时信号胜率极低, 暂停或极小仓位",
        "action": "require_cooldown_or_min_size",
    },
}


def learn_lessons(
    min_samples: int = 5,
    max_win_rate: float = 0.35,
) -> list[dict[str, Any]]:
    """Discover loss patterns from learning_brain and generate avoidance rules.

    Two discovery methods:
      1. Template-based: Check pre-defined lesson templates against the data
      2. Data-driven: Find feature-value combinations with win_rate < max_win_rate

    Returns list of lessons, each with:
        {lesson_type, conditions, loss_count, total_pnl, win_rate, avoidance_rule, applied}
    """
    brain = _get_brain()
    if brain is None:
        return []

    lessons: list[dict[str, Any]] = []

    # ── Method 1: Template-based ──
    for lesson_type, template in _LESSON_TYPES.items():
        conds = template["conditions"]
        query_kwargs = {}
        for c in conds:
            if c["op"] == "=":
                query_kwargs[c["feature"]] = c["value"]
            # Range conditions handled separately below

        stats = brain.query_win_rate(**query_kwargs) if query_kwargs else {"n": 0}
        if stats["n"] >= min_samples and stats["win_rate"] < max_win_rate:
            if not _lesson_exists(lesson_type):
                lessons.append({
                    "lesson_type": lesson_type,
                    "label": template["label"],
                    "conditions": conds,
                    "loss_count": stats["n"] - stats["wins"],
                    "total_pnl": round(stats["avg_pnl"] * stats["n"], 2),
                    "win_rate": stats["win_rate"],
                    "avoidance_rule": template["rule"],
                    "action": template["action"],
                    "applied": 0,
                })

    # ── Method 2: Data-driven (session risk patterns) ──
    by_rs = brain.query_by_regime_session()
    for g in by_rs:
        if g.get("n", 0) >= min_samples and g.get("win_rate", 1.0) < max_win_rate:
            lt = f"LOW_WR_{g.get('regime', 'X')}_{g.get('session', 'X')}"
            if not _lesson_exists(lt):
                lessons.append({
                    "lesson_type": lt,
                    "label": f"{g.get('regime')}/{g.get('session')} 低胜率",
                    "conditions": [
                        {"feature": "regime", "op": "=", "value": g["regime"]},
                        {"feature": "session", "op": "=", "value": g["session"]},
                    ],
                    "loss_count": g["n"] - int(g["n"] * g["win_rate"]),
                    "total_pnl": round(g.get("avg_pnl", 0) * g["n"], 2),
                    "win_rate": g["win_rate"],
                    "avoidance_rule": f"在 {g['regime']}/{g['session']} 组合下胜率仅 {g['win_rate']:.0%}, 降低仓位或跳过",
                    "action": "reduce_size_or_skip",
                    "applied": 0,
                })

    # Persist to brain for risk_manager consumption
    for lesson in lessons:
        _persist_lesson(brain, lesson)
        # ── Write to knowledge base ──
        try:
            from src.learning.knowledge_writer import write_lesson_note
            write_lesson_note(lesson)
        except Exception:
            pass

    return lessons


def get_active_avoidance_rules() -> list[dict[str, Any]]:
    """Return all lessons marked as applied that the risk manager should enforce."""
    brain = _get_brain()
    if brain is None:
        return []

    try:
        rows = brain._conn.execute(
            "SELECT * FROM loss_lessons WHERE applied=1 ORDER BY win_rate ASC"
        ).fetchall()
        return [dict(r) for r in rows]
    except Exception:
        return []


def apply_lesson(lesson_type: str) -> bool:
    """Mark a lesson as applied (deployed to risk manager)."""
    brain = _get_brain()
    if brain is None:
        return False
    try:
        brain._conn.execute(
            "UPDATE loss_lessons SET applied=1, applied_at=datetime('now') "
            "WHERE lesson_type=? AND applied=0",
            (lesson_type,),
        )
        brain._conn.commit()
        return True
    except Exception:
        return False


def dismiss_lesson(lesson_type: str) -> bool:
    """Remove a pending lesson or mark an applied lesson back to inactive."""
    brain = _get_brain()
    if brain is None:
        return False
    try:
        brain._conn.execute(
            "UPDATE loss_lessons SET applied=0, applied_at='' WHERE lesson_type=?",
            (lesson_type,),
        )
        brain._conn.commit()
        return True
    except Exception:
        return False


# ── Internal ────────────────────────────────────────────────────────

def _lesson_exists(lesson_type: str) -> bool:
    brain = _get_brain()
    if brain is None:
        return True  # fail safe: don't create duplicate
    try:
        row = brain._conn.execute(
            "SELECT COUNT(*) as n FROM loss_lessons WHERE lesson_type=?", (lesson_type,)
        ).fetchone()
        return (row["n"] if row else 0) > 0
    except Exception:
        return True


def _persist_lesson(brain, lesson: dict[str, Any]) -> None:
    """Persist a lesson to the loss_lessons table."""
    try:
        # Create loss_lessons table if not exists
        brain._conn.execute("""
            CREATE TABLE IF NOT EXISTS loss_lessons (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                learned_at TEXT DEFAULT (datetime('now')),
                lesson_type TEXT NOT NULL UNIQUE,
                label TEXT DEFAULT '',
                conditions TEXT DEFAULT '[]',
                loss_count INTEGER DEFAULT 0,
                total_pnl REAL DEFAULT 0.0,
                win_rate REAL DEFAULT 0.0,
                avoidance_rule TEXT DEFAULT '',
                action TEXT DEFAULT '',
                applied INTEGER DEFAULT 0,
                applied_at TEXT DEFAULT '',
                created_at TEXT DEFAULT (datetime('now'))
            )
        """)
        brain._conn.execute(
            "INSERT OR IGNORE INTO loss_lessons "
            "(lesson_type, label, conditions, loss_count, total_pnl, win_rate, "
            "avoidance_rule, action, applied) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)",
            (
                lesson["lesson_type"], lesson.get("label", ""),
                str(lesson.get("conditions", [])),
                lesson.get("loss_count", 0), lesson.get("total_pnl", 0.0),
                lesson.get("win_rate", 0.0), lesson.get("avoidance_rule", ""),
                lesson.get("action", ""),
            ),
        )
        brain._conn.commit()
    except Exception:
        pass
