"""Step definitions for features/tdd_stdio_transport.feature."""

from __future__ import annotations

import json
import os
import sys


from behave import given, then, when
from behave.runner import Context

from cleveragents.mcp.adapter import MCPServerConfig
from cleveragents.mcp.stdio_transport import StdioMCPTransport


def _get_stdio_server_path() -> str:
    return os.path.join(
        os.path.dirname(__file__),
        "..",
        "mocks",
        "mcp_stub_server_stdio.py",
    )


@given("the MCP stdio stub server is available")
def step_stub_server_available(context: Context) -> None:
    server_path = _get_stdio_server_path()
    if not os.path.exists(server_path):
        pytest_path = sys.executable
        msg = (
            f"MCP stdio stub server not found at {server_path} (python: {pytest_path})"
        )
        raise RuntimeError(msg)
    context.mcp_stdio_server_path = server_path


@given("an MCP server config using the stdio stub server")
def step_config_with_stub_server(context: Context) -> None:
    server_path = _get_stdio_server_path()
    context.mcp_config = MCPServerConfig(
        name="test-stdio-server",
        transport="stdio",
        command=sys.executable,
        args=[server_path],
    )


@given("an MCP server config with stdio transport and no command")
def step_config_stdio_no_command(context: Context) -> None:
    context.mcp_config = MCPServerConfig(
        name="bad-server",
        transport="stdio",
    )


@given("an MCP server config with stdio transport and command {command}")
def step_config_stdio_bad_command(context: Context, command: str) -> None:
    context.mcp_config = MCPServerConfig(
        name="bad-server",
        transport="stdio",
        command=command,
    )


@given("the config command is set to the stdio stub server path")
def step_config_command_set(context: Context) -> None:
    server_path = _get_stdio_server_path()
    context.mcp_config = MCPServerConfig(
        name="test-server",
        transport="stdio",
        command=sys.executable,
        args=[server_path],
    )


@given("a connected stdio transport")
def step_connected_stdio_transport(context: Context) -> None:
    server_path = _get_stdio_server_path()
    config = MCPServerConfig(
        name="test-stdio-server",
        transport="stdio",
        command=sys.executable,
        args=[server_path],
    )
    context.mcp_stdio_transport = StdioMCPTransport()
    context.mcp_stdio_capabilities = context.mcp_stdio_transport.connect(config)


@when("I create a stdio transport")
def step_create_stdio_transport(context: Context) -> None:
    context.mcp_stdio_transport = StdioMCPTransport()


@when("I connect the transport")
def step_connect_transport(context: Context) -> None:
    context.mcp_stdio_error = None
    try:
        context.mcp_stdio_capabilities = context.mcp_stdio_transport.connect(
            context.mcp_config
        )
    except Exception as exc:
        context.mcp_stdio_error = str(exc)


@when("I create a stdio transport and connect")
def step_create_and_connect_transport(context: Context) -> None:
    context.mcp_stdio_error = None
    try:
        transport = StdioMCPTransport()
        context.mcp_stdio_capabilities = transport.connect(context.mcp_config)
        context.mcp_stdio_transport = transport
    except Exception as exc:
        context.mcp_stdio_error = str(exc)


@when("I connect the transport again")
def step_reconnect_transport(context: Context) -> None:
    context.mcp_stdio_capabilities = context.mcp_stdio_transport.connect(
        context.mcp_config
    )


@when("I close the transport")
def step_close_transport(context: Context) -> None:
    context.mcp_stdio_transport.close()


@when('I call "{method}" on the transport')
def step_call_method(context: Context, method: str) -> None:
    context.mcp_stdio_call_error = None
    context.mcp_stdio_response = None
    try:
        context.mcp_stdio_response = context.mcp_stdio_transport.call(method, {})
    except Exception as exc:
        context.mcp_stdio_call_error = str(exc)


@when('I call "{method}" with arguments {args_json}')
def step_call_method_with_args(context: Context, method: str, args_json: str) -> None:
    arguments = json.loads(args_json)
    context.mcp_stdio_call_error = None
    context.mcp_stdio_response = None
    try:
        context.mcp_stdio_response = context.mcp_stdio_transport.call(method, arguments)
    except Exception as exc:
        context.mcp_stdio_call_error = str(exc)


@when('I send a "{method}" notification with params {params_json}')
def step_send_notification(context: Context, method: str, params_json: str) -> None:
    params = json.loads(params_json)
    context.mcp_stdio_transport._send_notification(method, params)


# -------------------------------------------------------------------
# Then Steps
# -------------------------------------------------------------------


@then("the MCP stdio transport should be connected")
def step_mcp_stdio_transport_connected(context: Context) -> None:
    assert context.mcp_stdio_transport is not None
    assert context.mcp_stdio_transport._connected


@then("the MCP stdio transport should not be connected")
def step_mcp_stdio_transport_not_connected(context: Context) -> None:
    assert context.mcp_stdio_transport is not None
    assert not context.mcp_stdio_transport._connected


@then("the server capabilities should include tools support")
def step_capabilities_have_tools(context: Context) -> None:
    caps = context.mcp_stdio_capabilities
    assert caps is not None
    assert "capabilities" in caps
    assert caps["capabilities"].get("tools") is not None


@then("the connection should fail with error containing {text}")
def step_connection_error_contains(context: Context, text: str) -> None:
    assert context.mcp_stdio_error is not None
    text = text.strip('"')
    assert text.lower() in context.mcp_stdio_error.lower(), (
        f"Expected error containing '{text}', got: {context.mcp_stdio_error}"
    )


@then('the response should contain "{key}" key')
def step_response_contains_key(context: Context, key: str) -> None:
    assert context.mcp_stdio_response is not None
    assert key in context.mcp_stdio_response, (
        f"Key '{key}' not in response: {context.mcp_stdio_response}"
    )


@then("the tools list should have {count:d} tools")
def step_tools_list_count(context: Context, count: int) -> None:
    tools = context.mcp_stdio_response.get("tools", [])
    assert len(tools) == count, f"Expected {count} tools, got {len(tools)}: {tools}"


@then('tool {idx:d} should have name "{name}"')
def step_tool_has_name(context: Context, idx: int, name: str) -> None:
    tools = context.mcp_stdio_response.get("tools", [])
    assert tools[idx]["name"] == name


@then("the call should succeed")
def step_call_succeeded(context: Context) -> None:
    assert context.mcp_stdio_call_error is None
    assert context.mcp_stdio_response is not None


@then("the call should fail with error containing {text}")
def step_call_failed(context: Context, text: str) -> None:
    text = text.strip('"')
    error_msg = context.mcp_stdio_call_error or ""
    assert text.lower() in error_msg.lower(), (
        f"Expected error containing '{text}', got: {error_msg}"
    )


@then('the response content should contain "{text}"')
def step_response_content_contains(context: Context, text: str) -> None:
    content = context.mcp_stdio_response.get("content", [])
    content_text = json.dumps(content)
    assert text in content_text, f"Expected '{text}' in content, got: {content_text}"


@then("the adapter transport type should be {transport}")
def step_adapter_transport_type(context: Context, transport: str) -> None:
    actual = context.mcp_adapter.transport_type
    transport = transport.strip('"')
    assert actual == transport, f"Expected transport type '{transport}', got '{actual}'"


@then("the MCP stdio transport should still be connected")
def step_mcp_stdio_transport_still_connected(context: Context) -> None:
    assert context.mcp_stdio_transport is not None
    assert context.mcp_stdio_transport._connected
