#!/usr/bin/env python3
"""
agent67v2/launch_paas.py — 通过 AgentPaaS 运行 lambda v2 🐑
=============================================================

多智能体版本的 PaaS 部署。注册协调者 + 5个微代理。
与 agent67/launch_paas.py 保持一致的启动流程:
  PaaS 启动 → 创建租户 → 注册代理 → Claude Code CLI 对话 + PaaS 记录

用法:
  # 一键启动（推荐，无需 API Key）
  python launch_paas.py all

  # 分步执行
  python launch_paas.py serve               # 启动 PaaS 服务
  python launch_paas.py register            # 注册全部代理 (协调者 + 5微代理)
  python launch_paas.py chat                # 通过 PaaS 与 lambda v2 对话
  python launch_paas.py chat --claude-code  # PaaS 追踪 + Claude Code 执行
  python launch_paas.py status              # 查看状态
"""
from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys
import time
import urllib.request
import urllib.error
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent  # lambdagentpaas/
AGENT_DIR = Path(__file__).resolve().parent                    # agent67v2/
AGENTS_DIR = AGENT_DIR / "agents"
sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(AGENT_DIR.parent))  # agentexample/

PAAS_URL = os.environ.get("AGENTPAAS_URL", "http://127.0.0.1:8000")
API_KEY = os.environ.get("AGENTPAAS_API_KEY", "")


# ═══════════════════════════════════════════════════════════
# PaaS API Helpers (与 agent67 一致)
# ═══════════════════════════════════════════════════════════

def api_call(method: str, path: str, data: dict = None) -> dict:
    """调用 AgentPaaS REST API"""
    url = f"{PAAS_URL}/api/v1{path}"
    body = json.dumps(data).encode("utf-8") if data else None
    req = urllib.request.Request(url, data=body, method=method)
    req.add_header("Content-Type", "application/json")
    req.add_header("Authorization", f"Bearer {API_KEY}")
    try:
        with urllib.request.urlopen(req, timeout=300) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        body = e.read().decode()
        print(f"  ✗ API Error {e.code}: {body[:300]}")
        return {"error": body}
    except urllib.error.URLError as e:
        print(f"  ✗ Connection error: {e}")
        return {"error": str(e)}


def wait_for_server(timeout: int = 30) -> bool:
    """等待 PaaS 服务就绪"""
    for _ in range(timeout):
        try:
            req = urllib.request.Request(f"{PAAS_URL}/health")
            with urllib.request.urlopen(req, timeout=2):
                return True
        except Exception:
            time.sleep(1)
    return False


def load_agent_configs() -> dict:
    """加载所有 YAML 配置"""
    import yaml
    configs = {}
    # 5 个微代理
    for yml_file in sorted(AGENTS_DIR.glob("*.yml")):
        with open(yml_file, "r", encoding="utf-8") as f:
            configs[yml_file.stem] = yaml.safe_load(f)
    # 协调者
    orchestrator_path = AGENT_DIR / "orchestrator.yml"
    with open(orchestrator_path, "r", encoding="utf-8") as f:
        configs["coordinator"] = yaml.safe_load(f)
    return configs


# ═══════════════════════════════════════════════════════════
# 命令: serve — 启动 AgentPaaS 服务
# ═══════════════════════════════════════════════════════════

def cmd_serve(args):
    """启动 AgentPaaS 服务"""
    print("🚀 启动 AgentPaaS 服务...")

    proc = subprocess.Popen(
        [sys.executable, "-m", "agentpaas", "serve", "--port", str(args.port), "--dev"],
        cwd=str(PROJECT_ROOT),
        stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    )

    print(f"  PID: {proc.pid}")
    print(f"  URL: http://127.0.0.1:{args.port}")
    print("  等待服务就绪...")

    if not wait_for_server(30):
        print("  ✗ 服务启动超时")
        proc.kill()
        return None

    print("  ✓ PaaS 服务就绪")
    return proc


def cmd_create_tenant(args):
    """创建租户并返回 API key"""
    print("\n📋 创建租户...")
    result = subprocess.run(
        [sys.executable, "-m", "agentpaas", "create-tenant", "--name", "agent67v2-lab"],
        capture_output=True, text=True, cwd=str(PROJECT_ROOT),
    )
    for line in result.stdout.splitlines():
        print(f"  {line}")
        if line.startswith("API Key:"):
            return line.split(":", 1)[1].strip()
    return None


# ═══════════════════════════════════════════════════════════
# 命令: register — 注册全部代理到 PaaS
# ═══════════════════════════════════════════════════════════

def cmd_register(args):
    """注册协调者 + 5个微代理到 PaaS"""
    configs = load_agent_configs()
    agent_ids = {}

    # 1. 注册 5 个微代理
    print("\n📝 注册 5 个微代理...")
    for name, config in configs.items():
        if name == "coordinator":
            continue
        agent_name = config.get("name", name)
        print(f"  注册 {agent_name}...", end=" ")

        result = api_call("POST", "/agents", {
            "name": agent_name,
            "description": config.get("description", ""),
            "config": config,
            "tags": config.get("skill", {}).get("tags", []) + ["agent67v2", "micro-agent"],
            "environment": "production",
        })

        if "agent_id" in result:
            agent_ids[agent_name] = result["agent_id"]
            print(f"✅ {result['agent_id']}")
        else:
            print(f"❌ {result.get('error', 'Unknown')[:80]}")

    # 2. 注册协调者
    print(f"\n  注册协调者...", end=" ")
    orch_config = configs["coordinator"]
    result = api_call("POST", "/agents", {
        "name": "lambda-v2",
        "description": orch_config.get("description", ""),
        "config": orch_config,
        "tags": ["agent67v2", "coordinator", "multi-agent"],
        "environment": "production",
        "metadata": {"sub_agents": agent_ids},
    })

    if "agent_id" in result:
        agent_ids["coordinator"] = result["agent_id"]
        print(f"✅ {result['agent_id']}")
    else:
        print(f"❌ {result.get('error', 'Unknown')[:80]}")

    # 保存映射
    mapping_path = AGENT_DIR / ".paas_agent_ids.json"
    mapping_path.write_text(json.dumps(agent_ids, indent=2))
    print(f"\n  ✅ 共注册 {len(agent_ids)} 个代理")
    return agent_ids


# ═══════════════════════════════════════════════════════════
# 命令: chat — 通过 PaaS 与 lambda v2 对话
# ═══════════════════════════════════════════════════════════

def cmd_chat(args):
    """通过 AgentPaaS 与 lambda v2 对话（交互式）"""
    # 加载 agent IDs
    mapping_path = AGENT_DIR / ".paas_agent_ids.json"
    if not mapping_path.exists():
        print("✗ 未找到 agent IDs，请先运行 register")
        return

    agent_ids = json.loads(mapping_path.read_text())
    coordinator_id = agent_ids.get("coordinator")
    if not coordinator_id:
        print("✗ 未找到协调者 ID，请重新 register")
        return

    use_claude_code = args.claude_code or not os.environ.get("ANTHROPIC_API_KEY")

    print(f"\n{'═' * 60}")
    print(f"  🐑 lambda v2 — 多智能体协作版 (通过 AgentPaaS 运行)")
    print(f"  Coordinator ID: {coordinator_id}")
    print(f"  微代理: {', '.join(k for k in agent_ids if k != 'coordinator')}")
    if use_claude_code:
        print(f"  后端: Claude Code Max Plan (无需 API Key)")
    else:
        print(f"  后端: AgentPaaS 标准执行 (from_config)")
    print(f"{'═' * 60}")

    if use_claude_code:
        _chat_claude_code(coordinator_id, args)
    else:
        _chat_paas_api(coordinator_id, args)


def _chat_paas_api(coordinator_id: str, args):
    """纯 PaaS API 模式：所有执行都通过 PaaS"""
    print("\n  💡 所有请求通过 PaaS API 路由，执行记录自动保存")
    print("  输入 'exit' 退出 | 'runs' 查看执行历史\n")

    while True:
        try:
            user_input = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\n👋 lambda v2 下线了！")
            break

        if not user_input:
            continue
        if user_input.lower() in ("exit", "quit", "bye"):
            print("👋 lambda v2 下线了！")
            break
        if user_input.lower() == "runs":
            _show_runs(coordinator_id)
            continue

        print()
        result = api_call("POST", f"/agents/{coordinator_id}/run", {
            "input": user_input,
            "parameters": {},
            "context": {},
        })

        if "error" not in result:
            print(f"🐑 lambda v2: {result.get('output', '')}")
            usage = result.get("usage", {})
            print(f"  📊 [run_id={result['run_id']} | "
                  f"steps={usage.get('steps', 0)} | "
                  f"tokens={usage.get('total_tokens', 0)} | "
                  f"{usage.get('duration_ms', 0)}ms]")
        else:
            print(f"❌ 执行失败: {result['error'][:300]}")
        print()


def _chat_claude_code(coordinator_id: str, args):
    """
    Claude Code 混合模式 (与 agent67 一致):
    - LLM 调用 → claude CLI (Max Plan, 无需 API Key)
    - 工具执行 → 本地 Python (微代理)
    - 执行追踪 → 写入 PaaS (runs 表)

    兼顾:
    ✅ 不需要 API Key
    ✅ 每次对话记录都在 PaaS 中追踪
    ✅ 版本管理、执行历史由 PaaS 管理
    ✅ 多智能体协作
    """
    from agent67v2.core.coordinator import CoordinatorAssistant

    print("\n  💡 LLM 调用走 Claude Code CLI，执行记录同步到 PaaS")
    print("  输入 'exit' 退出 | 'runs' 查看执行历史 | 'trace' 追踪 | 'stats' 统计\n")

    # 检测后端
    try:
        from agent67.core.config import detect_backend
        model, use_api, backend = detect_backend()
    except ImportError:
        # 如果 agent67 不可用，直接检测 claude CLI
        if shutil.which("claude"):
            model, use_api, backend = "sonnet", False, "claude_code"
            print("✅ 使用 Claude Code Max Plan (无需 API Key)")
        else:
            model, use_api, backend = "sonnet", False, ""
            print("⚠️  未检测到 claude CLI，将使用标准 Lam")

    assistant = CoordinatorAssistant(
        model=model or "sonnet", use_api=use_api, backend=backend
    )

    while True:
        try:
            user_input = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\n👋 lambda v2 下线了！")
            break

        if not user_input:
            continue
        if user_input.lower() in ("exit", "quit", "bye"):
            print("👋 lambda v2 下线了！")
            break
        if user_input.lower() == "runs":
            _show_runs(coordinator_id)
            continue
        if user_input.lower() == "trace":
            assistant.print_trace()
            continue
        if user_input.lower() == "stats":
            assistant.print_stats()
            continue
        if user_input.lower() == "skills":
            from lambdagent.skills import SkillRegistry
            registry = SkillRegistry()
            print(f"\n📦 已注册技能 ({len(registry)} 个):")
            for name in registry.list_all():
                skill = registry.get(name)
                print(f"  • {name}: {skill.description}")
            print()
            continue

        print()
        t0 = time.time()

        # 通过 CoordinatorAssistant + 微代理执行
        response = assistant.chat(user_input)
        duration_ms = int((time.time() - t0) * 1000)
        steps = len(assistant.ctx.trace)

        # 显示结果（流式模式下已在 chat() 中显示）
        is_streaming = hasattr(assistant.brain, 'stream') and assistant.brain.stream
        if not is_streaming:
            print(f"🐑 lambda v2: {response}")

        # 同步执行记录到 PaaS
        _record_run_to_paas(coordinator_id, user_input, response, duration_ms,
                           steps, assistant._total_delegations)
        print()


def _record_run_to_paas(coordinator_id: str, input_text: str, output: str,
                        duration_ms: int, steps: int, delegations: int = 0):
    """
    将执行记录写入 PaaS（仅记录，不触发执行）。

    调用 /agents/{id}/runs/record 端点，
    与 /agents/{id}/run 不同，这个端点不会调 from_config/Lam，
    只写入 runs 表。
    """
    result = api_call("POST", f"/agents/{coordinator_id}/runs/record", {
        "input": input_text[:500],
        "output": output[:2000],
        "status": "completed",
        "duration_ms": duration_ms,
        "steps": steps,
        "source": "claude-code-cli",
        "metadata": {"delegations": delegations, "version": "v2-multi-agent"},
    })
    if "run_id" in result:
        run_id = result["run_id"]
        print(f"  📊 [PaaS: run_id={run_id} | steps={steps} | "
              f"delegations={delegations} | {duration_ms}ms]")


def _show_runs(coordinator_id: str):
    """显示最近的执行历史"""
    result = api_call("GET", f"/agents/{coordinator_id}/runs?limit=10")
    runs = result.get("runs", [])
    if not runs:
        print("  (暂无执行记录)")
        return
    print(f"\n📜 最近 {len(runs)} 次执行:")
    for r in runs:
        status_icon = "✅" if r.get("status") == "completed" else "❌"
        print(f"  {status_icon} {r['id']} | {r.get('status')} | "
              f"{r.get('duration_ms', 0)}ms | {r.get('created_at', '')[:19]}")
        if r.get("input"):
            print(f"     输入: {r['input'][:80]}")
    print()


# ═══════════════════════════════════════════════════════════
# 命令: all — 一键启动
# ═══════════════════════════════════════════════════════════

def cmd_all(args):
    """一键: 启动 PaaS + 创建租户 + 注册代理 + 开始对话"""
    global API_KEY

    proc = cmd_serve(args)
    if not proc:
        return

    try:
        # 创建租户
        key = cmd_create_tenant(args)
        if key:
            API_KEY = key
            os.environ["AGENTPAAS_API_KEY"] = key

        # 注册代理 (协调者 + 5微代理)
        agent_ids = cmd_register(args)
        if not agent_ids or "coordinator" not in agent_ids:
            return

        # 开始对话
        print(f"\n{'═' * 60}")
        print(f"  🐑 lambda v2 已就绪! 多智能体协作模式")
        print(f"  协调者 + {len(agent_ids) - 1} 个微代理")
        print(f"{'═' * 60}")
        cmd_chat(args)

    except KeyboardInterrupt:
        print("\n\n🛑 中断")
    finally:
        print("\n关闭 PaaS 服务...")
        proc.terminate()
        try:
            proc.wait(timeout=5)
        except subprocess.TimeoutExpired:
            proc.kill()
        print("  ✓ 服务已关闭")


# ═══════════════════════════════════════════════════════════
# 命令: status — 查看代理状态
# ═══════════════════════════════════════════════════════════

def cmd_status(args):
    """查看所有代理在 PaaS 中的状态"""
    mapping_path = AGENT_DIR / ".paas_agent_ids.json"
    if not mapping_path.exists():
        print("✗ 未注册，请先运行: python launch_paas.py register")
        return

    agent_ids = json.loads(mapping_path.read_text())
    print(f"\n📋 lambda v2 🐑 状态查询 ({len(agent_ids)} 个代理)")

    for name, aid in agent_ids.items():
        role = "🎯 协调者" if name == "coordinator" else "🔧 微代理"
        print(f"\n  {role}: {name}")
        print(f"    Agent ID: {aid}")

        result = api_call("GET", f"/agents/{aid}")
        if "error" not in result:
            print(f"    版本: v{result.get('current_version', '?')}")
            print(f"    状态: {result.get('status', 'unknown')}")

    # 协调者执行历史
    coordinator_id = agent_ids.get("coordinator")
    if coordinator_id:
        _show_runs(coordinator_id)


# ═══════════════════════════════════════════════════════════
# CLI 入口
# ═══════════════════════════════════════════════════════════

def main():
    parser = argparse.ArgumentParser(
        description="🐑 lambda v2 — 多智能体协作版 (通过 AgentPaaS 运行)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python launch_paas.py all                  # 一键启动 (推荐, 无需 API Key)
  python launch_paas.py serve                # 只启动 PaaS 服务
  python launch_paas.py register             # 注册协调者 + 5微代理
  python launch_paas.py chat --claude-code   # Claude Code 模式对话
  python launch_paas.py status               # 查看状态
        """
    )
    sub = parser.add_subparsers(dest="command")

    # serve
    p_serve = sub.add_parser("serve", help="启动 AgentPaaS 服务")
    p_serve.add_argument("--port", type=int, default=8000)

    # register
    sub.add_parser("register", help="注册全部代理到 PaaS")

    # chat
    p_chat = sub.add_parser("chat", help="通过 PaaS 与 lambda v2 对话")
    p_chat.add_argument("--claude-code", action="store_true",
                        help="使用 Claude Code CLI 作为 LLM 后端")

    # all
    p_all = sub.add_parser("all", help="一键启动全流程")
    p_all.add_argument("--port", type=int, default=8000)
    p_all.add_argument("--claude-code", action="store_true", default=True,
                       help="使用 Claude Code CLI (默认)")

    # status
    sub.add_parser("status", help="查看代理状态")

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        return

    # 确保 claude-code 属性存在
    if not hasattr(args, "claude_code"):
        args.claude_code = bool(shutil.which("claude"))

    commands = {
        "serve": lambda: cmd_serve(args),
        "register": lambda: cmd_register(args),
        "chat": lambda: cmd_chat(args),
        "all": lambda: cmd_all(args),
        "status": lambda: cmd_status(args),
    }

    commands[args.command]()


if __name__ == "__main__":
    main()
