use g3_core::ui_writer::NullUiWriter;
use g3_core::Agent;
use serial_test::serial;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;

/// Helper to create a test agent in a temporary directory
async fn create_test_agent_in_dir(temp_dir: &TempDir) -> Agent<NullUiWriter> {
    // Change to temp directory
    std::env::set_current_dir(temp_dir.path()).unwrap();

    // Create a minimal config
    let config = g3_config::Config::default();
    let ui_writer = NullUiWriter;

    Agent::new(config, ui_writer).await.unwrap()
}

/// Helper to get todo.g3.md path in temp directory
fn get_todo_path(temp_dir: &TempDir) -> PathBuf {
    temp_dir.path().join("todo.g3.md")
}

#[tokio::test]
#[serial]
async fn test_todo_write_creates_file() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;
    let todo_path = get_todo_path(&temp_dir);

    // Initially, todo.g3.md should not exist
    assert!(!todo_path.exists(), "todo.g3.md should not exist initially");

    // Create a tool call to write TODO
    let tool_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": "- [ ] Task 1\n- [ ] Task 2\n- [x] Task 3"
        }),
    };

    // Execute the tool
    let result = agent.execute_tool(&tool_call).await.unwrap();

    // Should report success
    assert!(result.contains("✅"), "Should report success: {}", result);
    assert!(
        result.contains("todo.g3.md"),
        "Should mention todo.g3.md: {}",
        result
    );

    // File should now exist
    assert!(todo_path.exists(), "todo.g3.md should exist after write");

    // File should contain the correct content
    let content = fs::read_to_string(&todo_path).unwrap();
    assert_eq!(content, "- [ ] Task 1\n- [ ] Task 2\n- [x] Task 3");
}

#[tokio::test]
#[serial]
async fn test_todo_read_from_file() {
    let temp_dir = TempDir::new().unwrap();
    let todo_path = get_todo_path(&temp_dir);

    // Pre-create a todo.g3.md file
    let test_content = "# My TODO\n\n- [ ] First task\n- [x] Completed task";
    fs::write(&todo_path, test_content).unwrap();

    // Create agent (should load from file)
    let mut agent = create_test_agent_in_dir(&temp_dir).await;

    // Create a tool call to read TODO
    let tool_call = g3_core::ToolCall {
        tool: "todo_read".to_string(),
        args: serde_json::json!({}),
    };

    // Execute the tool
    let result = agent.execute_tool(&tool_call).await.unwrap();

    // Should contain the TODO content
    assert!(
        result.contains("📝 TODO list:"),
        "Should have TODO list header: {}",
        result
    );
    assert!(
        result.contains("First task"),
        "Should contain first task: {}",
        result
    );
    assert!(
        result.contains("Completed task"),
        "Should contain completed task: {}",
        result
    );
}

#[tokio::test]
#[serial]
async fn test_todo_read_empty_file() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;

    // Create a tool call to read TODO (file doesn't exist)
    let tool_call = g3_core::ToolCall {
        tool: "todo_read".to_string(),
        args: serde_json::json!({}),
    };

    // Execute the tool
    let result = agent.execute_tool(&tool_call).await.unwrap();

    // Should report empty
    assert!(result.contains("empty"), "Should report empty: {}", result);
}

#[tokio::test]
#[serial]
async fn test_todo_persistence_across_agents() {
    let temp_dir = TempDir::new().unwrap();
    let todo_path = get_todo_path(&temp_dir);

    // Agent 1: Write TODO
    {
        let mut agent = create_test_agent_in_dir(&temp_dir).await;
        let tool_call = g3_core::ToolCall {
            tool: "todo_write".to_string(),
            args: serde_json::json!({
                "content": "- [ ] Persistent task\n- [x] Done task"
            }),
        };
        agent.execute_tool(&tool_call).await.unwrap();
    }

    // Verify file exists
    assert!(
        todo_path.exists(),
        "todo.g3.md should persist after agent drops"
    );

    // Agent 2: Read TODO (new agent instance)
    {
        let mut agent = create_test_agent_in_dir(&temp_dir).await;
        let tool_call = g3_core::ToolCall {
            tool: "todo_read".to_string(),
            args: serde_json::json!({}),
        };
        let result = agent.execute_tool(&tool_call).await.unwrap();

        // Should read the persisted content
        assert!(
            result.contains("Persistent task"),
            "Should read persisted task: {}",
            result
        );
        assert!(
            result.contains("Done task"),
            "Should read done task: {}",
            result
        );
    }
}

#[tokio::test]
#[serial]
async fn test_todo_update_preserves_file() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;
    let todo_path = get_todo_path(&temp_dir);

    // Write initial TODO
    let write_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": "- [ ] Task 1\n- [ ] Task 2"
        }),
    };
    agent.execute_tool(&write_call).await.unwrap();

    // Update TODO
    let update_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": "- [x] Task 1\n- [ ] Task 2\n- [ ] Task 3"
        }),
    };
    agent.execute_tool(&update_call).await.unwrap();

    // Verify file has updated content
    let content = fs::read_to_string(&todo_path).unwrap();
    assert_eq!(content, "- [x] Task 1\n- [ ] Task 2\n- [ ] Task 3");
}

#[tokio::test]
#[serial]
async fn test_todo_handles_large_content() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;
    let todo_path = get_todo_path(&temp_dir);

    // Create a large TODO (but under the 50k limit)
    let mut large_content = String::from("# Large TODO\n\n");
    for i in 0..100 {
        large_content.push_str(&format!(
            "- [ ] Task {} with a long description that exceeds normal line lengths\n",
            i
        ));
    }

    let tool_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": large_content
        }),
    };

    let result = agent.execute_tool(&tool_call).await.unwrap();
    assert!(
        result.contains("✅"),
        "Should handle large content: {}",
        result
    );

    // Verify file contains all content
    let file_content = fs::read_to_string(&todo_path).unwrap();
    assert_eq!(file_content, large_content);
    assert!(file_content.contains("Task 99"), "Should contain all tasks");
}

#[tokio::test]
#[serial]
async fn test_todo_respects_size_limit() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;

    // Create content that exceeds the default 50k limit
    let huge_content = "x".repeat(60_000);

    let tool_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": huge_content
        }),
    };

    let result = agent.execute_tool(&tool_call).await.unwrap();

    // Should reject content that's too large
    assert!(
        result.contains("❌"),
        "Should reject oversized content: {}",
        result
    );
    assert!(
        result.contains("too large"),
        "Should mention size limit: {}",
        result
    );
}

#[tokio::test]
#[serial]
async fn test_todo_agent_initialization_loads_file() {
    let temp_dir = TempDir::new().unwrap();
    let todo_path = get_todo_path(&temp_dir);

    // Pre-create todo.g3.md before agent initialization
    let initial_content = "- [ ] Pre-existing task";
    fs::write(&todo_path, initial_content).unwrap();

    // Create agent - should load the file during initialization
    let mut agent = create_test_agent_in_dir(&temp_dir).await;

    // Read TODO - should return the pre-existing content
    let tool_call = g3_core::ToolCall {
        tool: "todo_read".to_string(),
        args: serde_json::json!({}),
    };

    let result = agent.execute_tool(&tool_call).await.unwrap();
    assert!(
        result.contains("Pre-existing task"),
        "Should load file on init: {}",
        result
    );
}

#[tokio::test]
#[serial]
async fn test_todo_handles_unicode_content() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;
    let todo_path = get_todo_path(&temp_dir);

    // Create TODO with unicode characters
    let unicode_content = "- [ ] 日本語タスク\n- [ ] Émoji task 🚀\n- [x] Ελληνικά task";

    let tool_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": unicode_content
        }),
    };

    agent.execute_tool(&tool_call).await.unwrap();

    // Verify file preserves unicode
    let file_content = fs::read_to_string(&todo_path).unwrap();
    assert_eq!(file_content, unicode_content);

    // Verify reading back works
    let read_call = g3_core::ToolCall {
        tool: "todo_read".to_string(),
        args: serde_json::json!({}),
    };

    let result = agent.execute_tool(&read_call).await.unwrap();
    assert!(
        result.contains("日本語"),
        "Should preserve Japanese: {}",
        result
    );
    assert!(result.contains("🚀"), "Should preserve emoji: {}", result);
    assert!(
        result.contains("Ελληνικά"),
        "Should preserve Greek: {}",
        result
    );
}

#[tokio::test]
#[serial]
async fn test_todo_empty_content_creates_empty_file() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;
    let todo_path = get_todo_path(&temp_dir);

    // Write empty TODO
    let tool_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": ""
        }),
    };

    agent.execute_tool(&tool_call).await.unwrap();

    // File should exist but be empty
    assert!(todo_path.exists(), "Empty todo.g3.md should create file");
    let content = fs::read_to_string(&todo_path).unwrap();
    assert_eq!(content, "");
}

#[tokio::test]
#[serial]
async fn test_todo_whitespace_only_content() {
    let temp_dir = TempDir::new().unwrap();
    let mut agent = create_test_agent_in_dir(&temp_dir).await;

    // Write whitespace-only TODO
    let tool_call = g3_core::ToolCall {
        tool: "todo_write".to_string(),
        args: serde_json::json!({
            "content": "   \n\n  \t  \n"
        }),
    };

    agent.execute_tool(&tool_call).await.unwrap();

    // Read it back
    let read_call = g3_core::ToolCall {
        tool: "todo_read".to_string(),
        args: serde_json::json!({}),
    };

    let result = agent.execute_tool(&read_call).await.unwrap();

    // Should report as empty (whitespace is trimmed)
    assert!(
        result.contains("empty"),
        "Whitespace-only should be empty: {}",
        result
    );
}
