#!/usr/bin/env python3
"""
OpenClaw MCP Server
MCP stdio 프로토콜 (JSON-RPC 2.0, protocolVersion '2024-11-05') 구현
"""

import json
import os
import subprocess
import sys


def send_response(response: dict) -> None:
    """stdout으로 JSON-RPC 응답 전송"""
    line = json.dumps(response, ensure_ascii=False)
    sys.stdout.write(line + "\n")
    sys.stdout.flush()


def send_error(req_id, code: int, message: str) -> None:
    """JSON-RPC 에러 응답 전송"""
    send_response({"jsonrpc": "2.0", "id": req_id, "error": {"code": code, "message": message}})


def extract_text(data) -> str:
    """openclaw --json 응답에서 텍스트 추출. 우선순위: response, text, message, content"""
    if isinstance(data, dict):
        for key in ("response", "text", "message", "content"):
            if key in data:
                value = data[key]
                if isinstance(value, str):
                    return value
                return json.dumps(value, ensure_ascii=False)
    # dict가 아니면 그대로 직렬화
    return json.dumps(data, ensure_ascii=False)


def run_openclaw(agent_id: str, message: str, timeout: float, current_depth: int) -> tuple[bool, str]:
    """
    openclaw agent --agent <id> -m <msg> --json 실행 후 (is_error, text) 반환
    timeout: 초 단위 (subprocess timeout)
    current_depth: 현재 호출 깊이 (subprocess 환경변수에 current_depth+1 로 전달)
    """
    cmd = ["openclaw", "agent", "--agent", agent_id, "-m", message, "--json"]

    # subprocess 환경에 깊이 + 1 을 주입하여 재귀 감지가 가능하도록 함
    child_env = os.environ.copy()
    child_env["OPENCLAW_MCP_DEPTH"] = str(current_depth + 1)

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, env=child_env)
        raw_output = result.stdout.strip()

        # 종료 코드가 비정상인 경우
        if result.returncode != 0:
            stderr_msg = result.stderr.strip()
            error_detail = stderr_msg if stderr_msg else raw_output
            return True, f"openclaw exited with code {result.returncode}: {error_detail}"

        if not raw_output:
            return True, "openclaw returned empty output"

        # JSON 파싱 시도
        try:
            parsed = json.loads(raw_output)
            text = extract_text(parsed)
            return False, text
        except json.JSONDecodeError:
            # 파싱 실패 시 원문 그대로 반환
            return False, raw_output

    except subprocess.TimeoutExpired:
        return True, f"openclaw timed out after {timeout} seconds"
    except FileNotFoundError:
        return True, "openclaw command not found"
    except Exception as exc:
        return True, f"Unexpected error running openclaw: {exc}"


def handle_initialize(req_id, _params) -> None:
    send_response(
        {
            "jsonrpc": "2.0",
            "id": req_id,
            "result": {
                "protocolVersion": "2024-11-05",
                "serverInfo": {"name": "openclaw-mcp-server", "version": "1.1.0"},
                "capabilities": {"tools": {}},
            },
        }
    )


def handle_tools_list(req_id, _params) -> None:
    send_response(
        {
            "jsonrpc": "2.0",
            "id": req_id,
            "result": {
                "tools": [
                    {
                        "name": "openclaw_agent",
                        "description": "OpenClaw 에이전트에 메시지를 보내고 응답을 받습니다.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "message": {"type": "string", "description": "에이전트에 보낼 메시지 (필수)"},
                                "agent": {"type": "string", "description": '에이전트 ID (선택, 기본값: "main")'},
                                "timeout": {"type": "number", "description": "타임아웃(초, 선택)"},
                            },
                            "required": ["message"],
                        },
                    }
                ]
            },
        }
    )


def handle_tools_call(req_id, params) -> None:
    tool_name = params.get("name", "")
    if tool_name != "openclaw_agent":
        send_error(req_id, -32601, f"Unknown tool: {tool_name}")
        return

    # --- 재귀 호출 방지 ---
    try:
        current_depth = int(os.environ.get("OPENCLAW_MCP_DEPTH", "0"))
    except (TypeError, ValueError):
        current_depth = 0

    try:
        max_depth = int(os.environ.get("OPENCLAW_MCP_MAX_DEPTH", "1"))
    except (TypeError, ValueError):
        max_depth = 1

    if current_depth >= max_depth:
        send_response(
            {
                "jsonrpc": "2.0",
                "id": req_id,
                "result": {
                    "content": [
                        {
                            "type": "text",
                            "text": (
                                f"Error: recursive call detected (depth={current_depth}, max={max_depth}). "
                                "openclaw_agent cannot be called from within an openclaw session."
                            ),
                        }
                    ],
                    "isError": True,
                },
            }
        )
        return
    # --- 재귀 호출 방지 끝 ---

    arguments = params.get("arguments") or params.get("input") or {}

    # message 파라미터 검증
    message = arguments.get("message")
    if not message:
        send_response(
            {
                "jsonrpc": "2.0",
                "id": req_id,
                "result": {
                    "content": [{"type": "text", "text": "Error: 'message' parameter is required"}],
                    "isError": True,
                },
            }
        )
        return

    agent_id = arguments.get("agent", "main")

    # timeout: 파라미터 + 10초 여유, 기본 1810초 (30분)
    raw_timeout = arguments.get("timeout")
    if raw_timeout is not None:
        try:
            subprocess_timeout = float(raw_timeout) + 10.0
        except (TypeError, ValueError):
            subprocess_timeout = 1810.0
    else:
        subprocess_timeout = 1810.0

    is_error, text = run_openclaw(agent_id, message, subprocess_timeout, current_depth)

    send_response(
        {"jsonrpc": "2.0", "id": req_id, "result": {"content": [{"type": "text", "text": text}], "isError": is_error}}
    )


def process_message(raw_line: str) -> None:
    """단일 JSON-RPC 메시지 처리"""
    try:
        msg = json.loads(raw_line)
    except json.JSONDecodeError:
        # 파싱 불가 라인은 무시
        return

    req_id = msg.get("id")
    method = msg.get("method", "")

    # id가 없는 메시지는 notification → 응답 없이 무시
    if req_id is None:
        return

    params = msg.get("params") or {}

    if method == "initialize":
        handle_initialize(req_id, params)
    elif method == "tools/list":
        handle_tools_list(req_id, params)
    elif method == "tools/call":
        handle_tools_call(req_id, params)
    else:
        send_error(req_id, -32601, f"Method not found: {method}")


def main() -> None:
    for raw_line in sys.stdin:
        stripped = raw_line.strip()
        if not stripped:
            continue
        process_message(stripped)


if __name__ == "__main__":
    main()
