"""
agentpaas CLI — Command-line interface for managing AgentPaaS platform.

All commands (except serve/create-tenant/provider) call the AgentPaaS HTTP API.

Usage:
    agentpaas serve [--dev] [--port 8000]
    agentpaas create-tenant --name "My Org"

    # Agent management
    agentpaas agent list
    agentpaas agent create --name my-agent --config agent.yml
    agentpaas agent get ag_xxx
    agentpaas agent delete ag_xxx
    agentpaas agent update ag_xxx --config new.yml
    agentpaas agent rollback ag_xxx --version 2
    agentpaas agent versions ag_xxx

    # Execution
    agentpaas run ag_xxx "Your input here"
    agentpaas run ag_xxx --input-file task.txt
    agentpaas run ag_xxx "input" --stream

    # Status & monitoring
    agentpaas status
    agentpaas status agents
    agentpaas status agent ag_xxx
    agentpaas health ag_xxx

    # Runs & traces
    agentpaas runs ag_xxx
    agentpaas trace run_xxx

    # Keys
    agentpaas key create --name prod-key
    agentpaas key list
    agentpaas key revoke key_xxx

    # Billing
    agentpaas usage
    agentpaas metrics

    # Provider management
    agentpaas provider list
    agentpaas provider add dashscope --api-key sk-xxx
    agentpaas provider test dashscope
    agentpaas provider models dashscope
    agentpaas provider remove xxx
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import urllib.request
import urllib.error
import yaml


# ============================================================
# Config: server URL + API key
# ============================================================

CONFIG_FILE = os.path.expanduser("~/.agentpaas/config.json")
PROVIDERS_FILE = os.path.expanduser("~/.agentpaas/providers.json")

DEFAULT_PROVIDERS = {
    "anthropic": {"name": "Anthropic", "type": "anthropic", "base_url": "https://api.anthropic.com",
                  "models": ["claude-sonnet-4-20250514", "claude-opus-4-20250514"], "env_key": "ANTHROPIC_API_KEY"},
    "openai": {"name": "OpenAI", "type": "openai", "base_url": "https://api.openai.com/v1",
               "models": ["gpt-4o", "gpt-4o-mini"], "env_key": "OPENAI_API_KEY"},
    "dashscope": {"name": "DashScope (Qwen)", "type": "openai_compatible",
                  "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
                  "models": ["qwen3-max-2026-01-23", "qwen-max", "qwen-plus"], "env_key": "DASHSCOPE_API_KEY"},
    "deepseek": {"name": "DeepSeek", "type": "openai_compatible", "base_url": "https://api.deepseek.com/v1",
                 "models": ["deepseek-chat", "deepseek-reasoner"], "env_key": "DEEPSEEK_API_KEY"},
    "zhipu": {"name": "Zhipu AI (GLM)", "type": "openai_compatible",
              "base_url": "https://open.bigmodel.cn/api/paas/v4",
              "models": ["glm-4-plus", "glm-4-flash"], "env_key": "ZHIPU_API_KEY"},
    "moonshot": {"name": "Moonshot (Kimi)", "type": "openai_compatible",
                 "base_url": "https://api.moonshot.cn/v1",
                 "models": ["moonshot-v1-128k"], "env_key": "MOONSHOT_API_KEY"},
    "ollama": {"name": "Ollama (Local)", "type": "openai_compatible",
               "base_url": "http://localhost:11434/v1",
               "models": ["llama3", "qwen2"], "env_key": "", "api_key": "ollama"},
}


def _load_config():
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE) as f:
            return json.load(f)
    return {"server": "http://localhost:8000", "api_key": ""}


def _save_config(cfg):
    os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
    with open(CONFIG_FILE, 'w') as f:
        json.dump(cfg, f, indent=2)


def _load_providers():
    if os.path.exists(PROVIDERS_FILE):
        with open(PROVIDERS_FILE) as f:
            return json.load(f)
    return dict(DEFAULT_PROVIDERS)


def _save_providers(p):
    os.makedirs(os.path.dirname(PROVIDERS_FILE), exist_ok=True)
    with open(PROVIDERS_FILE, 'w') as f:
        json.dump(p, f, indent=2, ensure_ascii=False)


# ============================================================
# HTTP client helper
# ============================================================

def api(method, path, data=None, stream=False):
    """Call AgentPaaS API. Returns parsed JSON or raises."""
    cfg = _load_config()
    url = "{}{}".format(cfg["server"].rstrip("/"), path)
    headers = {"Content-Type": "application/json"}
    if cfg.get("api_key"):
        headers["Authorization"] = "Bearer {}".format(cfg["api_key"])

    body = json.dumps(data, ensure_ascii=False).encode() if data else None
    req = urllib.request.Request(url, data=body, headers=headers, method=method)

    try:
        with urllib.request.urlopen(req, timeout=300) as resp:
            raw = resp.read().decode()
            return json.loads(raw) if raw else {}
    except urllib.error.HTTPError as e:
        body = e.read().decode()
        try:
            err = json.loads(body)
            msg = err.get("error", {}).get("message", body)
        except Exception:
            msg = body
        print("Error {}: {}".format(e.code, msg))
        sys.exit(1)
    except urllib.error.URLError as e:
        print("Connection failed: {}".format(e))
        print("Is the server running? Try: agentpaas serve --dev")
        sys.exit(1)


def pp(data):
    """Pretty print JSON."""
    print(json.dumps(data, indent=2, ensure_ascii=False))


# ============================================================
# Commands: config
# ============================================================

def cmd_config_set(args):
    cfg = _load_config()
    if args.server:
        cfg["server"] = args.server
    if args.api_key:
        cfg["api_key"] = args.api_key
    _save_config(cfg)
    print("Config saved to {}".format(CONFIG_FILE))
    print("  Server: {}".format(cfg["server"]))
    print("  API Key: {}...".format(cfg["api_key"][:12]) if cfg.get("api_key") else "  API Key: (not set)")


def cmd_config_show(args):
    cfg = _load_config()
    print("Server:  {}".format(cfg.get("server", "")))
    print("API Key: {}".format("{}...".format(cfg["api_key"][:12]) if cfg.get("api_key") else "(not set)"))
    print("Config:  {}".format(CONFIG_FILE))


# ============================================================
# Commands: serve / create-tenant
# ============================================================

def cmd_serve(args):
    import uvicorn
    uvicorn.run("agentpaas.api.app:app", host=args.host, port=args.port, reload=args.dev)


def cmd_create_tenant(args):
    from agentpaas.db.session import get_db
    from agentpaas.db.models import gen_id, now_utc
    from agentpaas.api.middleware.auth import hash_key
    import secrets
    db = get_db()
    tid = gen_id("tn_")
    uid = gen_id("usr_")
    key = "ap_{}".format(secrets.token_hex(16))
    now = now_utc()
    db.execute("INSERT INTO tenants (id,name,plan,status,created_at) VALUES (?,?,?,'active',?)", (tid, args.name, args.plan, now))
    db.execute("INSERT INTO users (id,tenant_id,email,role,created_at) VALUES (?,?,?,'admin',?)", (uid, tid, args.email, now))
    db.execute("INSERT INTO api_keys (id,tenant_id,user_id,key_hash,key_prefix,name,scopes,rate_limit,status,created_at) VALUES (?,?,?,?,?,'admin',?,600,'active',?)",
               (gen_id("key_"), tid, uid, hash_key(key), key[:8], json.dumps(["agents:*","keys:*","billing:read","admin:*"]), now))
    db.commit()
    print("Tenant: {}".format(tid))
    print("API Key: {}".format(key))
    # Auto-save to config
    cfg = _load_config()
    cfg["api_key"] = key
    _save_config(cfg)
    print("API key saved to config. Ready to use.")


# ============================================================
# Commands: agent
# ============================================================

def cmd_agent_list(args):
    data = api("GET", "/api/v1/agents")
    agents = data.get("agents", [])
    if not agents:
        print("No agents found.")
        return
    print("{:<16} {:<20} {:>4} {:<12} {}".format("ID", "Name", "Ver", "Status", "Updated"))
    print("-" * 80)
    for a in agents:
        print("{:<16} {:<20} {:>4} {:<12} {}".format(
            a["id"], a["name"][:20], a.get("current_version", "?"),
            a.get("status", ""), (a.get("updated_at") or "")[:19]))


def cmd_agent_create(args):
    if args.config:
        with open(args.config) as f:
            config = yaml.safe_load(f)
    else:
        config = {"type": "simple", "systemPrompt": args.prompt or "You are a helpful assistant.",
                  "model": {"provider": "dashscope", "name": "qwen3-max-2026-01-23"}}
    data = api("POST", "/api/v1/agents", {
        "name": args.name, "description": args.description or "",
        "config": config, "tags": args.tags.split(",") if args.tags else [],
    })
    print("Agent created: {}".format(data["agent_id"]))
    print("Version: {}".format(data["version"]))
    print("Endpoint: {}".format(data["endpoint"]))


def cmd_agent_get(args):
    pp(api("GET", "/api/v1/agents/{}".format(args.agent_id)))


def cmd_agent_delete(args):
    api("DELETE", "/api/v1/agents/{}".format(args.agent_id))
    print("Agent {} deleted.".format(args.agent_id))


def cmd_agent_update(args):
    with open(args.config) as f:
        config = yaml.safe_load(f)
    data = api("PUT", "/api/v1/agents/{}".format(args.agent_id),
               {"config": config, "changelog": args.changelog or ""})
    print("Updated: {} -> v{}".format(args.agent_id, data.get("version")))


def cmd_agent_rollback(args):
    data = api("POST", "/api/v1/agents/{}/rollback".format(args.agent_id),
               {"target_version": args.version})
    print("Rolled back to v{}".format(args.version))


def cmd_agent_versions(args):
    data = api("GET", "/api/v1/agents/{}/versions".format(args.agent_id))
    versions = data.get("versions", [])
    print("{:>4} {:<8} {:<20} {}".format("Ver", "Current", "Created", "Changelog"))
    print("-" * 60)
    for v in versions:
        cur = "*" if v.get("is_current") else ""
        print("{:>4} {:<8} {:<20} {}".format(v["version"], cur, (v.get("created_at") or "")[:19], v.get("changelog", "")[:30]))


# ============================================================
# Commands: run
# ============================================================

def cmd_run(args):
    agent_id = args.agent_id
    if args.input_file:
        with open(args.input_file) as f:
            input_text = f.read()
    else:
        input_text = args.input

    if not input_text:
        print("Error: provide input text or --input-file")
        sys.exit(1)

    params = {}
    if args.temperature is not None:
        params["model.temperature"] = args.temperature
    if args.max_steps is not None:
        params["react.maxSteps"] = args.max_steps

    print("Running agent {}...".format(agent_id))
    t0 = time.time()
    data = api("POST", "/api/v1/agents/{}/run".format(agent_id),
               {"input": input_text, "parameters": params})
    elapsed = time.time() - t0

    print("\n--- Result ({:.1f}s) ---".format(elapsed))
    print(data.get("output", ""))
    print("--- Stats ---")
    usage = data.get("usage", {})
    print("  Run ID:    {}".format(data.get("run_id")))
    print("  Tokens:    {} (in: {}, out: {})".format(
        usage.get("total_tokens", 0), usage.get("input_tokens", 0), usage.get("output_tokens", 0)))
    print("  Steps:     {}".format(usage.get("steps", 0)))
    print("  Duration:  {}ms".format(usage.get("duration_ms", 0)))


# ============================================================
# Commands: chat (interactive via PaaS)
# ============================================================

# ── Streaming colors ──
_DIM = "\033[2m"
_CYAN = "\033[36m"
_YELLOW = "\033[33m"
_GREEN = "\033[32m"
_RED = "\033[31m"
_BOLD = "\033[1m"
_RESET = "\033[0m"


def _chat_stream(agent_id, agent_name, input_text):
    """Stream agent execution via SSE. Returns final output or None if unavailable."""
    cfg = _load_config()
    url = "{}/api/v1/agents/{}/run/stream".format(cfg["server"].rstrip("/"), agent_id)
    headers = {"Content-Type": "application/json"}
    if cfg.get("api_key"):
        headers["Authorization"] = "Bearer {}".format(cfg["api_key"])

    body = json.dumps({"input": input_text}, ensure_ascii=False).encode()
    req = urllib.request.Request(url, data=body, headers=headers, method="POST")

    try:
        resp = urllib.request.urlopen(req, timeout=1800)  # 30min for long agent runs
    except urllib.error.HTTPError as e:
        if e.code == 404:
            return None  # Endpoint not available, fallback to sync
        return None
    except Exception:
        return None

    final_output = ""
    step_count = 0

    # Parse SSE stream line by line
    buffer = ""
    current_event = ""
    for raw_line in resp:
        line = raw_line.decode("utf-8", errors="replace").rstrip("\n").rstrip("\r")

        if line.startswith("event: "):
            current_event = line[7:]
        elif line.startswith("data: "):
            data_str = line[6:]
            try:
                data = json.loads(data_str)
            except json.JSONDecodeError:
                continue

            step = data.get("step", 0)
            content = data.get("content", "")
            tool = data.get("tool", "")
            duration = data.get("duration_ms", 0)

            if current_event == "think":
                step_count = step + 1
                # Show a concise thinking indicator with step number
                # Truncate thought to first line or 120 chars for readability
                short = content.split("\n")[0][:120]
                print("{}  [Step {}] 💭 {}{}".format(_DIM, step + 1, short, _RESET))

            elif current_event == "tool_call":
                print("{}  [Step {}] 🔧 {}{} {}".format(
                    _CYAN, step + 1, tool, _RESET, _DIM + content[:80] + _RESET))

            elif current_event == "tool_result":
                # Show truncated result
                short = content.replace("\n", " ")[:100]
                print("{}  [Step {}] ✅ {} → {}{}".format(
                    _GREEN, step + 1, tool, short, _RESET))

            elif current_event == "error":
                print("{}  [Step {}] ❌ {}{}".format(_RED, step + 1, content[:200], _RESET))

            elif current_event == "answer":
                final_output = content

            elif current_event == "done":
                status = data.get("status", "")
                if data.get("output"):
                    final_output = data["output"]
                steps = data.get("steps", step_count)
                tokens = data.get("total_tokens", 0)
                print()
                print("{}📊 {}: {}{}".format(_BOLD, agent_name, _RESET, final_output))
                if tokens:
                    print("{}  [{} steps | {} tokens]{}".format(_DIM, steps, tokens, _RESET))

            current_event = ""
        elif line == "":
            current_event = ""

    resp.close()
    return final_output


def cmd_chat(args):
    """Interactive chat with a deployed agent."""
    target = args.target

    # Resolve agent
    if not target:
        data = api("GET", "/api/v1/agents")
        agents = data.get("agents", [])
        if not agents:
            print("No agents found. Create one first:")
            print("  agentpaas agent create --name lambda --config agent-config.yml")
            return
        if len(agents) == 1:
            agent = agents[0]
        else:
            print("Multiple agents found:\n")
            for i, a in enumerate(agents):
                print("  [{}] {} — {} (v{})".format(i + 1, a["id"], a["name"], a.get("current_version", "?")))
            print()
            try:
                choice = input("Select (number or ID): ").strip()
                if choice.isdigit() and 1 <= int(choice) <= len(agents):
                    agent = agents[int(choice) - 1]
                else:
                    agent = next((a for a in agents if a["id"] == choice), agents[0])
            except (EOFError, KeyboardInterrupt):
                return
    elif target.startswith("ag_"):
        agent = api("GET", "/api/v1/agents/{}".format(target))
    else:
        data = api("GET", "/api/v1/agents")
        agents = data.get("agents", [])
        agent = next((a for a in agents if target.lower() in a.get("name", "").lower()), None)
        if not agent:
            print("Agent '{}' not found. Available:".format(target))
            for a in agents:
                print("  {} — {}".format(a["id"], a["name"]))
            return

    agent_id = agent["id"]
    agent_name = agent.get("name", agent_id)

    print()
    print("╔══════════════════════════════════════════════════════════╗")
    print("║  🐂 {} — via AgentPaaS".format(agent_name[:44]).ljust(56) + "║")
    print("║  Agent: {}  v{}".format(agent_id, agent.get("current_version", "?")).ljust(56) + "║")
    print("╠══════════════════════════════════════════════════════════╣")
    print("║  输入消息开始对话 | exit 退出 | history 查看历史        ║")
    print("╚══════════════════════════════════════════════════════════╝")
    print()

    conversation = []

    while True:
        try:
            user_input = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\n👋 对话结束")
            break

        if not user_input:
            continue
        if user_input.lower() in ("exit", "quit", "bye"):
            print("👋 对话结束")
            break
        if user_input.lower() == "history":
            for e in conversation:
                print("  [{}] {}".format(e["role"], e["content"][:150]))
            print()
            continue

        # Build input with history
        if conversation:
            history = "\n".join("[{}] {}".format(e["role"], e["content"][:300]) for e in conversation[-10:])
            full_input = "=== 对话历史 ===\n{}\n=== 历史结束 ===\n\n[用户] {}".format(history, user_input)
        else:
            full_input = user_input

        conversation.append({"role": "用户", "content": user_input})

        print()
        t0 = time.time()

        # Stream execution via SSE for real-time output
        output = _chat_stream(agent_id, agent_name, full_input)
        elapsed = time.time() - t0

        if output is None:
            # Fallback to sync if stream endpoint unavailable
            data = api("POST", "/api/v1/agents/{}/run".format(agent_id), {"input": full_input})
            elapsed = time.time() - t0
            output = data.get("output", "")
            usage = data.get("usage", {})
            print("📊 {}: {}".format(agent_name, output))
            print()
            print("  [{:.1f}s | {} tokens | {} steps]".format(
                elapsed, usage.get("total_tokens", 0), usage.get("steps", 0)))
        else:
            print()
            print("  [{:.1f}s]".format(elapsed))

        print()
        conversation.append({"role": agent_name, "content": (output or "")[:500]})


# ============================================================
# Commands: status / health
# ============================================================

def cmd_status(args):
    if args.target == "agents":
        data = api("GET", "/api/v1/status/agents?sort={}".format(args.sort or "last_run"))
        agents = data.get("agents", [])
        if not agents:
            print("No agents.")
            return
        print("{:<16} {:<16} {:>5} {:>6} {:>6} {:>8} {:>8} {}".format(
            "ID", "Name", "Score", "Runs", "Fail", "Succ%", "Lat(ms)", "Last Run"))
        print("-" * 100)
        for a in agents:
            h = a.get("health", {})
            score = h.get("score", -1)
            level = h.get("level", "?")
            icon = {"healthy": "+", "degraded": "~", "warning": "!", "critical": "X"}.get(level, "?")
            print("{:<16} {:<16} [{}]{:>3} {:>6} {:>6} {:>8} {:>8} {}".format(
                a["agent_id"], a["name"][:16], icon, score,
                a["runs_total"], a["runs_failed"], a["success_rate"],
                a["avg_latency_ms"], (a.get("last_run") or "never")[:19]))
    elif args.target and args.target.startswith("ag_"):
        pp(api("GET", "/api/v1/status/agents/{}".format(args.target)))
    else:
        data = api("GET", "/api/v1/status")
        print("=== AgentPaaS Status ===")
        print("  Active agents:  {}".format(data.get("agents_active", 0)))
        print("  Total runs:     {}".format(data.get("runs_total", 0)))
        print("  Running now:    {}".format(data.get("runs_running", 0)))
        print("  Success rate:   {}".format(data.get("success_rate", "N/A")))
        print("  Avg latency:    {}ms".format(data.get("avg_latency_ms", 0)))
        print("  Total tokens:   {}".format(data.get("total_tokens", 0)))


def cmd_health(args):
    data = api("GET", "/api/v1/status/agents/{}/health".format(args.agent_id))
    score = data.get("health_score", -1)
    level = data.get("health_level", "?")
    factors = data.get("factors", {})
    icon = {"healthy": "OK", "degraded": "!!", "warning": "!!", "critical": "XX"}.get(level, "??")
    print("[{}] Agent {} health: {}/100 ({})".format(icon, args.agent_id, score, level))
    for k, v in factors.items():
        print("  {}: {}".format(k, v))


# ============================================================
# Commands: runs / trace
# ============================================================

def cmd_runs(args):
    data = api("GET", "/api/v1/agents/{}/runs?limit={}".format(args.agent_id, args.limit))
    runs = data.get("runs", [])
    if not runs:
        print("No runs.")
        return
    print("{:<16} {:<10} {:>8} {:>6} {:>8} {}".format("Run ID", "Status", "Dur(ms)", "Steps", "Tokens", "Created"))
    print("-" * 80)
    for r in runs:
        tokens = (r.get("input_tokens") or 0) + (r.get("output_tokens") or 0)
        print("{:<16} {:<10} {:>8} {:>6} {:>8} {}".format(
            r["id"], r["status"], r.get("duration_ms", 0), r.get("steps", 0),
            tokens, (r.get("created_at") or "")[:19]))


def cmd_trace(args):
    pp(api("GET", "/api/v1/traces/{}".format(args.run_id)))


# ============================================================
# Commands: key
# ============================================================

def cmd_key_create(args):
    data = api("POST", "/api/v1/auth/keys",
               {"name": args.name, "scopes": args.scopes.split(",") if args.scopes else ["agents:read", "agents:execute"],
                "rate_limit": args.rate_limit})
    print("Key created: {}".format(data.get("key_id")))
    print("API Key: {}".format(data.get("api_key")))
    print("Store this key securely!")


def cmd_key_list(args):
    data = api("GET", "/api/v1/auth/keys")
    keys = data.get("keys", [])
    print("{:<16} {:<10} {:<16} {:<8} {}".format("ID", "Prefix", "Name", "Status", "Last Used"))
    print("-" * 70)
    for k in keys:
        print("{:<16} {:<10} {:<16} {:<8} {}".format(
            k["id"], k["key_prefix"], k.get("name", ""), k["status"],
            (k.get("last_used_at") or "never")[:19]))


def cmd_key_revoke(args):
    api("DELETE", "/api/v1/auth/keys/{}".format(args.key_id))
    print("Key {} revoked.".format(args.key_id))


# ============================================================
# Commands: billing / metrics
# ============================================================

def cmd_usage(args):
    data = api("GET", "/api/v1/billing/usage?group_by={}".format(args.group_by))
    pp(data)


def cmd_metrics(args):
    data = api("GET", "/api/v1/metrics/overview")
    pp(data)


# ============================================================
# Commands: provider (local, no API call)
# ============================================================

def cmd_provider_list(args):
    providers = _load_providers()
    print("{:<15} {:<25} {:<45} {}".format("ID", "Name", "Base URL", "Status"))
    print("-" * 100)
    for pid, p in providers.items():
        env = p.get("env_key", "")
        has_key = bool(os.environ.get(env, p.get("api_key", "")))
        status = "OK" if has_key else "NO KEY ({})".format(env)
        print("{:<15} {:<25} {:<45} [{}]".format(pid, p["name"], p.get("base_url", "")[:45], status))


def cmd_provider_add(args):
    providers = _load_providers()
    pid = args.provider_id
    if pid in providers:
        if args.api_key:
            providers[pid]["api_key"] = args.api_key
        if args.base_url:
            providers[pid]["base_url"] = args.base_url
    else:
        providers[pid] = {
            "name": args.name or pid, "type": "openai_compatible",
            "base_url": args.base_url or "", "models": args.models.split(",") if args.models else [],
            "env_key": "{}_API_KEY".format(pid.upper()),
        }
        if args.api_key:
            providers[pid]["api_key"] = args.api_key
    _save_providers(providers)
    if args.api_key:
        env_key = providers[pid].get("env_key", "")
        if env_key:
            os.environ[env_key] = args.api_key
    print("Provider '{}' configured.".format(pid))


def cmd_provider_test(args):
    providers = _load_providers()
    p = providers.get(args.provider_id)
    if not p:
        print("Unknown provider: {}".format(args.provider_id)); return
    env_key = p.get("env_key", "")
    api_key = args.api_key or os.environ.get(env_key, "") or p.get("api_key", "")
    if not api_key:
        print("No API key. Set {} or use --api-key".format(env_key)); return
    base_url = p.get("base_url", "")
    model = args.model or (p.get("models", [""])[0])
    prompt = args.prompt or "Say hello in one sentence."
    print("Testing {} ({})...".format(args.provider_id, model))
    if p.get("type") == "anthropic":
        url = "{}/v1/messages".format(base_url.rstrip("/"))
        body = json.dumps({"model": model, "max_tokens": 256, "messages": [{"role": "user", "content": prompt}]}).encode()
        headers = {"Content-Type": "application/json", "x-api-key": api_key, "anthropic-version": "2023-06-01"}
    else:
        url = "{}/chat/completions".format(base_url.rstrip("/"))
        body = json.dumps({"model": model, "max_tokens": 256, "temperature": 0.3,
                          "messages": [{"role": "user", "content": prompt}]}).encode()
        headers = {"Content-Type": "application/json", "Authorization": "Bearer {}".format(api_key)}
    t0 = time.time()
    try:
        req = urllib.request.Request(url, data=body, headers=headers)
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode())
        text = data["content"][0]["text"] if p.get("type") == "anthropic" else data["choices"][0]["message"]["content"]
        print("OK ({:.1f}s): {}".format(time.time() - t0, text.strip()[:100]))
    except Exception as e:
        print("FAILED: {}".format(e))


def cmd_provider_remove(args):
    providers = _load_providers()
    if args.provider_id in providers:
        del providers[args.provider_id]
        _save_providers(providers)
        print("Removed '{}'.".format(args.provider_id))
    else:
        print("Not found: {}".format(args.provider_id))


def cmd_provider_models(args):
    providers = _load_providers()
    p = providers.get(args.provider_id)
    if not p:
        print("Not found."); return
    api_key = os.environ.get(p.get("env_key", ""), "") or p.get("api_key", "")
    if p.get("type") == "anthropic":
        print("Models: {}".format(", ".join(p.get("models", [])))); return
    try:
        url = "{}/models".format(p["base_url"].rstrip("/"))
        req = urllib.request.Request(url, headers={"Authorization": "Bearer {}".format(api_key)})
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read().decode())
        for m in sorted(m["id"] for m in data.get("data", []))[:20]:
            print("  {}".format(m))
    except Exception as e:
        print("Query failed: {}. Configured: {}".format(e, ", ".join(p.get("models", []))))


# ============================================================
# Main
# ============================================================

def _cmd_launch(args):
    from agentpaas.launch import cmd_launch, cmd_launch_daemon
    if args.daemon:
        cmd_launch_daemon(args.config, port=args.port)
    else:
        cmd_launch(args.config, port=args.port)


def _cmd_wechat(args):
    from agentpaas.wechat import (
        cmd_wechat_start, cmd_wechat_status, cmd_wechat_stop, cmd_wechat_relogin,
    )
    handlers = {
        "start": lambda: cmd_wechat_start(args.agent_id, daemon=args.daemon, claude_code=args.claude_code),
        "status": lambda: cmd_wechat_status(),
        "stop": lambda: cmd_wechat_stop(),
        "relogin": lambda: cmd_wechat_relogin(args.agent_id),
    }
    if args.wechat_cmd in handlers:
        handlers[args.wechat_cmd]()
    else:
        print("agentpaas wechat — 微信 ClawBot 桥接\n")
        print("用法:")
        print("  agentpaas wechat start <agent_id>    # 绑定并开始监听")
        print("  agentpaas wechat start <agent_id> --daemon  # 后台运行")
        print("  agentpaas wechat status              # 查看状态")
        print("  agentpaas wechat stop                # 停止后台进程")
        print("  agentpaas wechat relogin             # 重新扫码登录")




def _cmd_xiaoyi(args):
    from agentpaas.xiaoyi import cmd_xiaoyi_start, cmd_xiaoyi_status, cmd_xiaoyi_stop
    handlers = {
        "start": lambda: cmd_xiaoyi_start(
            paas_agent_id=args.agent_id,
            ak=args.ak, sk=args.sk,
            xiaoyi_agent_id=args.xiaoyi_agent_id,
            claude_code=args.claude_code,
            daemon=args.daemon),
        "status": lambda: cmd_xiaoyi_status(),
        "stop": lambda: cmd_xiaoyi_stop(),
    }
    if args.xiaoyi_cmd in handlers:
        handlers[args.xiaoyi_cmd]()
    else:
        print("agentpaas xiaoyi — 华为小艺 OpenClaw 桥接\n")
        print("用法:")
        print("  agentpaas xiaoyi start <agent_id> --ak <AK> --sk <SK> --xiaoyi-agent-id <ID>")
        print("  agentpaas xiaoyi status")
        print("  agentpaas xiaoyi stop")


def main():
    parser = argparse.ArgumentParser(prog="agentpaas", description="AgentPaaS CLI — manage agents, runs, providers")
    sub = parser.add_subparsers(dest="command")

    # config
    cfg_p = sub.add_parser("config", help="Configure CLI (server URL, API key)")
    cfg_sub = cfg_p.add_subparsers(dest="config_cmd")
    cs = cfg_sub.add_parser("set"); cs.add_argument("--server", default=""); cs.add_argument("--api-key", default="")
    cfg_sub.add_parser("show")

    # serve
    s = sub.add_parser("serve", help="Start API server")
    s.add_argument("--host", default="0.0.0.0"); s.add_argument("--port", type=int, default=8000); s.add_argument("--dev", action="store_true")

    # create-tenant
    ct = sub.add_parser("create-tenant", help="Create tenant + API key")
    ct.add_argument("--name", required=True); ct.add_argument("--plan", default="free"); ct.add_argument("--email", default="")

    # agent
    ag = sub.add_parser("agent", help="Agent management")
    ag_sub = ag.add_subparsers(dest="agent_cmd")
    ag_sub.add_parser("list")
    ac = ag_sub.add_parser("create"); ac.add_argument("--name", required=True); ac.add_argument("--config", default=""); ac.add_argument("--prompt", default=""); ac.add_argument("--description", default=""); ac.add_argument("--tags", default="")
    ag_get = ag_sub.add_parser("get"); ag_get.add_argument("agent_id")
    ag_del = ag_sub.add_parser("delete"); ag_del.add_argument("agent_id")
    ag_up = ag_sub.add_parser("update"); ag_up.add_argument("agent_id"); ag_up.add_argument("--config", required=True); ag_up.add_argument("--changelog", default="")
    ag_rb = ag_sub.add_parser("rollback"); ag_rb.add_argument("agent_id"); ag_rb.add_argument("--version", type=int, required=True)
    ag_ver = ag_sub.add_parser("versions"); ag_ver.add_argument("agent_id")

    # run
    r = sub.add_parser("run", help="Execute an agent")
    r.add_argument("agent_id"); r.add_argument("input", nargs="?", default=""); r.add_argument("--input-file", default="")
    r.add_argument("--temperature", type=float, default=None); r.add_argument("--max-steps", type=int, default=None)
    r.add_argument("--stream", action="store_true")

    # chat (interactive)
    ch = sub.add_parser("chat", help="Interactive chat with a deployed agent")
    ch.add_argument("target", nargs="?", default="", help="Agent name or ID (ag_xxx)")

    # status
    st = sub.add_parser("status", help="Platform/agent status")
    st.add_argument("target", nargs="?", default=""); st.add_argument("--sort", default="last_run")

    # health
    hl = sub.add_parser("health", help="Agent health score")
    hl.add_argument("agent_id")

    # runs
    rn = sub.add_parser("runs", help="List runs for an agent")
    rn.add_argument("agent_id"); rn.add_argument("--limit", type=int, default=20)

    # trace
    tr = sub.add_parser("trace", help="Get run trace details")
    tr.add_argument("run_id")

    # key
    ky = sub.add_parser("key", help="API key management")
    ky_sub = ky.add_subparsers(dest="key_cmd")
    kc = ky_sub.add_parser("create"); kc.add_argument("--name", default="default"); kc.add_argument("--scopes", default=""); kc.add_argument("--rate-limit", type=int, default=60)
    ky_sub.add_parser("list")
    kr = ky_sub.add_parser("revoke"); kr.add_argument("key_id")

    # usage / metrics
    us = sub.add_parser("usage", help="Billing usage"); us.add_argument("--group-by", default="agent")
    sub.add_parser("metrics", help="Platform metrics")

    # launch
    la = sub.add_parser("launch", help="一键启动 agent (服务+注册+对话)")
    la.add_argument("config", help="Agent YAML 配置文件路径")
    la.add_argument("--port", type=int, default=8000, help="PaaS 服务端口 (default: 8000)")
    la.add_argument("--daemon", action="store_true", help="后台运行 PaaS 服务 (不进入对话)")

    # wechat
    wx = sub.add_parser("wechat", help="微信 ClawBot 桥接 (WeChat ↔ Agent)")
    wx_sub = wx.add_subparsers(dest="wechat_cmd")
    wx_start = wx_sub.add_parser("start", help="绑定 agent 并开始监听微信消息")
    wx_start.add_argument("agent_id", help="Agent ID")
    wx_start.add_argument("--claude-code", action="store_true", help="使用 Claude Code CLI 后端 (无需 API Key)")
    wx_start.add_argument("--daemon", action="store_true", help="后台运行")
    wx_sub.add_parser("status", help="查看微信桥接状态")
    wx_sub.add_parser("stop", help="停止后台桥接")
    wx_relogin = wx_sub.add_parser("relogin", help="重新扫码登录")
    wx_relogin.add_argument("agent_id", nargs="?", default=None)

    # xiaoyi (华为小艺 OpenClaw)
    xy = sub.add_parser("xiaoyi", help="华为小艺 OpenClaw 桥接 (XiaoYi ↔ Agent)")
    xy_sub = xy.add_subparsers(dest="xiaoyi_cmd")
    xy_start = xy_sub.add_parser("start", help="绑定 agent 并连接小艺")
    xy_start.add_argument("agent_id", help="PaaS Agent ID")
    xy_start.add_argument("--ak", required=True, help="小艺开放平台 Access Key")
    xy_start.add_argument("--sk", required=True, help="小艺开放平台 Secret Key")
    xy_start.add_argument("--xiaoyi-agent-id", required=True, help="小艺智能体 Agent ID")
    xy_start.add_argument("--claude-code", action="store_true", default=True, help="使用 Claude Code CLI 后端")
    xy_start.add_argument("--daemon", action="store_true", help="后台运行")
    xy_sub.add_parser("status", help="查看小艺桥接状态")
    xy_sub.add_parser("stop", help="停止后台桥接")

    # provider
    pv = sub.add_parser("provider", help="LLM provider management")
    pv_sub = pv.add_subparsers(dest="prov_cmd")
    pv_sub.add_parser("list")
    pa = pv_sub.add_parser("add"); pa.add_argument("provider_id"); pa.add_argument("--api-key", default=""); pa.add_argument("--base-url", default=""); pa.add_argument("--name", default=""); pa.add_argument("--models", default="")
    prm = pv_sub.add_parser("remove"); prm.add_argument("provider_id")
    pt = pv_sub.add_parser("test"); pt.add_argument("provider_id"); pt.add_argument("--api-key", default=""); pt.add_argument("--model", default=""); pt.add_argument("--prompt", default="")
    pm = pv_sub.add_parser("models"); pm.add_argument("provider_id")

    args = parser.parse_args()

    handlers = {
        "config": lambda: cmd_config_set(args) if args.config_cmd == "set" else cmd_config_show(args),
        "serve": lambda: cmd_serve(args),
        "create-tenant": lambda: cmd_create_tenant(args),
        "agent": lambda: {"list": cmd_agent_list, "create": cmd_agent_create, "get": cmd_agent_get,
                          "delete": cmd_agent_delete, "update": cmd_agent_update,
                          "rollback": cmd_agent_rollback, "versions": cmd_agent_versions
                          }.get(args.agent_cmd, lambda a: ag.print_help())(args),
        "run": lambda: cmd_run(args),
        "chat": lambda: cmd_chat(args),
        "status": lambda: cmd_status(args),
        "health": lambda: cmd_health(args),
        "runs": lambda: cmd_runs(args),
        "trace": lambda: cmd_trace(args),
        "key": lambda: {"create": cmd_key_create, "list": cmd_key_list, "revoke": cmd_key_revoke
                        }.get(args.key_cmd, lambda a: ky.print_help())(args),
        "usage": lambda: cmd_usage(args),
        "metrics": lambda: cmd_metrics(args),
        "provider": lambda: {"list": cmd_provider_list, "add": cmd_provider_add, "remove": cmd_provider_remove,
                             "test": cmd_provider_test, "models": cmd_provider_models
                             }.get(args.prov_cmd, lambda a: pv.print_help())(args),
        "launch": lambda: _cmd_launch(args),
        "wechat": lambda: _cmd_wechat(args),
        "xiaoyi": lambda: _cmd_xiaoyi(args),
    }

    if args.command in handlers:
        handlers[args.command]()
    else:
        parser.print_help()
        print("\nQuick start:")
        print("  agentpaas serve --dev              # Start server")
        print("  agentpaas create-tenant --name Org  # Get API key")
        print("  agentpaas agent list                # List agents")
        print("  agentpaas run ag_xxx 'Hello'        # Run an agent")
        print("  agentpaas status                    # Platform status")
        print("  agentpaas provider list             # LLM providers")


if __name__ == "__main__":
    main()
