"""brain_scheduler.py — Three-layer learning cycle scheduler.

Phase 3: triggers the learning cycles based on trade count thresholds:
  Ring 1 (instant): every trade close → updates brain (already done in gold_trader)
  Ring 2 (hourly): every 20 trades → pattern mining + lesson learning
  Ring 3 (daily): every 100 trades → future: ML model retraining

Called from autonomy_loop.py each cycle to check if ring thresholds have been
crossed since the last execution.

Usage::

    from src.learning.brain_scheduler import run_learning_cycles
    result = run_learning_cycles()
"""

from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any

import config_manager
from datetime import datetime


_STATE_FILE_NAME = "brain_scheduler_state.json"


def _state_path() -> Path:
    d = config_manager.get_data_dir() / "learning"
    d.mkdir(parents=True, exist_ok=True)
    return d / _STATE_FILE_NAME


def _load_state() -> dict[str, Any]:
    sp = _state_path()
    if sp.exists():
        try:
            return json.loads(sp.read_text(encoding="utf-8"))
        except Exception:
            pass
    return {
        "last_ring2_trade_count": 0,
        "last_ring3_trade_count": 0,
        "ring2_last_run_ts": "",
        "ring3_last_run_ts": "",
    }


def _save_state(state: dict[str, Any]) -> None:
    try:
        _state_path().write_text(json.dumps(state, indent=2), encoding="utf-8")
    except Exception:
        pass


def run_learning_cycles() -> dict[str, Any]:
    """Check and execute learning ring triggers.

    Returns:
        {"ring2_triggered": bool, "ring3_triggered": bool,
         "ring2_result": {...}, "ring3_result": {...},
         "trade_count": int}
    """
    state = _load_state()
    result: dict[str, Any] = {
        "ring2_triggered": False, "ring3_triggered": False,
        "ring2_result": {}, "ring3_result": {}, "trade_count": 0,
    }

    # Get current trade count
    trade_count = 0
    try:
        from src.learning.trading_brain import get_brain
        brain = get_brain()
        sizes = brain.table_sizes()
        trade_count = sizes.get("trade_events", 0)
    except Exception:
        return result

    result["trade_count"] = trade_count

    ring2_threshold = 20
    ring3_threshold = 100

    # ── Ring 2: Pattern + Lesson discovery ──
    new_since_ring2 = trade_count - state["last_ring2_trade_count"]
    if new_since_ring2 >= ring2_threshold:
        ring2 = {}
        try:
            from src.learning.pattern_miner import mine_win_patterns, compute_feature_importance
            patterns = mine_win_patterns(min_samples=5, min_win_rate=0.50)
            importance = compute_feature_importance()
            ring2 = {
                "patterns_found": len(patterns),
                "top_patterns": patterns[:5],
                "top_features": [{"feature": i["feature"], "score": i["importance_score"]}
                                 for i in importance[:5]],
            }
        except Exception as e:
            ring2 = {"error": str(e)}

        try:
            from src.learning.lesson_learner import learn_lessons
            lessons = learn_lessons(min_samples=5, max_win_rate=0.35)
            ring2["lessons_found"] = len(lessons)
            ring2["lessons"] = [
                {"type": l["lesson_type"], "win_rate": l["win_rate"], "applied": l["applied"]}
                for l in lessons
            ]
            # Auto-apply lessons that are clearly bad
            for lesson in lessons:
                if lesson["win_rate"] < 0.25 and lesson.get("loss_count", 0) >= 5:
                    try:
                        from src.learning.lesson_learner import apply_lesson
                        apply_lesson(lesson["lesson_type"])
                        ring2.setdefault("auto_applied", []).append(lesson["lesson_type"])
                    except Exception:
                        pass
        except Exception as e:
            ring2["lesson_error"] = str(e)

        # ── Factor scoring + decay detection ──
        try:
            from src.learning.factor_scorer import score_all_factors, generate_factor_leaderboard_md
            factors = score_all_factors()
            ring2["factors_scored"] = len(factors)
            ring2["top_factor"] = factors[0]["feature"] if factors else "none"
            ring2["top_score"] = factors[0]["importance_score"] if factors else 0.0
            from src.learning.factor_scorer import compute_oos_validation
            factors = compute_oos_validation(factors)
            overfit = [f for f in factors if f.get("oos_status") == "overfit"]
            ring2["factors_overfit"] = len(overfit)
            generate_factor_leaderboard_md(factors)

            from src.learning.factor_decay import detect_decay
            decay = detect_decay(factors)
            ring2["factors_decaying"] = len(decay.get("decaying", []))
            ring2["factors_improving"] = len(decay.get("improving", []))
        except Exception as e:
            ring2["factor_error"] = str(e)

        # ── Shadow audit: detect execution drift ──
        drifts = []
        try:
            from src.learning.shadow_audit import audit_strategy_drift
            drifts = audit_strategy_drift()
            ring2["shadow_drifts"] = len(drifts)
            if drifts:
                ring2["shadow_top_drift"] = {
                    "strategy": drifts[0]["strategy"],
                    "regime": drifts[0]["regime"],
                    "wr_drift": drifts[0]["wr_drift_pct"],
                }
        except Exception as e:
            ring2["shadow_error"] = str(e)
        # ── Wire severe drifts into avoidance rules ──
        if drifts:
            for _d in drifts:
                if _d.get("n", 0) >= 10 and _d.get("wr_drift_pct", 0) > 30:
                    try:
                        from src.learning.trading_brain import get_brain
                        _br = get_brain()
                        _lt = f"EXECUTION_DRIFT_{_d['regime']}_{_d['session']}"
                        _conds = json.dumps([
                            {"feature": "regime", "op": "=", "value": _d["regime"]},
                            {"feature": "session", "op": "=", "value": _d["session"]},
                        ])
                        _br._conn.execute(
                            "INSERT OR IGNORE INTO loss_lessons "
                            "(lesson_type, conditions, loss_count, win_rate, avoidance_rule, action, applied) "
                            "VALUES (?, ?, ?, ?, ?, ?, 1)",
                            (_lt, _conds, _d["n"], _d["actual_wr"],
                             f"Severe execution drift: expected wr={_d['expected_wr']:.0%} actual={_d['actual_wr']:.0%}",
                             "limit_only_or_reduce_size"))
                        _br._conn.commit()
                        ring2.setdefault("drift_rules_created", []).append(_lt)
                    except Exception:
                        pass

        # ── Hypothesis validation ──
        try:
            from src.learning.hypothesis_registry import validate_all
            hyp_results = validate_all()
            ring2["hypotheses_validated"] = len(hyp_results)
            promoted = [r for r in hyp_results if r.get("status") == "validated"]
            ring2["hypotheses_passed"] = len(promoted)
        except Exception as e:
            ring2["hypothesis_error"] = str(e)

        result["ring2_triggered"] = True
        result["ring2_result"] = ring2
        state["last_ring2_trade_count"] = trade_count
        state["ring2_last_run_ts"] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
        # ── Rebuild knowledge base index ──
        try:
            from src.learning.knowledge_writer import rebuild_index
            rebuild_index()
        except Exception:
            pass

    # ── Ring 3: Dimension importance coefficients ──
    new_since_ring3 = trade_count - state["last_ring3_trade_count"]
    if new_since_ring3 >= ring3_threshold:
        ring3 = {}
        try:
            from src.learning.trading_brain import get_brain
            _br = get_brain()
            _all = _br.query_recent_trades(500)
            if len(_all) >= 50:
                _dims = [("direction", "direction_score"), ("timing", "timing_score"),
                         ("regime_fit", "regime_fit"), ("anti_fade", "anti_fade"),
                         ("session", "session_mult")]
                _coeff = {}
                for _short, _key in _dims:
                    _scores = []
                    for t in _all:
                        _v = float(t.get(_key, 50) or 50)
                        _pnl = float(t.get("pnl", 0) or 0)
                        if _v > 0:
                            _scores.append((_v, _pnl))
                    if len(_scores) < 20:
                        continue
                    _scores.sort(key=lambda x: x[0])
                    _third = len(_scores) // 3
                    _top_avg = sum(p for _, p in _scores[-_third:]) / _third
                    _bot_avg = sum(p for _, p in _scores[:_third]) / _third
                    _gap = _top_avg - _bot_avg
                    _baseline_pnl = sum(p for _, p in _scores) / len(_scores)
                    _coeff[_short] = {
                        "exp": round(1.0 + (_gap / max(0.01, abs(_baseline_pnl))) * 0.15, 3),
                        "top_third_avg_pnl": round(_top_avg, 2),
                        "bottom_third_avg_pnl": round(_bot_avg, 2),
                        "n": len(_scores),
                    }
                if _coeff:
                    _dc_path = config_manager.get_data_dir() / "learning" / "dimension_coefficients.json"
                    _dc_path.parent.mkdir(parents=True, exist_ok=True)
                    _dc_path.write_text(json.dumps({
                        "generated_at": datetime.now().isoformat(),
                        "coefficients": _coeff,
                    }, ensure_ascii=False, indent=2), encoding="utf-8")
                    ring3["coefficients_computed"] = True
                    ring3["coefficients"] = {k: v["exp"] for k, v in _coeff.items()}
        except Exception as e:
            ring3 = {"triggered": True, "error": str(e)}
        if not ring3.get("coefficients_computed"):
            ring3["note"] = "insufficient data for dimension coefficients"
        ring3["triggered"] = True
        result["ring3_triggered"] = True
        result["ring3_result"] = ring3
        state["last_ring3_trade_count"] = trade_count
        state["ring3_last_run_ts"] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())

    _save_state(state)
    return result
