#!/usr/bin/env python3
"""
Bot Self-Modifier Script
通过 API 读取和修改 bot 配置：系统提示词、MCP服务器、技能、环境变量
"""

import argparse
import json
import os
import sys
import urllib.request
import urllib.error
import urllib.parse


def get_config():
    """获取配置，下面的MASTERKEY和ASSISTANT_ID是从环境变量自动获取的，不需要用户提供"""
    masterkey = os.environ.get("MASTERKEY", "master")
    bot_id = os.environ.get("ASSISTANT_ID", "")
    if not masterkey:
        print("ERROR: MASTERKEY environment variable is required")
        sys.exit(1)
    return masterkey, "http://localhost:8001", bot_id


def api_request(method, url, headers=None, data=None, is_multipart=False):
    """发送 HTTP 请求"""
    if headers is None:
        headers = {}

    if data is not None and not is_multipart:
        data = json.dumps(data).encode("utf-8")
        headers.setdefault("Content-Type", "application/json")

    req = urllib.request.Request(url, data=data, headers=headers, method=method)

    try:
        with urllib.request.urlopen(req) as resp:
            body = resp.read().decode("utf-8")
            return json.loads(body) if body else {}
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8")
        try:
            detail = json.loads(body)
        except Exception:
            detail = body
        print(f"ERROR: HTTP {e.code} - {detail}")
        sys.exit(1)


def build_multipart_data(fields, files):
    """构建 multipart/form-data 请求体"""
    boundary = "----BotModifierBoundary"
    lines = []

    for key, value in fields.items():
        lines.append(f"--{boundary}".encode())
        lines.append(f'Content-Disposition: form-data; name="{key}"'.encode())
        lines.append(b"")
        lines.append(value.encode() if isinstance(value, str) else value)

    for key, (filename, file_data, content_type) in files.items():
        lines.append(f"--{boundary}".encode())
        lines.append(
            f'Content-Disposition: form-data; name="{key}"; filename="{filename}"'.encode()
        )
        lines.append(f"Content-Type: {content_type}".encode())
        lines.append(b"")
        lines.append(file_data)

    lines.append(f"--{boundary}--".encode())
    lines.append(b"")

    body = b"\r\n".join(lines)
    content_type = f"multipart/form-data; boundary={boundary}"
    return body, content_type


# ============ 系统提示词 ============

def get_prompt(bot_id, masterkey, api_base):
    """读取系统提示词"""
    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("GET", url, headers)

    prompt = result.get("system_prompt", "")
    print("=== System Prompt ===")
    print(prompt if prompt else "(empty)")
    return prompt


def set_prompt(bot_id, masterkey, api_base, value):
    """修改系统提示词"""
    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    data = {"system_prompt": value}
    result = api_request("PUT", url, headers, data)
    print(f"OK: System prompt updated. {result.get('message', '')}")


# ============ Bot 基本信息 ============

def get_info(bot_id, masterkey, api_base):
    """读取 bot 基本信息（头像、描述、建议问题）"""
    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("GET", url, headers)

    print("=== Bot Info ===")
    print(f"  Name: {result.get('name', '(empty)')}")
    print(f"  Avatar URL: {result.get('avatar_url') or '(empty)'}")
    print(f"  Description: {result.get('description') or '(empty)'}")
    suggestions = result.get("suggestions") or []
    print(f"  Suggestions: {json.dumps(suggestions, ensure_ascii=False) if suggestions else '(empty)'}")
    return result


def set_avatar(bot_id, masterkey, api_base, value):
    """设置头像 URL"""
    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    data = {"avatar_url": value}
    result = api_request("PUT", url, headers, data)
    print(f"OK: Avatar updated. {result.get('message', '')}")


def set_name(bot_id, masterkey, api_base, value):
    """设置 bot 标题名称"""
    url = f"{api_base}/api/v1/bots/{bot_id}"
    headers = {"Authorization": f"Bearer {masterkey}"}
    data = {"name": value}
    result = api_request("PUT", url, headers, data)
    print(f"OK: Name updated to '{result.get('name', value)}'.")


def set_description(bot_id, masterkey, api_base, value):
    """设置描述"""
    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    data = {"description": value}
    result = api_request("PUT", url, headers, data)
    print(f"OK: Description updated. {result.get('message', '')}")


def set_suggestions(bot_id, masterkey, api_base, value):
    """设置建议问题（JSON 数组）"""
    try:
        suggestions = json.loads(value)
    except json.JSONDecodeError:
        print("ERROR: --value must be a valid JSON array for set_suggestions")
        sys.exit(1)

    if not isinstance(suggestions, list):
        print("ERROR: --value must be a JSON array, e.g. '[\"question1\", \"question2\"]'")
        sys.exit(1)

    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    data = {"suggestions": suggestions}
    result = api_request("PUT", url, headers, data)
    print(f"OK: Suggestions updated. {result.get('message', '')}")


# ============ MCP 服务器 ============

def list_mcp(bot_id, masterkey, api_base):
    """列出 MCP 服务器"""
    url = f"{api_base}/api/v1/bots/{bot_id}/mcp"
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("GET", url, headers)

    if not result:
        print("No MCP servers configured.")
        return

    print("=== MCP Servers ===")
    for server in result:
        enabled_str = "enabled" if server.get("enabled") else "disabled"
        print(f"  [{enabled_str}] {server['name']} (id: {server['id']}, type: {server.get('type', 'N/A')})")
        config = server.get("config", {})
        if config:
            print(f"    config: {json.dumps(config, ensure_ascii=False)}")
    return result


def add_mcp(bot_id, masterkey, api_base, name, mcp_type, config_str):
    """添加 MCP 服务器"""
    try:
        config = json.loads(config_str)
    except json.JSONDecodeError:
        print("ERROR: --config must be valid JSON")
        sys.exit(1)

    url = f"{api_base}/api/v1/bots/{bot_id}/mcp"
    headers = {"Authorization": f"Bearer {masterkey}"}
    data = {
        "name": name,
        "type": mcp_type,
        "config": config,
        "enabled": True,
    }
    result = api_request("POST", url, headers, data)
    print(f"OK: MCP server '{name}' added (id: {result.get('id', 'N/A')})")


def delete_mcp(bot_id, masterkey, api_base, mcp_id):
    """删除 MCP 服务器"""
    url = f"{api_base}/api/v1/bots/{bot_id}/mcp/{mcp_id}"
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("DELETE", url, headers)
    print(f"OK: MCP server deleted. {result.get('message', '')}")


# ============ 技能操作 ============

def list_skills(bot_id, masterkey, api_base):
    """列出所有技能"""
    # 获取技能文件列表
    url = f"{api_base}/api/v1/skill/list?bot_id={urllib.parse.quote(bot_id)}"
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("GET", url, headers)

    # 获取当前启用的技能
    settings_url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    settings = api_request("GET", settings_url, headers)
    enabled_skills_str = settings.get("skills", "") or ""
    enabled_skills = [s.strip() for s in enabled_skills_str.split(",") if s.strip()]

    skills = result.get("skills", [])
    print(f"=== Skills (total: {result.get('total', len(skills))}) ===")
    print(f"Enabled: {', '.join(enabled_skills) if enabled_skills else '(none)'}")
    print()
    for skill in skills:
        is_user = "[user]" if skill.get("user_skill") else "[official]"
        is_enabled = "*" if skill["name"] in enabled_skills else " "
        print(f"  {is_enabled} {is_user} {skill['name']}: {skill.get('description', '')}")
    return skills


def upload_skill(bot_id, masterkey, api_base, file_path):
    """上传技能 zip"""
    if not os.path.exists(file_path):
        print(f"ERROR: File not found: {file_path}")
        sys.exit(1)

    filename = os.path.basename(file_path)
    with open(file_path, "rb") as f:
        file_data = f.read()

    body, content_type = build_multipart_data(
        fields={"bot_id": bot_id},
        files={"file": (filename, file_data, "application/zip")},
    )

    url = f"{api_base}/api/v1/skill/upload"
    headers = {
        "Authorization": f"Bearer {masterkey}",
        "Content-Type": content_type,
    }
    result = api_request("POST", url, headers, body, is_multipart=True)
    print(f"OK: Skill uploaded. {result.get('message', '')} (name: {result.get('skill_name', 'N/A')})")


def enable_skill(bot_id, masterkey, api_base, skill_names):
    """启用技能 - 将技能名添加到 settings.skills"""
    # 先获取当前启用的技能
    settings_url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    settings = api_request("GET", settings_url, headers)

    current = settings.get("skills", "") or ""
    current_list = [s.strip() for s in current.split(",") if s.strip()]

    new_skills = [s.strip() for s in skill_names.split(",") if s.strip()]
    for s in new_skills:
        if s not in current_list:
            current_list.append(s)

    updated = ",".join(current_list)
    data = {"skills": updated}
    result = api_request("PUT", settings_url, headers, data)
    print(f"OK: Skills enabled. Current: {updated}")


def disable_skill(bot_id, masterkey, api_base, skill_name):
    """禁用技能 - 从 settings.skills 中移除"""
    settings_url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    settings = api_request("GET", settings_url, headers)

    current = settings.get("skills", "") or ""
    current_list = [s.strip() for s in current.split(",") if s.strip()]

    to_remove = [s.strip() for s in skill_name.split(",") if s.strip()]
    current_list = [s for s in current_list if s not in to_remove]

    updated = ",".join(current_list)
    data = {"skills": updated}
    result = api_request("PUT", settings_url, headers, data)
    print(f"OK: Skills disabled. Current: {updated if updated else '(none)'}")


def delete_skill(bot_id, masterkey, api_base, skill_name):
    """删除已上传的技能"""
    url = (
        f"{api_base}/api/v1/skill/remove"
        f"?bot_id={urllib.parse.quote(bot_id)}"
        f"&skill_name={urllib.parse.quote(skill_name)}"
    )
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("DELETE", url, headers)
    print(f"OK: Skill '{skill_name}' deleted. {result.get('message', '')}")

    # 同时从启用列表中移除
    disable_skill(bot_id, masterkey, api_base, skill_name)


# ============ 环境变量 ============

def get_env(bot_id, masterkey, api_base):
    """读取环境变量"""
    url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    result = api_request("GET", url, headers)

    shell_env = result.get("shell_env", {})
    print("=== Environment Variables ===")
    if not shell_env:
        print("(none)")
    else:
        for key, value in shell_env.items():
            display_val = value if value else "(empty)"
            print(f"  {key}={display_val}")
    return shell_env


def set_env(bot_id, masterkey, api_base, env_json):
    """修改环境变量（合并更新，不会覆盖未指定的变量）"""
    # 先获取当前环境变量
    settings_url = f"{api_base}/api/v1/bots/{bot_id}/settings"
    headers = {"Authorization": f"Bearer {masterkey}"}
    settings = api_request("GET", settings_url, headers)

    current_env = settings.get("shell_env", {}) or {}

    try:
        new_env = json.loads(env_json)
    except json.JSONDecodeError:
        print("ERROR: --value must be valid JSON for set_env")
        sys.exit(1)

    # 合并
    current_env.update(new_env)

    data = {"shell_env": current_env}
    result = api_request("PUT", settings_url, headers, data)
    print(f"OK: Environment variables updated. {result.get('message', '')}")
    for key, value in current_env.items():
        print(f"  {key}={value if value else '(empty)'}")


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

def main():
    parser = argparse.ArgumentParser(description="Bot Self-Modifier: read and modify bot configuration via API")
    parser.add_argument(
        "--action",
        required=True,
        choices=[
            "get_prompt", "set_prompt",
            "get_info", "set_name", "set_avatar", "set_description", "set_suggestions",
            "list_mcp", "add_mcp", "delete_mcp",
            "list_skills", "upload_skill", "enable_skill", "disable_skill", "delete_skill",
            "get_env", "set_env",
        ],
        help="Action to perform",
    )
    parser.add_argument("--value", help="Value for the action (prompt text, skill name, env JSON, etc.)")
    parser.add_argument("--name", help="MCP server name (for add_mcp)")
    parser.add_argument("--mcp-type", default="sse", help="MCP server type (for add_mcp, default: sse)")
    parser.add_argument("--config", help="MCP server config JSON (for add_mcp)")
    parser.add_argument("--mcp-id", help="MCP server ID (for delete_mcp)")
    parser.add_argument("--file", help="Skill zip file path (for upload_skill)")

    args = parser.parse_args()
    masterkey, api_base, bot_id = get_config()

    action_map = {
        "get_prompt": lambda: get_prompt(bot_id, masterkey, api_base),
        "set_prompt": lambda: set_prompt(bot_id, masterkey, api_base, args.value),
        "get_info": lambda: get_info(bot_id, masterkey, api_base),
        "set_name": lambda: set_name(bot_id, masterkey, api_base, args.value),
        "set_avatar": lambda: set_avatar(bot_id, masterkey, api_base, args.value),
        "set_description": lambda: set_description(bot_id, masterkey, api_base, args.value),
        "set_suggestions": lambda: set_suggestions(bot_id, masterkey, api_base, args.value),
        "list_mcp": lambda: list_mcp(bot_id, masterkey, api_base),
        "add_mcp": lambda: add_mcp(bot_id, masterkey, api_base, args.name, args.mcp_type, args.config),
        "delete_mcp": lambda: delete_mcp(bot_id, masterkey, api_base, args.mcp_id),
        "list_skills": lambda: list_skills(bot_id, masterkey, api_base),
        "upload_skill": lambda: upload_skill(bot_id, masterkey, api_base, args.file),
        "enable_skill": lambda: enable_skill(bot_id, masterkey, api_base, args.value),
        "disable_skill": lambda: disable_skill(bot_id, masterkey, api_base, args.value),
        "delete_skill": lambda: delete_skill(bot_id, masterkey, api_base, args.value),
        "get_env": lambda: get_env(bot_id, masterkey, api_base),
        "set_env": lambda: set_env(bot_id, masterkey, api_base, args.value),
    }

    # 参数验证
    if args.action == "set_prompt" and not args.value:
        print("ERROR: --value is required for set_prompt")
        sys.exit(1)
    if args.action in ("set_avatar", "set_name", "set_description", "set_suggestions") and not args.value:
        print(f"ERROR: --value is required for {args.action}")
        sys.exit(1)
    if args.action == "add_mcp" and (not args.name or not args.config):
        print("ERROR: --name and --config are required for add_mcp")
        sys.exit(1)
    if args.action == "delete_mcp" and not args.mcp_id:
        print("ERROR: --mcp-id is required for delete_mcp")
        sys.exit(1)
    if args.action == "upload_skill" and not args.file:
        print("ERROR: --file is required for upload_skill")
        sys.exit(1)
    if args.action in ("enable_skill", "disable_skill", "delete_skill") and not args.value:
        print(f"ERROR: --value is required for {args.action}")
        sys.exit(1)
    if args.action == "set_env" and not args.value:
        print("ERROR: --value is required for set_env (JSON format)")
        sys.exit(1)

    action_map[args.action]()


if __name__ == "__main__":
    main()
