#!/usr/bin/env python3
"""
Agent API — 外部 Agent 调度接口

GoldAutoTrade 对外暴露的统一 API 层。任意外部 Agent（Claude Code / OpenClaw /
自定义 Agent 框架）均可通过以下两种方式对接：

  1. Python import:  from src.agent_api import AgentAPI
  2. CLI subprocess: python3 src/agent_api.py <action> [--params]

所有 action 返回 {"ok": bool, "data": ..., "error": ...} 的 JSON 结构。
"""

from __future__ import annotations

import json
import os
import sys
import time
import pathlib
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

# ROOT now resolved via frozen_path (PyInstaller-safe)
from frozen_path import APP_ROOT as ROOT
sys.path.insert(0, str(ROOT))


# ═══════════════════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════════════════

def _ok(data: Any = None) -> dict:
    return {"ok": True, "data": data, "error": None}


def _err(msg: str) -> dict:
    return {"ok": False, "data": None, "error": msg}


def _ts() -> str:
    return datetime.now(timezone.utc).isoformat()


# ═══════════════════════════════════════════════════════════════
# AgentAPI — 核心接口类
# ═══════════════════════════════════════════════════════════════

@dataclass
class AgentAPI:
    """外部 Agent 对接 GoldAutoTrade 的统一入口。

    用法:
        api = AgentAPI()
        api.set_direction("LONG", reason="外部 Agent 判断", confidence=0.85)
        print(api.get_status())
    """

    # ── Direction (宏观方向) ─────────────────────────────────

    def set_direction(
        self,
        direction: str,
        reason: str = "",
        confidence: float = 0.8,
        source: str = "external_agent",
    ) -> dict:
        """外部 Agent 直接设定宏观方向，覆盖 M2 决策。

        Args:
            direction: LONG | SHORT | NEUTRAL
            reason: 决策理由
            confidence: 置信度 0.0~1.0
            source: 调用来源标识
        """
        direction = direction.upper().strip()
        if direction not in ("LONG", "SHORT", "NEUTRAL"):
            return _err(f"无效方向: {direction}，允许 LONG/SHORT/NEUTRAL")

        try:
            from bridge.direction_registry import save_agent_direction
            record = save_agent_direction(
                direction=direction,
                reason=f"[{source}] {reason}".strip(),
                confidence=float(confidence),
            )
            return _ok({
                "direction": direction,
                "confidence": confidence,
                "timestamp": record.get("timestamp", _ts()),
            })
        except Exception as e:
            return _err(str(e))

    def get_direction(self) -> dict:
        """查询当前宏观方向。"""
        try:
            from bridge.direction_registry import get_agent_direction
            return _ok(get_agent_direction())
        except Exception as e:
            return _err(str(e))

    def run_macro_analysis(self) -> dict:
        """触发 M2 决策主脑执行一次宏观分析。"""
        try:
            from src.deepseek_agent import run_macro_analysis
            run_macro_analysis(trigger_reason="override")
            return _ok({"message": "macro analysis triggered"})
        except Exception as e:
            return _err(str(e))

    # ── Market Analysis (市场解读) ────────────────────────────

    def run_market_interpreter(self) -> dict:
        """触发实时市场解读，结果写入 structure_snapshot。"""
        try:
            from src.market_interpreter import run_market_interpreter
            run_market_interpreter()
            return _ok({"message": "market interpreter triggered"})
        except Exception as e:
            return _err(str(e))

    def get_market_view(self) -> dict:
        """查询最新市场结构快照。"""
        try:
            from src.market_structure import load_structure_snapshot
            snapshot = load_structure_snapshot(valid_only=True)
            return _ok(snapshot)
        except Exception as e:
            return _err(str(e))

    # ── Strategy Management (策略管理) ────────────────────────

    def list_strategies(self) -> dict:
        """列出策略池中所有策略及其状态。"""
        try:
            pool_dir = ROOT / "strategy_pool"
            if not pool_dir.exists():
                return _ok({"strategies": [], "count": 0})

            strategies = []
            for py_file in sorted(pool_dir.glob("*.py")):
                strategies.append({
                    "name": py_file.stem,
                    "path": str(py_file),
                    "size": py_file.stat().st_size,
                })
            return _ok({"strategies": strategies, "count": len(strategies)})
        except Exception as e:
            return _err(str(e))

    def run_evolution(self) -> dict:
        """触发策略演化闭环（LLM 生成 → 沙盒验证 → 回测 → 入池）。"""
        try:
            from src.evolution_pipeline import run_evolution_cycle
            result = run_evolution_cycle()
            return _ok(result)
        except Exception as e:
            return _err(str(e))

    def run_evolver_review(self) -> dict:
        """触发周末复盘演化（参数优化 + 影子验证）。"""
        try:
            from src.swarm.evolver_agent import run_evolver_review
            result = run_evolver_review()
            return _ok(result)
        except Exception as e:
            return _err(str(e))

    def run_offline_train(self, strategy: str = "ema_rsi_bb", data_source: str = "mt5", mode: str = "once") -> dict:
        """触发离线训练/回测。

        Args:
            strategy: 策略名 (ema_rsi_bb / grid / composite 等)
            data_source: mt5 | yfinance
            mode: once | train
        """
        try:
            from src.subprocess_util import run as _sp_run
            cmd = [
                sys.executable, str(ROOT / "src" / "offline_train.py"),
                "--mode", mode,
                "--strategy", strategy,
                "--data-source", data_source,
            ]
            proc = _sp_run(cmd, capture_output=True, text=True, timeout=300)
            return _ok({
                "stdout": proc.stdout[-2000:] if proc.stdout else "",
                "stderr": proc.stderr[-500:] if proc.stderr else "",
                "returncode": proc.returncode,
            })
        except Exception as e:
            return _err(str(e))

    # ── System Health (系统状态) ──────────────────────────────

    def get_status(self) -> dict:
        """获取系统综合状态：blackboard + 持仓 + 健康度。"""
        status = {
            "timestamp": _ts(),
            "direction": None,
            "blackboard": {},
            "positions": [],
        }
        try:
            from bridge.direction_registry import get_agent_direction
            status["direction"] = get_agent_direction()
        except Exception:
            pass

        try:
            from config_manager import get_app_dir
            bb_file = get_app_dir() / "blackboard_state.json"
            if bb_file.exists():
                status["blackboard"] = json.loads(bb_file.read_text(encoding="utf-8"))
        except Exception:
            pass

        try:
            from bridge.auto_fetch_data import get_positions
            status["positions"] = get_positions()
        except Exception:
            try:
                pos_file = ROOT / "data" / "positions.json"
                if pos_file.exists():
                    status["positions"] = json.loads(pos_file.read_text(encoding="utf-8"))
            except Exception:
                pass

        return _ok(status)

    def run_self_healer(self) -> dict:
        """触发自愈引擎检查并修复系统。"""
        try:
            from src.self_healer import run_self_healer
            run_self_healer()
            return _ok({"message": "self-healer triggered"})
        except Exception as e:
            return _err(str(e))

    def get_recent_trades(self, limit: int = 20) -> dict:
        """查询最近 N 笔交易记录。"""
        try:
            from config_manager import get_logs_dir
            ledger = get_logs_dir() / "trading_ledger.ndjson"
            if not ledger.exists():
                return _ok({"trades": [], "count": 0})
            trades = []
            with open(ledger, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if line:
                        try:
                            trades.append(json.loads(line))
                        except Exception:
                            pass
            trades = trades[-limit:]
            return _ok({"trades": trades, "count": len(trades)})
        except Exception as e:
            return _err(str(e))

    # ── Manual Override (人工干预) ────────────────────────────

    def block_trading(self, reason: str = "外部 Agent 紧急停止") -> dict:
        """紧急停止交易，写入 kill_switch 标记。"""
        try:
            from config_manager import get_app_dir
            ks_file = get_app_dir() / "kill_switch.json"
            ks_file.write_text(json.dumps({
                "active": True,
                "reason": reason,
                "timestamp": _ts(),
                "source": "external_agent",
            }, indent=2, ensure_ascii=False))
            return _ok({"trading_blocked": True, "reason": reason})
        except Exception as e:
            return _err(str(e))

    def unblock_trading(self) -> dict:
        """恢复交易，清除 kill_switch 标记。"""
        try:
            from config_manager import get_app_dir
            ks_file = get_app_dir() / "kill_switch.json"
            if ks_file.exists():
                ks_file.write_text(json.dumps({
                    "active": False,
                    "timestamp": _ts(),
                    "source": "external_agent",
                }, indent=2, ensure_ascii=False))
            return _ok({"trading_blocked": False})
        except Exception as e:
            return _err(str(e))


# ═══════════════════════════════════════════════════════════════
# CLI — 供外部 Agent 通过 subprocess 调用
# ═══════════════════════════════════════════════════════════════

_ACTIONS: Dict[str, Dict] = {
    "set-direction":       {"method": "set_direction",       "params": ["direction", "reason", "confidence"]},
    "get-direction":       {"method": "get_direction",       "params": []},
    "run-macro-analysis":  {"method": "run_macro_analysis",  "params": []},
    "run-market-interpreter": {"method": "run_market_interpreter", "params": []},
    "get-market-view":     {"method": "get_market_view",     "params": []},
    "list-strategies":     {"method": "list_strategies",     "params": []},
    "run-evolution":       {"method": "run_evolution",       "params": []},
    "run-evolver-review":  {"method": "run_evolver_review",  "params": []},
    "run-offline-train":   {"method": "run_offline_train",   "params": ["strategy", "data_source", "mode"]},
    "get-status":          {"method": "get_status",          "params": []},
    "run-self-healer":     {"method": "run_self_healer",     "params": []},
    "get-recent-trades":   {"method": "get_recent_trades",   "params": ["limit"]},
    "block-trading":       {"method": "block_trading",       "params": ["reason"]},
    "unblock-trading":     {"method": "unblock_trading",     "params": []},
}


def _parse_cli_args(argv: list) -> dict:
    """极简参数解析：--key value 或 --key=value"""
    kwargs: dict = {}
    i = 0
    while i < len(argv):
        arg = argv[i]
        if arg.startswith("--"):
            key = arg[2:].replace("-", "_")
            if "=" in key:
                key, val = key.split("=", 1)
                kwargs[key] = val
            elif i + 1 < len(argv) and not argv[i + 1].startswith("--"):
                kwargs[key] = argv[i + 1]
                i += 1
            else:
                kwargs[key] = "true"
        i += 1
    return kwargs


def _cast_params(kwargs: dict, param_names: list) -> dict:
    """类型转换。"""
    casted = {}
    for key in param_names:
        val = kwargs.get(key)
        if val is None:
            continue
        if key == "confidence":
            try:
                casted[key] = float(val)
            except (ValueError, TypeError):
                casted[key] = 0.8
        elif key == "limit":
            try:
                casted[key] = int(val)
            except (ValueError, TypeError):
                casted[key] = 20
        else:
            casted[key] = str(val)
    return casted


def main(argv: Optional[list] = None) -> int:
    if argv is None:
        argv = sys.argv[1:]

    if not argv:
        actions = "\n".join(f"  {a}" for a in sorted(_ACTIONS))
        print(json.dumps(_ok({
            "help": "Agent API for GoldAutoTrade",
            "actions": sorted(_ACTIONS.keys()),
            "usage": f"python3 {__file__} <action> [--key value ...]",
        }), indent=2, ensure_ascii=False))
        return 0

    action = argv[0]
    if action not in _ACTIONS:
        print(json.dumps(_err(f"未知 action: {action}。可用: {sorted(_ACTIONS.keys())}"), ensure_ascii=False))
        return 1

    spec = _ACTIONS[action]
    kwargs = _parse_cli_args(argv[1:])
    params = _cast_params(kwargs, spec["params"])
    api = AgentAPI()
    result = getattr(api, spec["method"])(**params)
    print(json.dumps(result, indent=2, ensure_ascii=False))
    return 0 if result.get("ok") else 1


if __name__ == "__main__":
    raise SystemExit(main())
