//! Phase 4 Integration Tests: MCP Tool Integration with RAG
//!
//! Tests for tool registry, explicit tool calls, hybrid mode, and all tool
//! categories.

use std::net::SocketAddr;

use ai_service::mcp::ToolCategory;
use ai_service::service::{AiService, AskRequest, McpToolRequest};
use serde_json::json;

#[tokio::test]
async fn test_tool_registry_initialization() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Test that all tool categories are registered
    let all_tools = service.list_all_tools();
    assert!(!all_tools.is_empty(), "Tool registry should not be empty");

    // Verify we have tools from each category
    let categories: Vec<_> = all_tools.iter().map(|t| t.category).collect();
    assert!(
        categories.contains(&ToolCategory::Rag),
        "RAG tools should be registered"
    );
    assert!(
        categories.contains(&ToolCategory::Guidance),
        "Guidance tools should be registered"
    );
    assert!(
        categories.contains(&ToolCategory::Settings),
        "Settings tools should be registered"
    );
    assert!(
        categories.contains(&ToolCategory::Iac),
        "IaC tools should be registered"
    );
}

#[tokio::test]
async fn test_rag_tool_count() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let rag_tools = service.tools_by_category(ToolCategory::Rag);
    assert_eq!(
        rag_tools.len(),
        3,
        "Should have 3 RAG tools: ask, search, status"
    );

    let tool_names: Vec<_> = rag_tools.iter().map(|t| t.name.as_str()).collect();
    assert!(tool_names.contains(&"rag_ask_question"));
    assert!(tool_names.contains(&"rag_semantic_search"));
    assert!(tool_names.contains(&"rag_get_status"));
}

#[tokio::test]
async fn test_guidance_tool_count() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let guidance_tools = service.tools_by_category(ToolCategory::Guidance);
    assert_eq!(guidance_tools.len(), 5, "Should have 5 Guidance tools");

    let tool_names: Vec<_> = guidance_tools.iter().map(|t| t.name.as_str()).collect();
    assert!(tool_names.contains(&"guidance_check_system_status"));
    assert!(tool_names.contains(&"guidance_suggest_next_action"));
    assert!(tool_names.contains(&"guidance_find_docs"));
    assert!(tool_names.contains(&"guidance_troubleshoot"));
    assert!(tool_names.contains(&"guidance_validate_config"));
}

#[tokio::test]
async fn test_settings_tool_count() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let settings_tools = service.tools_by_category(ToolCategory::Settings);
    assert_eq!(settings_tools.len(), 7, "Should have 7 Settings tools");
}

#[tokio::test]
async fn test_iac_tool_count() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let iac_tools = service.tools_by_category(ToolCategory::Iac);
    assert_eq!(iac_tools.len(), 3, "Should have 3 IaC tools");
}

#[tokio::test]
async fn test_explicit_tool_call_rag_ask() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "rag_ask_question".to_string(),
        args: json!({"question": "What is Nushell?"}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
    assert_eq!(response.result["tool"], "rag_ask_question");
}

#[tokio::test]
#[ignore]
async fn test_explicit_tool_call_guidance_status() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "guidance_check_system_status".to_string(),
        args: json!({}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("guidance_check_system_status failed");
    assert_eq!(response.result["status"], "healthy");
    assert_eq!(response.result["tool"], "guidance_check_system_status");
}

#[tokio::test]
async fn test_explicit_tool_call_settings() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "installer_get_settings".to_string(),
        args: json!({}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
    // Verify real SettingsTools data is returned (not empty placeholder)
    assert!(
        response.result.get("platforms").is_some()
            || response.result.get("modes").is_some()
            || response.result.get("available_services").is_some(),
        "Should return real settings data from SettingsTools"
    );
}

#[tokio::test]
async fn test_settings_tools_platform_recommendations() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "installer_platform_recommendations".to_string(),
        args: json!({}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
    // Should have real recommendations array from SettingsTools platform detection
    assert!(response.result.get("recommendations").is_some());
}

#[tokio::test]
async fn test_settings_tools_mode_defaults() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "installer_get_defaults".to_string(),
        args: json!({"mode": "solo"}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
    // Verify real mode defaults (resource requirements)
    assert!(response.result.get("min_cpu_cores").is_some());
    assert!(response.result.get("min_memory_gb").is_some());
}

#[tokio::test]
async fn test_explicit_tool_call_iac() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "iac_detect_technologies".to_string(),
        args: json!({"path": "/tmp/infra"}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
    // Verify real technology detection (returns technologies array)
    assert!(response.result.get("technologies").is_some());
}

#[tokio::test]
async fn test_iac_detect_technologies_real() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Test with provisioning directory that has Nickel files
    let req = McpToolRequest {
        tool_name: "iac_detect_technologies".to_string(),
        args: json!({"path": "../../provisioning"}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");

    // Should detect technologies as an array
    let techs = response.result.get("technologies");
    assert!(techs.is_some(), "Should have technologies array");
    assert!(techs.unwrap().is_array());
}

#[tokio::test]
async fn test_iac_analyze_completeness() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "iac_analyze_completeness".to_string(),
        args: json!({"path": "/tmp/test-infra"}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
    // Verify real analysis data
    assert!(response.result.get("complete").is_some());
    assert!(response.result.get("completeness_score").is_some());
    assert!(response.result.get("missing_files").is_some());
}

#[tokio::test]
async fn test_unknown_tool_error() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let req = McpToolRequest {
        tool_name: "unknown_tool_xyz".to_string(),
        args: json!({}),
    };

    let result = service.call_mcp_tool(req).await;
    assert!(result.is_err(), "Should fail with unknown tool");
}

#[tokio::test]
async fn test_hybrid_mode_disabled() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Load knowledge base (required for ask)
    service
        .load_knowledge_base("../../config/knowledge-base")
        .await
        .ok();

    let req = AskRequest {
        question: "What are deployment best practices?".to_string(),
        context: None,
        enable_tool_execution: Some(false), // Explicitly disabled
        max_tool_calls: None,
    };

    let response = service.ask(req).await.unwrap();

    // Should not have tool executions when disabled
    assert!(
        response.tool_executions.is_none() || response.tool_executions.as_ref().unwrap().is_empty(),
        "Tool executions should be empty when disabled"
    );
}

#[tokio::test]
async fn test_hybrid_mode_enabled() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Load knowledge base
    service
        .load_knowledge_base("../../config/knowledge-base")
        .await
        .ok();

    let req = AskRequest {
        question: "What is the current system status and best practices?".to_string(),
        context: None,
        enable_tool_execution: Some(true), // Enable hybrid mode
        max_tool_calls: Some(3),
    };

    let response = service.ask(req).await.unwrap();

    // Should have RAG answer
    assert!(!response.answer.is_empty(), "Should have RAG answer");

    // Tool executions may or may not occur depending on tool suggestions
    // The important thing is that when enabled, the mechanism works
}

#[tokio::test]
async fn test_max_tool_calls_limit() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    service
        .load_knowledge_base("../../config/knowledge-base")
        .await
        .ok();

    let req = AskRequest {
        question: "What is system status and what should I do next and how do I find \
                   documentation?"
            .to_string(),
        context: None,
        enable_tool_execution: Some(true),
        max_tool_calls: Some(1), // Limit to 1 tool
    };

    let response = service.ask(req).await.unwrap();

    // Even if multiple tools are suggested, only max_tool_calls should execute
    if let Some(executions) = &response.tool_executions {
        assert!(
            executions.len() <= 1,
            "Should respect max_tool_calls limit of 1"
        );
    }
}

#[tokio::test]
async fn test_tool_definition_schemas() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    let all_tools = service.list_all_tools();

    // Verify all tools have proper definitions
    for tool in all_tools {
        assert!(!tool.name.is_empty(), "Tool name should not be empty");
        assert!(
            !tool.description.is_empty(),
            "Tool description should not be empty"
        );

        // Verify input schema is valid JSON
        assert!(
            tool.input_schema.is_object(),
            "Input schema should be an object"
        );
        assert!(
            tool.input_schema.get("type").is_some(),
            "Input schema should have 'type' field"
        );
    }
}

#[tokio::test]
async fn test_tool_execution_with_required_args() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Tool that requires arguments should work when provided
    let req = McpToolRequest {
        tool_name: "rag_semantic_search".to_string(),
        args: json!({"query": "kubernetes"}),
    };

    let response = service
        .call_mcp_tool(req)
        .await
        .expect("MCP tool call failed");
    assert_eq!(response.result["status"], "success");
}

#[tokio::test]
async fn test_tool_execution_error_handling() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Tool that requires arguments should fail when not provided
    let req = McpToolRequest {
        tool_name: "rag_semantic_search".to_string(),
        args: json!({}), // Missing required 'query'
    };

    let result = service.call_mcp_tool(req).await;

    // Should either fail or return an error in the result
    match result {
        Ok(response) => {
            // Even if it doesn't fail, it should indicate an error
            assert!(
                response.result.get("status").is_some() || response.result.get("error").is_some()
            );
        }
        Err(_) => {
            // Expected: missing required parameter
        }
    }
}

#[tokio::test]
#[ignore] // Requires Nushell to be available
async fn test_guidance_tools_nushell_execution() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Test system status tool (requires Nushell)
    let req = McpToolRequest {
        tool_name: "guidance_check_system_status".to_string(),
        args: json!({}),
    };

    let response = service.call_mcp_tool(req).await;

    // Should either succeed with Nushell data or fail with Nushell not found
    match response {
        Ok(result) => {
            // If Nushell is available, should have JSON data
            assert!(result.result.is_object());
        }
        Err(e) => {
            // Expected if Nushell not available
            let err_msg = e.to_string();
            assert!(err_msg.contains("Nushell") || err_msg.contains("command"));
        }
    }
}

#[tokio::test]
#[ignore] // Requires Nushell to be available
async fn test_guidance_find_docs() {
    let addr: SocketAddr = "127.0.0.1:8083".parse().unwrap();
    let service = AiService::new(addr);

    // Test documentation finding tool (requires Nushell)
    let req = McpToolRequest {
        tool_name: "guidance_find_docs".to_string(),
        args: json!({"query": "deployment"}),
    };

    let response = service.call_mcp_tool(req).await;

    match response {
        Ok(result) => {
            // If Nushell is available, should have JSON data
            assert!(result.result.is_object());
        }
        Err(e) => {
            // Expected if Nushell not available
            let err_msg = e.to_string();
            assert!(err_msg.contains("Nushell") || err_msg.contains("command"));
        }
    }
}
