#!/usr/bin/env python3
"""
DeepSeek Agent — 第一层：宏观战略定调 (The Commander)

职责：
每隔数小时触发，结合 session_analyzer 的多时段数据和宏观上下文，
调用 DeepSeek API，生成当天的宏观交易方向，
并写入 gold_trader 系统的 direction 决策中。

输出指令：
- LONG (做多)
- SHORT (做空，当 active_instances.json 中 allow_short=true)
- NEUTRAL (震荡/无方向，允许底层双向交易)
注意：是否允许 SHORT 由 active_instances.json 的 macro.allow_short 控制，默认允许。
"""
try:
    from src.agent_role_router import ROLE_TREND_ANALYST
except ModuleNotFoundError:
    from agent_role_router import ROLE_TREND_ANALYST

import json
import sys
import pathlib
import time

# 设置项目根目录
ROOT = pathlib.Path(__file__).parent.parent.absolute()
sys.path.insert(0, str(ROOT))

from bridge.session_analyzer import analyze_all_sessions
from src.learning.meta_decision import get_threshold as _get_mt
from bridge.direction_registry import save_agent_direction


def _read_allow_short() -> bool:
    """统一读取 allow_short 配置，避免重复代码。

    默认 True（允许做空），与 gold_trader._spawn_macro_direction_process 保持一致。
    使用 config_manager 解析路径，兼容 frozen 模式。
    """
    try:
        import config_manager
        config_path = config_manager.get_config_dir() / "active_instances.json"
        if config_path.exists():
            config_data = json.loads(config_path.read_text(encoding="utf-8"))
            return bool(config_data.get("macro", {}).get("allow_short", True))
    except Exception:
        pass
    return True


def extract_json_block(text):
    """从 LLM 的回复文本中安全提取 JSON（委托共享工具，静默失败返回 None）"""
    try:
        from src.json_utils import extract_json_block as _extract
        return _extract(text)
    except (ValueError, Exception):
        return None


def run_macro_analysis(force_refresh: bool = False, trigger_reason: str = ""):
    print("Gathering market data for macro analysis...")
    try:
        session_data = analyze_all_sessions()
    except Exception as e:
        print(f"Failed to get session data: {e}")
        return

    allow_short = _read_allow_short()

    # 获取之前的方向和分数（使用 data_layer 统一路径而非硬编码）
    try:
        from src.data_layer import blackboard_state_path
        bb_path = blackboard_state_path()
        if bb_path and bb_path.exists():
            bb = json.loads(bb_path.read_text(encoding="utf-8"))
        else:
            bb = {}
        mc = bb.get("Macro_Commander", {})
        prev_dir = mc.get("bias", "")
        prev_score = float(mc.get("session_score", 0.0))
    except Exception:
        prev_dir = ""
        prev_score = None

    # =========================================================
    # 方向重判门控：时间优先，分数辅助
    # - 超过 max_age_sec 未刷新 → 强制执行（防止方向锁死）
    # - 分数变化 < min_score_delta 且未超时 → 维持旧方向（节约 LLM 成本）
    # - 触发 force_refresh → 无条件下发
    # =========================================================
    max_age_sec = _get_mt("max_age_sec")
    min_score_delta = _get_mt("min_score_delta")

    curr_score = session_data.get("overall_score", 0)

    # 计算上次方向的新鲜度
    prev_updated_ts = float(mc.get("updated_ts", 0.0) or 0.0)
    age_sec = time.time() - prev_updated_ts if prev_updated_ts > 0 else max_age_sec + 1

    if prev_dir and prev_score is not None and not force_refresh:
        score_delta = abs(curr_score - prev_score)
        if score_delta < min_score_delta and age_sec < max_age_sec:
            if prev_dir == "SHORT" and not allow_short:
                print(f"[Commander] allow_short=False: converting cached SHORT → NEUTRAL")
                prev_dir = "NEUTRAL"
            print(f"[Commander] 状态无显著变化 (score_delta={score_delta:.2f} < {min_score_delta}, "
                  f"age={age_sec:.0f}s < {max_age_sec}s)，维持 {prev_dir}")
            reason = f"State stable (score_delta={score_delta:.2f}). Maintained {prev_dir}."
            conf = mc.get("confidence", 0.5)

            try:
                from src.swarm.blackboard import global_blackboard
                if global_blackboard:
                    global_blackboard.update("Macro_Commander", "bias", prev_dir)
                    global_blackboard.update("Macro_Commander", "confidence", conf)
                    global_blackboard.update("Macro_Commander", "session_score", curr_score)
                    global_blackboard.update("Macro_Commander", "status", "DONE")
                    global_blackboard.update("Macro_Commander", "degraded", False)
                    if trigger_reason:
                        global_blackboard.update("Macro_Commander", "trigger_reason", trigger_reason)
                    global_blackboard.add_log("Macro_Commander", "BROADCAST",
                        f"[OK] 维持宏观定调: {prev_dir}，置信度: {conf:.2f}。理由: {reason[:30]}...")
            except Exception as e:
                print(f"[Commander] Failed to update blackboard: {e}")

            save_agent_direction(prev_dir, f"[DeepSeek] {reason}", conf)
            return
        elif age_sec >= max_age_sec:
            print(f"[Commander] 方向已过期 ({age_sec:.0f}s >= {max_age_sec}s)，强制执行 LLM 重判")

    # ── Delegate LLM call to the unified commander_client ──
    regime_label = str(session_data.get("regime", "UNKNOWN") or "UNKNOWN")
    regime_detail = session_data.get("regime_detail", {}) if isinstance(session_data, dict) else {}
    macro_correlation = session_data.get("macro_correlation", {}) if isinstance(session_data, dict) else {}
    vol_ratio = float(regime_detail.get("vol_ratio", session_data.get("vol_ratio", 1.0)) or 1.0)

    try:
        from bridge.commander_client import call_commander

        result = call_commander(
            session_data=session_data,
            structure_snapshot=None,
            current_regime=regime_label,
            vol_ratio=vol_ratio,
            current_position=None,
            macro_correlation=macro_correlation,
            force_refresh=force_refresh,
            allow_short=allow_short,
        )

        direction = result["direction"]
        confidence = result["confidence"]
        reason = result["reason"]

        print(f"[Commander] Decision: {direction} (Conf: {confidence}) - {reason[:120]}")
        save_agent_direction(direction, f"[LLM] {reason}", confidence)

        try:
            from src.swarm.blackboard import global_blackboard
            if global_blackboard:
                global_blackboard.update("Macro_Commander", "bias", direction)
                global_blackboard.update("Macro_Commander", "confidence", confidence)
                global_blackboard.update("Macro_Commander", "session_score", session_data.get("overall_score", 0))
                global_blackboard.update("Macro_Commander", "status", "DONE")
                global_blackboard.update("Macro_Commander", "degraded", result.get("degraded", False))
                if trigger_reason:
                    global_blackboard.update("Macro_Commander", "trigger_reason", trigger_reason)
                global_blackboard.add_log("Macro_Commander", "BROADCAST",
                    f"[OK] 宏观定调: {direction}，置信度: {confidence:.2f}。理由: {reason[:30]}...")
        except Exception as e:
            print(f"[Commander] Failed to update blackboard after LLM: {e}")

    except Exception as e:
        print(f"Macro analysis failed: {e}")
        _run_technical_fallback(session_data)


def _run_technical_fallback(session_data: dict = None):
    """降级方案：使用 session_analyzer 纯技术面的统计结果。

    方向映射规则（与 commander_client._fallback_response 对齐）：
    - BULLISH/SLIGHTLY_BULLISH → LONG
    - BEARISH/SLIGHTLY_BEARISH → SHORT（allow_short=True 时），否则 NEUTRAL
    - NEUTRAL 或其他 → NEUTRAL（不再默认 LONG）

    所有降级结果均标记 degraded=True，供下游区分真实 LLM 判断 vs 技术推测。
    """
    if session_data is None:
        try:
            session_data = analyze_all_sessions()
        except Exception as e:
            print(f"[Commander] Fallback: cannot get session data: {e}")
            save_agent_direction("NEUTRAL", "[Fallback] Default NEUTRAL", 0.50, degraded=True)
            return

    allow_short = _read_allow_short()

    regime_label = str(session_data.get("regime", "UNKNOWN") or "UNKNOWN")
    regime_aware_dir = str(session_data.get("regime_aware_direction", "") or "")
    fallback_dir = regime_aware_dir if regime_aware_dir else str(
        session_data.get("overall_direction", "NEUTRAL") or "NEUTRAL"
    ).upper()

    # Regime veto: TRANSITION/EXTREME/FAKE_TREND force NEUTRAL regardless of technical signals
    if regime_label in ("TRANSITION", "EXTREME", "FAKE_TREND", "VOLATILITY_SPIKE"):
        direction = "NEUTRAL"
    elif fallback_dir in ("BULLISH", "SLIGHTLY_BULLISH"):
        direction = "LONG"
    elif fallback_dir in ("BEARISH", "SLIGHTLY_BEARISH"):
        direction = "SHORT" if allow_short else "NEUTRAL"
    else:
        direction = "NEUTRAL"

    confidence = 0.60 if fallback_dir in ("BULLISH", "BEARISH") else 0.55
    print(f"Fallback to technical direction: {direction} (regime={regime_label}, "
          f"confidence={confidence}, degraded=True)")
    save_agent_direction(direction, f"[Fallback] Regime={regime_label} Technical={fallback_dir}",
                         confidence, degraded=True)


if __name__ == "__main__":
    run_macro_analysis()
