//! `tools/call` MCP method — dispatches a tool name to the appropriate `*_tools` module.

use serde_json::Value;

use super::{
    agent_tools, diagnostics, git_tools, merge_tools, qa_tools, shell_tools, status_tools,
    story_tools, timer_tools, trigger_tools, wizard_tools,
};
use crate::http::context::AppContext;

// ── Tool dispatch ─────────────────────────────────────────────────

/// Execute an MCP tool by name, returning the text result or an error string.
///
/// This is the shared dispatch entry point used by both the WebSocket
/// rendezvous channel and API-based agent runtimes (Gemini, OpenAI).
pub async fn dispatch_tool_call(
    tool_name: &str,
    args: Value,
    ctx: &AppContext,
) -> Result<String, String> {
    match tool_name {
        // Workflow tools
        "create_story" => story_tools::tool_create_story(&args, ctx),
        "validate_stories" => story_tools::tool_validate_stories(ctx),
        "list_upcoming" => story_tools::tool_list_upcoming(ctx),
        "get_story_todos" => story_tools::tool_get_story_todos(&args, ctx),
        "record_tests" => story_tools::tool_record_tests(&args, ctx),
        "ensure_acceptance" => story_tools::tool_ensure_acceptance(&args, ctx),
        // Agent tools (async)
        "start_agent" => agent_tools::tool_start_agent(&args, ctx).await,
        "stop_agent" => agent_tools::tool_stop_agent(&args, ctx).await,
        "list_agents" => agent_tools::tool_list_agents(ctx).await,
        "get_agent_config" => agent_tools::tool_get_agent_config(ctx).await,
        "reload_agent_config" => agent_tools::tool_get_agent_config(ctx).await,
        "get_agent_output" => agent_tools::tool_get_agent_output(&args, ctx).await,
        "wait_for_agent" => agent_tools::tool_wait_for_agent(&args, ctx).await,
        "get_agent_remaining_turns_and_budget" => {
            agent_tools::tool_get_agent_remaining_turns_and_budget(&args, ctx).await
        }
        // Worktree tools
        "create_worktree" => agent_tools::tool_create_worktree(&args, ctx).await,
        "list_worktrees" => agent_tools::tool_list_worktrees(ctx),
        "remove_worktree" => agent_tools::tool_remove_worktree(&args, ctx).await,
        "cleanup_worktrees" => agent_tools::tool_cleanup_worktrees(&args, ctx).await,
        "gc" => agent_tools::tool_gc(ctx).await,
        // Editor tools
        "get_editor_command" => agent_tools::tool_get_editor_command(&args, ctx),
        // Lifecycle tools
        "accept_story" => story_tools::tool_accept_story(&args, ctx).await,
        // Story mutation tools (auto-commit to master)
        "check_criterion" => story_tools::tool_check_criterion(&args, ctx),
        "edit_criterion" => story_tools::tool_edit_criterion(&args, ctx),
        "add_criterion" => story_tools::tool_add_criterion(&args, ctx),
        "remove_criterion" => story_tools::tool_remove_criterion(&args, ctx),
        "update_story" => story_tools::tool_update_story(&args, ctx),
        // Spike lifecycle tools
        "create_spike" => story_tools::tool_create_spike(&args, ctx),
        // Bug lifecycle tools
        "create_bug" => story_tools::tool_create_bug(&args, ctx),
        "list_bugs" => story_tools::tool_list_bugs(ctx),
        "close_bug" => story_tools::tool_close_bug(&args, ctx).await,
        // Refactor lifecycle tools
        "create_refactor" => story_tools::tool_create_refactor(&args, ctx),
        "list_refactors" => story_tools::tool_list_refactors(ctx),
        // Epic lifecycle tools
        "create_epic" => story_tools::tool_create_epic(&args, ctx),
        "list_epics" => story_tools::tool_list_epics(ctx),
        "show_epic" => story_tools::tool_show_epic(&args, ctx),
        // Mergemaster tools
        "merge_agent_work" => merge_tools::tool_merge_agent_work(&args, ctx).await,
        "get_merge_status" => merge_tools::tool_get_merge_status(&args, ctx),
        "move_story_to_merge" => merge_tools::tool_move_story_to_merge(&args, ctx).await,
        "report_merge_failure" => merge_tools::tool_report_merge_failure(&args, ctx).await,
        // QA tools
        "request_qa" => qa_tools::tool_request_qa(&args, ctx).await,
        "approve_qa" => qa_tools::tool_approve_qa(&args, ctx).await,
        "reject_qa" => qa_tools::tool_reject_qa(&args, ctx).await,
        "launch_qa_app" => qa_tools::tool_launch_qa_app(&args, ctx).await,
        // Pipeline status
        "get_pipeline_status" => story_tools::tool_get_pipeline_status(ctx),
        "pipeline_query" => story_tools::tool_pipeline_query(&args),
        // Diagnostics
        "get_server_logs" => diagnostics::tool_get_server_logs(&args),
        "get_version" => diagnostics::tool_get_version(ctx),
        // Permission bridge (Claude Code → frontend dialog)
        "prompt_permission" => diagnostics::tool_prompt_permission(&args, ctx).await,
        // Token usage
        "get_token_usage" => diagnostics::tool_get_token_usage(&args, ctx),
        // Delete story
        "delete_story" => story_tools::tool_delete_story(&args, ctx).await,
        // Purge story (CRDT tombstone — story 521)
        "purge_story" => story_tools::tool_purge_story(&args, ctx),
        // Debug CRDT dump (story 515)
        "dump_crdt" => diagnostics::tool_dump_crdt(&args),
        // Half-written item recovery (bug 1001)
        "find_orphaned_items" => diagnostics::tool_find_orphaned_items(&args),
        "recover_half_written_items" => diagnostics::tool_recover_half_written_items(&args),
        // Read-only peer mesh diagnostics (story 720)
        "mesh_status" => diagnostics::tool_mesh_status(&args),
        // Arbitrary pipeline movement
        "move_story" => diagnostics::tool_move_story(&args, ctx),
        // Unblock story
        "unblock_story" => story_tools::tool_unblock_story(&args, ctx),
        // Convert work-item type in place (story 1141)
        "convert_item_type" => story_tools::tool_convert_item_type(&args, ctx),
        // Freeze / unfreeze story
        "freeze_story" => story_tools::tool_freeze_story(&args, ctx),
        "unfreeze_story" => story_tools::tool_unfreeze_story(&args, ctx),
        // Worktree-sandboxed file editing (replaces Claude's built-in Edit/Write for coder agents)
        "edit" => shell_tools::tool_edit(&args, ctx),
        "write" => shell_tools::tool_write(&args, ctx),
        // Shell command execution
        "run_command" => shell_tools::tool_run_command(&args, ctx).await,
        "run_tests" => shell_tools::tool_run_tests(&args, ctx).await,
        "get_test_result" => shell_tools::tool_get_test_result(&args, ctx).await,
        "run_build" => shell_tools::tool_run_build(&args, ctx).await,
        "run_lint" => shell_tools::tool_run_lint(&args, ctx).await,
        "run_check" => shell_tools::tool_run_check(&args, ctx).await,
        // Git operations
        "git_status" => git_tools::tool_git_status(&args, ctx).await,
        "git_diff" => git_tools::tool_git_diff(&args, ctx).await,
        "git_add" => git_tools::tool_git_add(&args, ctx).await,
        "git_commit" => git_tools::tool_git_commit(&args, ctx).await,
        "git_log" => git_tools::tool_git_log(&args, ctx).await,
        // Story detail (any stage)
        "show" => status_tools::tool_show(&args, ctx).await,
        // File line count
        "loc_file" => diagnostics::tool_loc_file(&args, ctx),
        // Setup wizard tools
        "wizard_status" => wizard_tools::tool_wizard_status(ctx),
        "wizard_generate" => wizard_tools::tool_wizard_generate(&args, ctx),
        "wizard_confirm" => wizard_tools::tool_wizard_confirm(ctx),
        "wizard_skip" => wizard_tools::tool_wizard_skip(ctx),
        "wizard_retry" => wizard_tools::tool_wizard_retry(ctx),
        // Event trigger tools
        "schedule_event_trigger" => trigger_tools::tool_schedule_event_trigger(&args, ctx),
        "list_event_triggers" => trigger_tools::tool_list_event_triggers(ctx),
        "cancel_event_trigger" => trigger_tools::tool_cancel_event_trigger(&args, ctx),
        // Scheduled timer tools
        "schedule_timer" => timer_tools::tool_schedule_timer(&args, ctx),
        "list_timers" => timer_tools::tool_list_timers(ctx),
        "cancel_timer" => timer_tools::tool_cancel_timer(&args, ctx),
        _ => Err(format!("Unknown tool: {tool_name}")),
    }
}

#[cfg(test)]
mod tests {
    use crate::http::gateway::jsonrpc::JsonRpcResponse;
    use crate::slog_warn;
    use serde_json::json;

    /// Test helper: invoke a `tools/call` JSON-RPC request and return the response.
    pub(in crate::http::mcp) async fn handle_tools_call(
        id: Option<serde_json::Value>,
        params: &serde_json::Value,
        ctx: &crate::http::context::AppContext,
    ) -> JsonRpcResponse {
        let tool_name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
        let args = params.get("arguments").cloned().unwrap_or(json!({}));

        match super::dispatch_tool_call(tool_name, args, ctx).await {
            Ok(content) => JsonRpcResponse::success(
                id,
                json!({
                    "content": [{ "type": "text", "text": content }]
                }),
            ),
            Err(msg) => {
                slog_warn!("[mcp] Tool call failed: tool={tool_name} error={msg}");
                JsonRpcResponse::success(
                    id,
                    json!({
                        "content": [{ "type": "text", "text": msg }],
                        "isError": true
                    }),
                )
            }
        }
    }
    use crate::http::test_helpers::test_ctx;

    #[test]
    fn handle_tools_call_unknown_tool() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        let rt = tokio::runtime::Runtime::new().unwrap();
        let resp = rt.block_on(handle_tools_call(
            Some(json!(1)),
            &json!({"name": "bogus_tool", "arguments": {}}),
            &ctx,
        ));
        let result = resp.result.unwrap();
        assert_eq!(result["isError"], true);
        assert!(
            result["content"][0]["text"]
                .as_str()
                .unwrap()
                .contains("Unknown tool")
        );
    }
}
