//! Wiremock tests for `list_sessions` and `list_commands` tools.

#![allow(clippy::expect_used, clippy::unwrap_used)]

mod support;

use agentic_tools_core::Tool;
use agentic_tools_core::ToolContext;
use agentic_tools_core::ToolError;
use opencode_orchestrator_mcp::tools::GetSessionStateTool;
use opencode_orchestrator_mcp::tools::ListAgentsTool;
use opencode_orchestrator_mcp::tools::ListCommandsTool;
use opencode_orchestrator_mcp::tools::ListSessionsTool;
use opencode_orchestrator_mcp::types::GetSessionStateInput;
use opencode_orchestrator_mcp::types::ListAgentsInput;
use opencode_orchestrator_mcp::types::ListCommandsInput;
use opencode_orchestrator_mcp::types::ListSessionsInput;
use opencode_orchestrator_mcp::types::SessionStatusSummary;
use opencode_orchestrator_mcp::types::ToolStateSummary;
use serde_json::json;
use std::sync::Arc;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;

use support::busy_status_fixture;
use support::commands_list_fixture;
use support::message_fixture;
use support::message_history_fixture;
use support::retry_status_fixture;
use support::seed_spawned_sessions;
use support::session_fixture;
use support::session_fixture_with_path;
use support::session_status_fixture;
use support::sessions_list_fixture;
use support::test_orchestrator_server;
use support::tool_part_fixture;
use support::unknown_status_fixture;

#[tokio::test]
async fn list_sessions_returns_session_ids() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session"))
        .respond_with(
            ResponseTemplate::new(200).set_body_json(sessions_list_fixture(&["ses-1", "ses-2"])),
        )
        .mount(&mock)
        .await;

    let tool = ListSessionsTool::new(Arc::clone(&server));

    let result = tool
        .call(ListSessionsInput { limit: None }, &ToolContext::default())
        .await
        .expect("list_sessions should succeed");

    assert_eq!(result.sessions.len(), 2);
    assert_eq!(result.sessions[0].id, "ses-1");
    assert_eq!(result.sessions[1].id, "ses-2");
    assert!(result.sessions[0].status.is_none());
    assert!(result.sessions[1].status.is_none());
}

#[tokio::test]
async fn list_sessions_returns_empty_list() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([])))
        .mount(&mock)
        .await;

    let tool = ListSessionsTool::new(Arc::clone(&server));

    let result = tool
        .call(ListSessionsInput { limit: None }, &ToolContext::default())
        .await
        .expect("list_sessions should succeed");

    assert!(result.sessions.is_empty());
}

#[tokio::test]
async fn list_sessions_returns_enriched_fields() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([
            {
                "id": "ses-1",
                "slug": "ses-1",
                "projectId": "proj1",
                "directory": "/tmp/project-a",
                "path": "src/a.rs",
                "title": "Session A",
                "version": "1.0",
                "summary": { "additions": 5, "deletions": 2, "files": 1 },
                "time": { "created": 10, "updated": 20 }
            },
            {
                "id": "ses-2",
                "slug": "ses-2",
                "projectId": "proj1",
                "directory": "/tmp/project-b",
                "path": "src/b.rs",
                "title": "Session B",
                "version": "1.0",
                "summary": { "additions": 1, "deletions": 0, "files": 3 },
                "time": { "created": 30, "updated": 40 }
            }
        ])))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200).set_body_json(session_status_fixture(&[
                ("ses-1", busy_status_fixture()),
                ("ses-2", retry_status_fixture(2, "rate limited", 1234)),
            ])),
        )
        .mount(&mock)
        .await;

    let tool = ListSessionsTool::new(Arc::clone(&server));
    let result = tool
        .call(ListSessionsInput { limit: None }, &ToolContext::default())
        .await
        .expect("list_sessions should succeed");

    assert_eq!(result.sessions.len(), 2);

    let first = &result.sessions[0];
    assert_eq!(first.created, Some(10));
    assert_eq!(first.updated, Some(20));
    assert_eq!(first.directory.as_deref(), Some("/tmp/project-a"));
    assert_eq!(first.path.as_deref(), Some("src/a.rs"));
    assert!(matches!(first.status, Some(SessionStatusSummary::Busy)));
    let first_stats = first.change_stats.as_ref().expect("change stats expected");
    assert_eq!(first_stats.additions, 5);
    assert_eq!(first_stats.deletions, 2);
    assert_eq!(first_stats.files, 1);

    let second = &result.sessions[1];
    assert!(matches!(
        second.status,
        Some(SessionStatusSummary::Retry {
            attempt: 2,
            ref message,
            next: 1234,
        }) if message == "rate limited"
    ));
    assert_eq!(second.path.as_deref(), Some("src/b.rs"));
}

#[tokio::test]
async fn list_sessions_marks_launched_by_you() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    seed_spawned_sessions(&server, &["ses-1"]).await;

    Mock::given(method("GET"))
        .and(path("/session"))
        .respond_with(
            ResponseTemplate::new(200).set_body_json(sessions_list_fixture(&["ses-1", "ses-2"])),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
        .mount(&mock)
        .await;

    let tool = ListSessionsTool::new(Arc::clone(&server));
    let result = tool
        .call(ListSessionsInput { limit: None }, &ToolContext::default())
        .await
        .expect("list_sessions should succeed");

    assert!(result.sessions[0].launched_by_you);
    assert!(!result.sessions[1].launched_by_you);
    assert!(matches!(
        result.sessions[0].status,
        Some(SessionStatusSummary::Idle)
    ));
    assert!(matches!(
        result.sessions[1].status,
        Some(SessionStatusSummary::Idle)
    ));
}

#[tokio::test]
async fn list_sessions_treats_unknown_status_as_busy() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session"))
        .respond_with(ResponseTemplate::new(200).set_body_json(sessions_list_fixture(&["ses-1"])))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200).set_body_json(session_status_fixture(&[(
                "ses-1",
                unknown_status_fixture(),
            )])),
        )
        .mount(&mock)
        .await;

    let tool = ListSessionsTool::new(Arc::clone(&server));
    let result = tool
        .call(ListSessionsInput { limit: None }, &ToolContext::default())
        .await
        .expect("list_sessions should succeed");

    assert!(matches!(
        result.sessions[0].status,
        Some(SessionStatusSummary::Busy)
    ));
}

#[tokio::test]
async fn get_session_state_returns_idle_status() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_fixture_with_path("ses-1", Some("src/session.rs"))),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert!(matches!(result.status, SessionStatusSummary::Idle));
    assert!(!result.launched_by_you);
    assert_eq!(result.path.as_deref(), Some("src/session.rs"));
}

#[tokio::test]
async fn get_session_state_returns_busy_status() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_fixture_with_path("ses-1", Some("src/session.rs"))),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_status_fixture(&[("ses-1", busy_status_fixture())])),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert!(matches!(result.status, SessionStatusSummary::Busy));
    assert_eq!(result.path.as_deref(), Some("src/session.rs"));
}

#[tokio::test]
async fn get_session_state_returns_retry_status() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_fixture_with_path("ses-1", Some("src/session.rs"))),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200).set_body_json(session_status_fixture(&[(
                "ses-1",
                retry_status_fixture(3, "provider overloaded", 9876),
            )])),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert!(matches!(
        result.status,
        SessionStatusSummary::Retry {
            attempt: 3,
            ref message,
            next: 9876,
        } if message == "provider overloaded"
    ));
    assert_eq!(result.path.as_deref(), Some("src/session.rs"));
}

#[tokio::test]
async fn get_session_state_treats_unknown_status_as_busy() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_fixture_with_path("ses-1", Some("src/session.rs"))),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200).set_body_json(session_status_fixture(&[(
                "ses-1",
                unknown_status_fixture(),
            )])),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert!(matches!(result.status, SessionStatusSummary::Busy));
    assert_eq!(result.path.as_deref(), Some("src/session.rs"));
}

#[tokio::test]
async fn get_session_state_summarizes_messages_and_launched_by_you() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    seed_spawned_sessions(&server, &["ses-1"]).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_fixture_with_path("ses-1", Some("src/session.rs"))),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_status_fixture(&[("ses-1", busy_status_fixture())])),
        )
        .mount(&mock)
        .await;

    let history = message_history_fixture(vec![
        message_fixture(
            "ses-1",
            "m1",
            "assistant",
            1,
            Some(2),
            vec![tool_part_fixture(
                "call-pending",
                "read",
                Some(json!({ "status": "pending", "input": {}, "raw": "read" })),
            )],
        ),
        message_fixture(
            "ses-1",
            "m2",
            "assistant",
            2,
            Some(3),
            vec![tool_part_fixture(
                "call-running",
                "write",
                Some(json!({ "status": "running", "input": {}, "time": { "start": 20 } })),
            )],
        ),
        message_fixture(
            "ses-1",
            "m3",
            "assistant",
            3,
            Some(4),
            vec![tool_part_fixture(
                "call-completed",
                "grep",
                Some(json!({
                    "status": "completed",
                    "input": {},
                    "output": "done",
                    "title": "grep",
                    "metadata": {},
                    "time": { "start": 30, "end": 31 }
                })),
            )],
        ),
        message_fixture(
            "ses-1",
            "m4",
            "assistant",
            4,
            Some(5),
            vec![tool_part_fixture(
                "call-error",
                "edit",
                Some(json!({
                    "status": "error",
                    "input": {},
                    "error": "boom",
                    "time": { "start": 40, "end": 41 }
                })),
            )],
        ),
        message_fixture("ses-1", "m5", "user", 6, None, vec![]),
    ]);

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(history))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert!(result.launched_by_you);
    assert_eq!(result.pending_message_count, 1);
    assert_eq!(result.last_activity, Some(6));
    assert_eq!(result.directory.as_deref(), Some("/tmp"));
    assert_eq!(result.path.as_deref(), Some("src/session.rs"));
    assert_eq!(result.recent_tool_calls.len(), 4);
    assert!(matches!(
        result.recent_tool_calls[0].state,
        ToolStateSummary::Error { ref message } if message == "boom"
    ));
    assert!(matches!(
        result.recent_tool_calls[1].state,
        ToolStateSummary::Completed
    ));
    assert!(matches!(
        result.recent_tool_calls[2].state,
        ToolStateSummary::Running
    ));
    assert!(matches!(
        result.recent_tool_calls[3].state,
        ToolStateSummary::Pending
    ));
}

#[tokio::test]
async fn get_session_state_orders_tool_parts_recent_first_within_message() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(ResponseTemplate::new(200).set_body_json(session_fixture("ses-1")))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_status_fixture(&[("ses-1", busy_status_fixture())])),
        )
        .mount(&mock)
        .await;

    let history = message_history_fixture(vec![message_fixture(
        "ses-1",
        "m1",
        "assistant",
        1,
        Some(2),
        vec![
            tool_part_fixture(
                "call-older",
                "read",
                Some(json!({
                    "status": "completed",
                    "input": {},
                    "output": "first",
                    "title": "read",
                    "metadata": {},
                    "time": { "start": 10, "end": 11 }
                })),
            ),
            tool_part_fixture(
                "call-newer",
                "write",
                Some(json!({
                    "status": "completed",
                    "input": {},
                    "output": "second",
                    "title": "write",
                    "metadata": {},
                    "time": { "start": 20, "end": 21 }
                })),
            ),
        ],
    )]);

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(history))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert_eq!(result.recent_tool_calls.len(), 2);
    assert_eq!(result.recent_tool_calls[0].call_id, "call-newer");
    assert_eq!(result.recent_tool_calls[0].tool_name, "write");
    assert_eq!(result.recent_tool_calls[1].call_id, "call-older");
    assert_eq!(result.recent_tool_calls[1].tool_name, "read");
}

#[tokio::test]
async fn get_session_state_returns_error_when_status_lookup_fails() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(ResponseTemplate::new(200).set_body_json(session_fixture("ses-1")))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(ResponseTemplate::new(500).set_body_json(json!({
            "name": "InternalError",
            "message": "boom"
        })))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let err = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect_err("status lookup failure should error");

    assert!(matches!(err, ToolError::Internal(_)));
    assert!(err.to_string().contains("Failed to get session status"));
}

#[tokio::test]
async fn get_session_state_returns_error_when_message_lookup_fails() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(ResponseTemplate::new(200).set_body_json(session_fixture("ses-1")))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(session_status_fixture(&[("ses-1", busy_status_fixture())])),
        )
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(500).set_body_json(json!({
            "name": "InternalError",
            "message": "boom"
        })))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let err = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect_err("message lookup failure should error");

    assert!(matches!(err, ToolError::Internal(_)));
    assert!(err.to_string().contains("Failed to list messages"));
}

#[tokio::test]
async fn get_session_state_counts_each_trailing_user_message() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/ses-1"))
        .respond_with(ResponseTemplate::new(200).set_body_json(session_fixture("ses-1")))
        .mount(&mock)
        .await;

    Mock::given(method("GET"))
        .and(path("/session/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
        .mount(&mock)
        .await;

    let history = message_history_fixture(vec![
        message_fixture("ses-1", "m1", "assistant", 1, Some(2), vec![]),
        message_fixture("ses-1", "m2", "user", 3, None, vec![]),
        message_fixture("ses-1", "m3", "user", 4, None, vec![]),
    ]);

    Mock::given(method("GET"))
        .and(path("/session/ses-1/message"))
        .respond_with(ResponseTemplate::new(200).set_body_json(history))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let result = tool
        .call(
            GetSessionStateInput {
                session_id: "ses-1".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect("get_session_state should succeed");

    assert_eq!(result.pending_message_count, 2);
}

#[tokio::test]
async fn get_session_state_unknown_session_returns_error() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/session/missing"))
        .respond_with(ResponseTemplate::new(404).set_body_json(json!({
            "name": "NotFound",
            "message": "Session not found"
        })))
        .mount(&mock)
        .await;

    let tool = GetSessionStateTool::new(Arc::clone(&server));
    let err = tool
        .call(
            GetSessionStateInput {
                session_id: "missing".into(),
            },
            &ToolContext::default(),
        )
        .await
        .expect_err("missing session should fail");

    assert!(matches!(err, ToolError::InvalidInput(_)));
    assert!(err.to_string().contains("Use list_sessions"));
}

#[tokio::test]
async fn list_commands_returns_available_commands() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/command"))
        .respond_with(ResponseTemplate::new(200).set_body_json(commands_list_fixture()))
        .mount(&mock)
        .await;

    let tool = ListCommandsTool::new(Arc::clone(&server));

    let result = tool
        .call(ListCommandsInput {}, &ToolContext::default())
        .await
        .expect("list_commands should succeed");

    assert_eq!(result.commands.len(), 3);
    assert_eq!(result.commands[0].name, "test");
    assert_eq!(result.commands[1].name, "build");
    assert_eq!(result.commands[2].name, "lint");
}

#[tokio::test]
async fn list_commands_returns_empty_list() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/command"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([])))
        .mount(&mock)
        .await;

    let tool = ListCommandsTool::new(Arc::clone(&server));

    let result = tool
        .call(ListCommandsInput {}, &ToolContext::default())
        .await
        .expect("list_commands should succeed");

    assert!(result.commands.is_empty());
}

#[tokio::test]
async fn list_agents_returns_visible_agents() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/agent"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([
            {
                "name": "Plan",
                "description": "Planning agent",
                "hidden": false
            },
            {
                "name": "Research",
                "description": "Research agent"
            }
        ])))
        .mount(&mock)
        .await;

    let tool = ListAgentsTool::new(Arc::clone(&server));

    let result = tool
        .call(ListAgentsInput {}, &ToolContext::default())
        .await
        .expect("list_agents should succeed");

    assert_eq!(result.agents.len(), 2);
    assert_eq!(result.agents[0].name, "Plan");
    assert_eq!(
        result.agents[0].description.as_deref(),
        Some("Planning agent")
    );
    assert_eq!(result.agents[1].name, "Research");
}

#[tokio::test]
async fn list_agents_omits_hidden_agents() {
    let mock = MockServer::start().await;
    let server = test_orchestrator_server(&mock).await;

    Mock::given(method("GET"))
        .and(path("/agent"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([
            {
                "name": "Plan",
                "description": "Planning agent",
                "hidden": false
            },
            {
                "name": "HiddenAgent",
                "description": "Internal agent",
                "hidden": true
            }
        ])))
        .mount(&mock)
        .await;

    let tool = ListAgentsTool::new(Arc::clone(&server));

    let result = tool
        .call(ListAgentsInput {}, &ToolContext::default())
        .await
        .expect("list_agents should succeed");

    assert_eq!(result.agents.len(), 1);
    assert_eq!(result.agents[0].name, "Plan");
}
