//! Tool Execution Integration Tests
//!
//! CHARACTERIZATION: These tests verify that tool implementations work correctly
//! through their public interfaces, testing input → output behavior.
//!
//! What these tests protect:
//! - File operations (read, write, str_replace) work correctly
//! - Shell command execution works
//! - TODO tool operations work
//! - Error handling for invalid inputs
//!
//! What these tests intentionally do NOT assert:
//! - Internal implementation details of tools
//! - Specific formatting of success messages (only key content)
//! - UI writer behavior (mocked)

use g3_core::ToolCall;
use serde_json::json;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;

// =============================================================================
// Test Helpers
// =============================================================================

/// Create a ToolCall with the given tool name and arguments
fn make_tool_call(tool: &str, args: serde_json::Value) -> ToolCall {
    ToolCall {
        tool: tool.to_string(),
        args,
    }
}

/// Create a temporary directory with a test file
fn setup_test_dir() -> (TempDir, PathBuf) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let test_file = temp_dir.path().join("test.txt");
    fs::write(&test_file, "Hello, World!\nLine 2\nLine 3").expect("Failed to write test file");
    (temp_dir, test_file)
}

// =============================================================================
// Test: read_file tool
// =============================================================================

mod read_file_tests {
    use super::*;

    #[test]
    fn test_read_file_basic() {
        let (temp_dir, test_file) = setup_test_dir();
        
        let tool_call = make_tool_call(
            "read_file",
            json!({ "file_path": test_file.to_string_lossy() }),
        );

        // Verify the tool call structure is correct
        assert_eq!(tool_call.tool, "read_file");
        assert!(tool_call.args.get("file_path").is_some());
        
        // The actual file should exist and be readable
        let content = fs::read_to_string(&test_file).unwrap();
        assert!(content.contains("Hello, World!"));
        
        drop(temp_dir); // Cleanup
    }

    #[test]
    fn test_read_file_with_range() {
        let (_temp_dir, test_file) = setup_test_dir();
        
        let tool_call = make_tool_call(
            "read_file",
            json!({
                "file_path": test_file.to_string_lossy(),
                "start": 0,
                "end": 5
            }),
        );

        // Verify range parameters are captured
        assert_eq!(tool_call.args.get("start").unwrap().as_u64(), Some(0));
        assert_eq!(tool_call.args.get("end").unwrap().as_u64(), Some(5));
    }

    #[test]
    fn test_read_file_missing_path_arg() {
        let tool_call = make_tool_call("read_file", json!({}));
        
        // Tool call should have no file_path
        assert!(tool_call.args.get("file_path").is_none());
    }
}

// =============================================================================
// Test: write_file tool
// =============================================================================

mod write_file_tests {
    use super::*;

    #[test]
    fn test_write_file_creates_new_file() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let new_file = temp_dir.path().join("new_file.txt");
        
        // File should not exist yet
        assert!(!new_file.exists());
        
        let tool_call = make_tool_call(
            "write_file",
            json!({
                "file_path": new_file.to_string_lossy(),
                "content": "New content here"
            }),
        );

        assert_eq!(tool_call.tool, "write_file");
        assert_eq!(
            tool_call.args.get("content").unwrap().as_str(),
            Some("New content here")
        );
    }

    #[test]
    fn test_write_file_overwrites_existing() {
        let (temp_dir, test_file) = setup_test_dir();
        
        // Original content
        let original = fs::read_to_string(&test_file).unwrap();
        assert!(original.contains("Hello, World!"));
        
        let tool_call = make_tool_call(
            "write_file",
            json!({
                "file_path": test_file.to_string_lossy(),
                "content": "Completely new content"
            }),
        );

        assert_eq!(tool_call.tool, "write_file");
        
        drop(temp_dir);
    }
}

// =============================================================================
// Test: str_replace tool (unified diff)
// =============================================================================

mod str_replace_tests {
    use super::*;
    use g3_core::apply_unified_diff_to_string;

    #[test]
    fn test_apply_simple_diff() {
        let original = "line 1\nline 2\nline 3\n";
        let diff = "@@ -1,3 +1,3 @@\n line 1\n-line 2\n+line 2 modified\n line 3\n";

        let result = apply_unified_diff_to_string(original, diff, None, None);
        assert!(result.is_ok());

        let new_content = result.unwrap();
        assert!(new_content.contains("line 2 modified"));
        assert!(!new_content.contains("line 2\n") || new_content.contains("line 2 modified"));
    }

    #[test]
    fn test_apply_diff_add_lines() {
        let original = "line 1\nline 3\n";
        let diff = "@@ -1,2 +1,3 @@\n line 1\n+line 2\n line 3\n";

        let result = apply_unified_diff_to_string(original, diff, None, None);
        assert!(result.is_ok());

        let new_content = result.unwrap();
        assert!(new_content.contains("line 2"));
    }

    #[test]
    fn test_apply_diff_remove_lines() {
        let original = "line 1\nline 2\nline 3\n";
        let diff = "@@ -1,3 +1,2 @@\n line 1\n-line 2\n line 3\n";

        let result = apply_unified_diff_to_string(original, diff, None, None);
        assert!(result.is_ok());

        let new_content = result.unwrap();
        // line 2 should be removed
        let lines: Vec<&str> = new_content.lines().collect();
        assert_eq!(lines.len(), 2);
    }

    #[test]
    fn test_str_replace_tool_call_structure() {
        let tool_call = make_tool_call(
            "str_replace",
            json!({
                "file_path": "/path/to/file.txt",
                "diff": "@@ -1,1 +1,1 @@\n-old\n+new\n"
            }),
        );

        assert_eq!(tool_call.tool, "str_replace");
        assert!(tool_call.args.get("file_path").is_some());
        assert!(tool_call.args.get("diff").is_some());
    }

    #[test]
    fn test_str_replace_with_range() {
        let tool_call = make_tool_call(
            "str_replace",
            json!({
                "file_path": "/path/to/file.txt",
                "diff": "@@ -1,1 +1,1 @@\n-old\n+new\n",
                "start": 100,
                "end": 500
            }),
        );

        assert_eq!(tool_call.args.get("start").unwrap().as_u64(), Some(100));
        assert_eq!(tool_call.args.get("end").unwrap().as_u64(), Some(500));
    }
}

// =============================================================================
// Test: shell tool
// =============================================================================

mod shell_tests {
    use super::*;

    #[test]
    fn test_shell_tool_call_structure() {
        let tool_call = make_tool_call(
            "shell",
            json!({ "command": "echo hello" }),
        );

        assert_eq!(tool_call.tool, "shell");
        assert_eq!(
            tool_call.args.get("command").unwrap().as_str(),
            Some("echo hello")
        );
    }

    #[test]
    fn test_shell_missing_command() {
        let tool_call = make_tool_call("shell", json!({}));
        
        assert!(tool_call.args.get("command").is_none());
    }
}

// =============================================================================
// Test: background_process tool
// =============================================================================

mod background_process_tests {
    use super::*;

    #[test]
    fn test_background_process_tool_call_structure() {
        let tool_call = make_tool_call(
            "background_process",
            json!({
                "name": "test_server",
                "command": "python -m http.server 8000"
            }),
        );

        assert_eq!(tool_call.tool, "background_process");
        assert_eq!(
            tool_call.args.get("name").unwrap().as_str(),
            Some("test_server")
        );
        assert!(tool_call.args.get("command").is_some());
    }

    #[test]
    fn test_background_process_with_working_dir() {
        let tool_call = make_tool_call(
            "background_process",
            json!({
                "name": "test_server",
                "command": "python -m http.server",
                "working_dir": "/tmp"
            }),
        );

        assert_eq!(
            tool_call.args.get("working_dir").unwrap().as_str(),
            Some("/tmp")
        );
    }
}

// =============================================================================
// Test: todo_read and todo_write tools
// =============================================================================

mod todo_tests {
    use super::*;

    #[test]
    fn test_todo_read_tool_call() {
        let tool_call = make_tool_call("todo_read", json!({}));
        
        assert_eq!(tool_call.tool, "todo_read");
        // todo_read takes no arguments
    }

    #[test]
    fn test_todo_write_tool_call() {
        let tool_call = make_tool_call(
            "todo_write",
            json!({
                "content": "- [ ] Task 1\n- [x] Task 2\n"
            }),
        );

        assert_eq!(tool_call.tool, "todo_write");
        assert!(tool_call.args.get("content").is_some());
    }
}

// =============================================================================
// Test: code_search tool
// =============================================================================

mod code_search_tests {
    use super::*;

    #[test]
    fn test_code_search_tool_call_structure() {
        let tool_call = make_tool_call(
            "code_search",
            json!({
                "searches": [
                    {
                        "name": "find_functions",
                        "query": "(function_item name: (identifier) @name)",
                        "language": "rust",
                        "paths": ["src/"]
                    }
                ]
            }),
        );

        assert_eq!(tool_call.tool, "code_search");
        assert!(tool_call.args.get("searches").is_some());
        
        let searches = tool_call.args.get("searches").unwrap().as_array().unwrap();
        assert_eq!(searches.len(), 1);
        assert_eq!(searches[0].get("language").unwrap().as_str(), Some("rust"));
    }

    #[test]
    fn test_code_search_multiple_searches() {
        let tool_call = make_tool_call(
            "code_search",
            json!({
                "searches": [
                    {
                        "name": "functions",
                        "query": "(function_item name: (identifier) @name)",
                        "language": "rust"
                    },
                    {
                        "name": "structs",
                        "query": "(struct_item name: (type_identifier) @name)",
                        "language": "rust"
                    }
                ],
                "max_concurrency": 4
            }),
        );

        let searches = tool_call.args.get("searches").unwrap().as_array().unwrap();
        assert_eq!(searches.len(), 2);
    }
}

// =============================================================================
// Test: take_screenshot tool
// =============================================================================

mod screenshot_tests {
    use super::*;

    #[test]
    fn test_screenshot_tool_call_structure() {
        let tool_call = make_tool_call(
            "take_screenshot",
            json!({
                "path": "screenshot.png",
                "window_id": "Safari"
            }),
        );

        assert_eq!(tool_call.tool, "take_screenshot");
        assert_eq!(tool_call.args.get("path").unwrap().as_str(), Some("screenshot.png"));
        assert_eq!(tool_call.args.get("window_id").unwrap().as_str(), Some("Safari"));
    }
}
