//! Cursor IDE framework adapter.
//!
//! This adapter handles integration with Cursor IDE's agent mode hooks.
//! Cursor uses a hooks.json configuration file similar to other frameworks,
//! but with a different structure and camelCase event names.
//!
//! # Hook Configuration
//!
//! Cursor hooks are configured in `.cursor/hooks.json` (project) or
//! `~/.cursor/hooks.json` (user) with the following format:
//!
//! ```json
//! {
//!   "version": 1,
//!   "hooks": {
//!     "beforeShellExecution": [{ "command": "mi6 ingest event beforeShellExecution --framework cursor" }]
//!   }
//! }
//! ```
//!
//! # Blocking Hooks
//!
//! Some Cursor hooks expect a JSON response to allow/deny operations:
//! - `beforeShellExecution`: Expects `{"permission":"allow"}` or `{"permission":"deny"}`
//! - `beforeMCPExecution`: Same as above
//! - `beforeReadFile`: Same as above
//! - `beforeTabFileRead`: Same as above
//!
//! This adapter implements `hook_response()` to return the appropriate response.

use super::{
    FrameworkAdapter, ParsedHookInput, ParsedHookInputBuilder, common, get_first_array_element,
};
use crate::model::EventType;
use std::borrow::Cow;
use std::path::PathBuf;

/// OTel environment variable keys.
///
/// NOTE: Cursor doesn't currently support OTel natively, but these keys are included
/// for consistency with other adapters and for use in `remove_hooks()` to clean up
/// any manually-added OTel configuration. If Cursor adds OTel support in the future,
/// `generate_hooks_config()` can be updated to use these keys.
const CURSOR_OTEL_KEYS: &[&str] = &[
    "OTEL_LOGS_EXPORTER",
    "OTEL_EXPORTER_OTLP_PROTOCOL",
    "OTEL_EXPORTER_OTLP_ENDPOINT",
];

/// Cursor IDE framework adapter.
///
/// Handles hook generation and event parsing for Cursor IDE agent mode.
pub struct CursorAdapter;

impl FrameworkAdapter for CursorAdapter {
    fn name(&self) -> &'static str {
        "cursor"
    }

    fn display_name(&self) -> &'static str {
        "Cursor"
    }

    fn project_config_path(&self) -> PathBuf {
        PathBuf::from(".cursor/hooks.json")
    }

    fn user_config_path(&self) -> Option<PathBuf> {
        dirs::home_dir().map(|h| h.join(".cursor/hooks.json"))
    }

    fn generate_hooks_config(
        &self,
        enabled_events: &[EventType],
        mi6_bin: &str,
        _otel_enabled: bool,
        _otel_port: u16,
    ) -> serde_json::Value {
        let mut hooks = serde_json::Map::new();

        // Cursor-supported event names
        let cursor_events: std::collections::HashSet<&str> =
            self.supported_events().into_iter().collect();

        // Process canonical events that map to Cursor events
        for event in enabled_events {
            let cursor_event = canonical_to_cursor(event);

            // Skip events that Cursor doesn't support
            if !cursor_events.contains(cursor_event.as_ref()) {
                continue;
            }

            // Cursor hook format: array of hook objects with "command" field
            let command = format!(
                "{} ingest event {} --framework cursor",
                mi6_bin, cursor_event
            );

            let hook_entry = serde_json::json!([{
                "command": command
            }]);
            hooks.insert(cursor_event.into_owned(), hook_entry);
        }

        // Add framework-specific events that have no canonical equivalent
        for event_name in self.framework_specific_events() {
            if hooks.contains_key(event_name) {
                continue;
            }

            let command = format!("{} ingest event {} --framework cursor", mi6_bin, event_name);

            let hook_entry = serde_json::json!([{
                "command": command
            }]);
            hooks.insert(event_name.to_string(), hook_entry);
        }

        serde_json::json!({
            "version": 1,
            "hooks": hooks
        })
    }

    fn merge_config(
        &self,
        generated: serde_json::Value,
        existing: Option<serde_json::Value>,
    ) -> serde_json::Value {
        let mut settings = existing.unwrap_or_else(|| serde_json::json!({}));

        // Ensure version is set
        settings["version"] = serde_json::json!(1);

        let Some(new_hooks) = generated.get("hooks").and_then(|h| h.as_object()) else {
            return settings;
        };

        // Ensure hooks object exists
        if settings.get("hooks").is_none() {
            settings["hooks"] = serde_json::json!({});
        }

        let Some(existing_hooks) = settings.get_mut("hooks").and_then(|h| h.as_object_mut()) else {
            return settings;
        };

        // Merge hooks, preserving user hooks
        // Cursor uses flat command structure: { "command": "..." }
        for (event_type, new_hook_array) in new_hooks {
            let Some(new_hooks_arr) = new_hook_array.as_array() else {
                continue;
            };

            if let Some(existing_event_hooks) = existing_hooks.get_mut(event_type) {
                if let Some(existing_arr) = existing_event_hooks.as_array_mut() {
                    // Remove any existing mi6 hooks to avoid duplicates
                    existing_arr.retain(|entry| {
                        !entry
                            .get("command")
                            .and_then(|c| c.as_str())
                            .is_some_and(common::is_mi6_command)
                    });

                    // Append the new mi6 hooks
                    for hook in new_hooks_arr {
                        existing_arr.push(hook.clone());
                    }
                }
            } else {
                // Event type doesn't exist, add the entire array
                existing_hooks.insert(event_type.clone(), new_hook_array.clone());
            }
        }

        settings
    }

    fn parse_hook_input(
        &self,
        _event_type: &str,
        stdin_json: &serde_json::Value,
    ) -> ParsedHookInput {
        let mut parsed = ParsedHookInputBuilder::new(stdin_json)
            .session_id("conversation_id")
            .tool_use_id("generation_id")
            .tool_name("tool_name")
            .cwd("cwd")
            .model("model")
            .duration_ms_or(&["duration_ms", "duration"])
            .build();

        // Cursor provides workspace_roots as an array - use first one as cwd fallback
        if parsed.cwd.is_none() {
            parsed.cwd = get_first_array_element(stdin_json, "workspace_roots");
        }

        parsed
        // Note: Cursor doesn't provide subagent_type, spawned_agent_id, permission_mode,
        // transcript_path, session_source, agent_id, agent_transcript_path, compact_trigger,
        // tokens, or cost via hooks
    }

    fn map_event_type(&self, framework_event: &str) -> EventType {
        match framework_event {
            // Tool-related events
            "beforeShellExecution"
            | "beforeMCPExecution"
            | "beforeReadFile"
            | "beforeTabFileRead" => EventType::PreToolUse,
            "afterShellExecution" | "afterMCPExecution" | "afterFileEdit" | "afterTabFileEdit" => {
                EventType::PostToolUse
            }
            // User interaction
            "beforeSubmitPrompt" => EventType::UserPromptSubmit,
            // Session lifecycle
            "stop" => EventType::Stop,
            // Cursor-specific events become Custom
            "afterAgentResponse" | "afterAgentThought" => {
                EventType::Custom(framework_event.to_string())
            }
            // Try canonical parsing, fall back to custom
            other => other
                .parse()
                .unwrap_or_else(|_| EventType::Custom(other.to_string())),
        }
    }

    fn supported_events(&self) -> Vec<&'static str> {
        vec![
            "beforeShellExecution",
            "afterShellExecution",
            "beforeMCPExecution",
            "afterMCPExecution",
            "beforeReadFile",
            "afterFileEdit",
            "beforeSubmitPrompt",
            "afterAgentResponse",
            "afterAgentThought",
            "stop",
            "beforeTabFileRead",
            "afterTabFileEdit",
        ]
    }

    fn framework_specific_events(&self) -> Vec<&'static str> {
        // These Cursor events have no canonical equivalent
        vec!["afterAgentResponse", "afterAgentThought"]
    }

    fn detection_env_vars(&self) -> &[&'static str] {
        // Cursor may set these env vars when running hooks
        // Note: These need to be verified with actual Cursor installation
        &["CURSOR_SESSION_ID", "CURSOR_WORKSPACE_ROOT"]
    }

    fn is_installed(&self) -> bool {
        common::is_framework_installed(self.user_config_path(), "cursor")
    }

    fn otel_support(&self) -> super::OtelSupport {
        use super::OtelSupport;
        // Cursor supports OTEL via env vars in hooks.json
        let Some(config_path) = self.user_config_path() else {
            return OtelSupport::Disabled;
        };
        let Ok(contents) = std::fs::read_to_string(&config_path) else {
            return OtelSupport::Disabled;
        };
        let Ok(json) = serde_json::from_str::<serde_json::Value>(&contents) else {
            return OtelSupport::Disabled;
        };

        // Check for OTEL env vars in the "env" section
        if let Some(env) = json.get("env") {
            for key in CURSOR_OTEL_KEYS {
                if env.get(*key).is_some() {
                    return OtelSupport::Enabled;
                }
            }
        }
        OtelSupport::Disabled
    }

    fn remove_hooks(&self, existing: serde_json::Value) -> Option<serde_json::Value> {
        // Cursor uses a different hook structure than Claude/Gemini
        // Each event has an array of hook objects with "command" field
        let mut settings = existing;
        let mut modified = false;

        if let Some(hooks) = settings.get_mut("hooks")
            && let Some(hooks_obj) = hooks.as_object_mut()
        {
            let keys: Vec<String> = hooks_obj.keys().cloned().collect();
            for key in keys {
                if let Some(event_hooks) = hooks_obj.get_mut(&key)
                    && let Some(arr) = event_hooks.as_array_mut()
                {
                    let original_len = arr.len();
                    // Filter out mi6 hooks (direct command field, not nested)
                    arr.retain(|entry| {
                        !entry
                            .get("command")
                            .and_then(|c| c.as_str())
                            .is_some_and(common::is_mi6_command)
                    });

                    if arr.len() != original_len {
                        modified = true;
                    }

                    if arr.is_empty() {
                        hooks_obj.remove(&key);
                    }
                }
            }

            // If hooks object is now empty, remove it entirely
            if hooks_obj.is_empty()
                && let Some(obj) = settings.as_object_mut()
            {
                obj.remove("hooks");
            }
        }

        // Remove OTel env vars if present
        if let Some(env) = settings.get_mut("env")
            && let Some(env_obj) = env.as_object_mut()
        {
            for key in CURSOR_OTEL_KEYS {
                if env_obj.remove(*key).is_some() {
                    modified = true;
                }
            }

            if env_obj.is_empty()
                && let Some(obj) = settings.as_object_mut()
            {
                obj.remove("env");
            }
        }

        if modified { Some(settings) } else { None }
    }

    fn hook_response(&self, event_type: &str) -> Option<&'static str> {
        // Cursor's blocking hooks expect a JSON response to allow the operation.
        // Different hooks use different response formats:
        // - beforeShellExecution, beforeMCPExecution, beforeReadFile, beforeTabFileRead
        //   use: {"permission": "allow" | "deny" | "ask"}
        // - beforeSubmitPrompt uses: {"continue": true | false}
        match event_type {
            "beforeShellExecution"
            | "beforeMCPExecution"
            | "beforeReadFile"
            | "beforeTabFileRead" => Some(r#"{"permission":"allow"}"#),
            "beforeSubmitPrompt" => Some(r#"{"continue":true}"#),
            _ => None,
        }
    }
}

/// Map canonical event types to Cursor event names.
fn canonical_to_cursor(event: &EventType) -> Cow<'static, str> {
    match event {
        EventType::PreToolUse => Cow::Borrowed("beforeShellExecution"),
        EventType::PostToolUse => Cow::Borrowed("afterShellExecution"),
        EventType::UserPromptSubmit => Cow::Borrowed("beforeSubmitPrompt"),
        EventType::Stop => Cow::Borrowed("stop"),
        // Events that don't have a direct Cursor equivalent
        EventType::SessionStart => Cow::Borrowed("SessionStart"),
        EventType::SessionEnd => Cow::Borrowed("SessionEnd"),
        EventType::PermissionRequest => Cow::Borrowed("PermissionRequest"),
        EventType::PreCompact => Cow::Borrowed("PreCompact"),
        EventType::SubagentStart => Cow::Borrowed("SubagentStart"),
        EventType::SubagentStop => Cow::Borrowed("SubagentStop"),
        EventType::Notification => Cow::Borrowed("Notification"),
        EventType::ApiRequest => Cow::Borrowed("ApiRequest"),
        EventType::Custom(s) => Cow::Owned(s.clone()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_name() {
        let adapter = CursorAdapter;
        assert_eq!(adapter.name(), "cursor");
        assert_eq!(adapter.display_name(), "Cursor");
    }

    #[test]
    fn test_project_config_path() {
        let adapter = CursorAdapter;
        assert_eq!(
            adapter.project_config_path(),
            PathBuf::from(".cursor/hooks.json")
        );
    }

    #[test]
    fn test_map_event_type() {
        let adapter = CursorAdapter;

        // Tool events
        assert_eq!(
            adapter.map_event_type("beforeShellExecution"),
            EventType::PreToolUse
        );
        assert_eq!(
            adapter.map_event_type("afterShellExecution"),
            EventType::PostToolUse
        );
        assert_eq!(
            adapter.map_event_type("beforeMCPExecution"),
            EventType::PreToolUse
        );
        assert_eq!(
            adapter.map_event_type("afterMCPExecution"),
            EventType::PostToolUse
        );
        assert_eq!(
            adapter.map_event_type("beforeReadFile"),
            EventType::PreToolUse
        );
        assert_eq!(
            adapter.map_event_type("afterFileEdit"),
            EventType::PostToolUse
        );

        // User interaction
        assert_eq!(
            adapter.map_event_type("beforeSubmitPrompt"),
            EventType::UserPromptSubmit
        );

        // Session lifecycle
        assert_eq!(adapter.map_event_type("stop"), EventType::Stop);

        // Cursor-specific events become Custom
        assert_eq!(
            adapter.map_event_type("afterAgentResponse"),
            EventType::Custom("afterAgentResponse".to_string())
        );
        assert_eq!(
            adapter.map_event_type("afterAgentThought"),
            EventType::Custom("afterAgentThought".to_string())
        );
    }

    #[test]
    fn test_parse_hook_input() {
        let adapter = CursorAdapter;
        let input = serde_json::json!({
            "conversation_id": "cursor-conv-123",
            "generation_id": "gen-456",
            "tool_name": "shell",
            "cwd": "/projects/test",
            "model": "claude-3-opus",
            "cursor_version": "1.7.0",
            "workspace_roots": ["/projects/test", "/projects/other"]
        });

        let parsed = adapter.parse_hook_input("beforeShellExecution", &input);

        assert_eq!(parsed.session_id, Some("cursor-conv-123".to_string()));
        assert_eq!(parsed.tool_use_id, Some("gen-456".to_string()));
        assert_eq!(parsed.tool_name, Some("shell".to_string()));
        assert_eq!(parsed.cwd, Some("/projects/test".to_string()));
        assert_eq!(parsed.model, Some("claude-3-opus".to_string()));

        // Claude-specific fields should be None
        assert_eq!(parsed.subagent_type, None);
        assert_eq!(parsed.spawned_agent_id, None);
        assert_eq!(parsed.permission_mode, None);
    }

    #[test]
    fn test_parse_hook_input_with_duration() {
        let adapter = CursorAdapter;
        // afterShellExecution includes duration
        let input = serde_json::json!({
            "conversation_id": "cursor-123",
            "generation_id": "gen-456",
            "command": "ls -la",
            "output": "file1.txt\nfile2.txt",
            "duration": 1234,
            "model": "gpt-4"
        });

        let parsed = adapter.parse_hook_input("afterShellExecution", &input);

        assert_eq!(parsed.duration_ms, Some(1234));
        assert_eq!(parsed.model, Some("gpt-4".to_string()));
    }

    #[test]
    fn test_parse_hook_input_with_duration_ms() {
        let adapter = CursorAdapter;
        // afterAgentThought uses duration_ms
        let input = serde_json::json!({
            "conversation_id": "cursor-123",
            "text": "Thinking about the problem...",
            "duration_ms": 5000,
            "model": "claude-3-sonnet"
        });

        let parsed = adapter.parse_hook_input("afterAgentThought", &input);

        assert_eq!(parsed.duration_ms, Some(5000));
        assert_eq!(parsed.model, Some("claude-3-sonnet".to_string()));
    }

    #[test]
    fn test_parse_hook_input_workspace_roots_fallback() {
        let adapter = CursorAdapter;
        // When cwd is not present, use first workspace_root
        let input = serde_json::json!({
            "conversation_id": "cursor-123",
            "workspace_roots": ["/projects/main", "/projects/other"]
        });

        let parsed = adapter.parse_hook_input("beforeShellExecution", &input);

        assert_eq!(parsed.cwd, Some("/projects/main".to_string()));
    }

    #[test]
    fn test_generate_hooks_config() -> Result<(), String> {
        let adapter = CursorAdapter;
        let events = vec![EventType::PreToolUse, EventType::UserPromptSubmit];

        let config = adapter.generate_hooks_config(&events, "mi6", false, 4318);

        // Should have version field
        assert_eq!(config["version"], 1);

        let hooks = config
            .get("hooks")
            .ok_or("missing hooks")?
            .as_object()
            .ok_or("hooks not an object")?;

        // PreToolUse maps to beforeShellExecution
        assert!(hooks.contains_key("beforeShellExecution"));
        // UserPromptSubmit maps to beforeSubmitPrompt
        assert!(hooks.contains_key("beforeSubmitPrompt"));

        let shell_hook = &hooks["beforeShellExecution"][0];
        let command = shell_hook["command"].as_str().ok_or("missing command")?;
        assert!(command.contains("mi6 ingest event beforeShellExecution"));
        assert!(command.contains("--framework cursor"));

        // Should also include framework-specific events
        assert!(
            hooks.contains_key("afterAgentResponse"),
            "Framework-specific afterAgentResponse should be included"
        );
        assert!(
            hooks.contains_key("afterAgentThought"),
            "Framework-specific afterAgentThought should be included"
        );

        Ok(())
    }

    #[test]
    fn test_framework_specific_events() {
        let adapter = CursorAdapter;
        let events = adapter.framework_specific_events();

        assert!(events.contains(&"afterAgentResponse"));
        assert!(events.contains(&"afterAgentThought"));
        assert_eq!(events.len(), 2);
    }

    #[test]
    fn test_merge_config_new() {
        let adapter = CursorAdapter;
        let generated = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [{"command": "mi6 ingest event beforeShellExecution"}]
            }
        });

        let merged = adapter.merge_config(generated, None);

        assert_eq!(merged["version"], 1);
        assert!(merged.get("hooks").is_some());
        assert!(merged["hooks"].get("beforeShellExecution").is_some());
    }

    #[test]
    fn test_merge_config_existing() {
        let adapter = CursorAdapter;
        let generated = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [{"command": "mi6 ingest event beforeShellExecution --framework cursor"}]
            }
        });
        let existing = serde_json::json!({
            "version": 1,
            "hooks": {
                "stop": [{"command": "other-tool log"}]
            }
        });

        let merged = adapter.merge_config(generated, Some(existing));

        assert_eq!(merged["version"], 1);
        // Should merge hooks - both should exist
        assert!(merged["hooks"].get("stop").is_some());
        assert!(merged["hooks"].get("beforeShellExecution").is_some());
    }

    #[test]
    fn test_merge_config_preserves_user_hooks_for_same_event() {
        // Test for issue #440: User hooks should be preserved when mi6 enables
        let adapter = CursorAdapter;
        let generated = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [{"command": "mi6 ingest event beforeShellExecution --framework cursor"}]
            }
        });
        let existing = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [{"command": "my-custom-logger --event shell"}]
            }
        });

        let merged = adapter.merge_config(generated, Some(existing));

        let shell_hooks = merged["hooks"]["beforeShellExecution"].as_array().unwrap();
        assert_eq!(shell_hooks.len(), 2, "Should have both user and mi6 hooks");

        // First hook should be the user's custom hook (preserved)
        assert!(
            shell_hooks[0]["command"]
                .as_str()
                .unwrap()
                .contains("my-custom-logger"),
            "User's custom hook should be preserved"
        );

        // Second hook should be mi6's hook (appended)
        assert!(
            shell_hooks[1]["command"]
                .as_str()
                .unwrap()
                .contains("mi6 ingest"),
            "mi6 hook should be appended"
        );
    }

    #[test]
    fn test_merge_config_updates_existing_mi6_hook() {
        // When mi6 is re-enabled, it should replace the old mi6 hook
        let adapter = CursorAdapter;
        let generated = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [{"command": "mi6 ingest event beforeShellExecution --new-flag"}]
            }
        });
        let existing = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [
                    {"command": "my-custom-logger"},
                    {"command": "mi6 ingest event beforeShellExecution --old-flag"}
                ]
            }
        });

        let merged = adapter.merge_config(generated, Some(existing));

        let shell_hooks = merged["hooks"]["beforeShellExecution"].as_array().unwrap();
        assert_eq!(shell_hooks.len(), 2, "Should still have 2 hooks");

        // First hook should be the user's custom hook
        assert!(
            shell_hooks[0]["command"]
                .as_str()
                .unwrap()
                .contains("my-custom-logger"),
            "User's custom hook should be preserved"
        );

        // Second hook should be the NEW mi6 hook
        let mi6_command = shell_hooks[1]["command"].as_str().unwrap();
        assert!(
            mi6_command.contains("--new-flag"),
            "New mi6 hook should be present"
        );
        assert!(
            !mi6_command.contains("--old-flag"),
            "Old mi6 hook should be replaced"
        );
    }

    #[test]
    fn test_supported_events() {
        let adapter = CursorAdapter;
        let events = adapter.supported_events();

        assert!(events.contains(&"beforeShellExecution"));
        assert!(events.contains(&"afterShellExecution"));
        assert!(events.contains(&"beforeMCPExecution"));
        assert!(events.contains(&"afterMCPExecution"));
        assert!(events.contains(&"beforeReadFile"));
        assert!(events.contains(&"afterFileEdit"));
        assert!(events.contains(&"beforeSubmitPrompt"));
        assert!(events.contains(&"afterAgentResponse"));
        assert!(events.contains(&"afterAgentThought"));
        assert!(events.contains(&"stop"));
    }

    #[test]
    fn test_detection_env_vars() {
        let adapter = CursorAdapter;
        let vars = adapter.detection_env_vars();

        assert!(vars.contains(&"CURSOR_SESSION_ID"));
        assert!(vars.contains(&"CURSOR_WORKSPACE_ROOT"));
    }

    #[test]
    fn test_hook_response() {
        let adapter = CursorAdapter;

        // Permission-based blocking hooks
        assert_eq!(
            adapter.hook_response("beforeShellExecution"),
            Some(r#"{"permission":"allow"}"#)
        );
        assert_eq!(
            adapter.hook_response("beforeMCPExecution"),
            Some(r#"{"permission":"allow"}"#)
        );
        assert_eq!(
            adapter.hook_response("beforeReadFile"),
            Some(r#"{"permission":"allow"}"#)
        );
        assert_eq!(
            adapter.hook_response("beforeTabFileRead"),
            Some(r#"{"permission":"allow"}"#)
        );

        // beforeSubmitPrompt uses "continue" format instead of "permission"
        assert_eq!(
            adapter.hook_response("beforeSubmitPrompt"),
            Some(r#"{"continue":true}"#)
        );

        // Non-blocking hooks should return None
        assert_eq!(adapter.hook_response("afterShellExecution"), None);
        assert_eq!(adapter.hook_response("afterAgentResponse"), None);
        assert_eq!(adapter.hook_response("stop"), None);
    }

    #[test]
    fn test_remove_hooks_with_mi6() {
        let adapter = CursorAdapter;
        let existing = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [
                    {"command": "mi6 ingest event beforeShellExecution --framework cursor"}
                ]
            }
        });

        let result = adapter.remove_hooks(existing);

        assert!(result.is_some());
        let settings = result.unwrap();
        // Version should be preserved
        assert_eq!(settings["version"], 1);
        // Hooks should be removed (was only mi6 hooks)
        assert!(settings.get("hooks").is_none());
    }

    #[test]
    fn test_remove_hooks_preserves_non_mi6() {
        let adapter = CursorAdapter;
        let existing = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [
                    {"command": "mi6 ingest event beforeShellExecution --framework cursor"},
                    {"command": "other-tool log"}
                ]
            }
        });

        let result = adapter.remove_hooks(existing);

        assert!(result.is_some());
        let settings = result.unwrap();
        // Hooks object should still exist with the non-mi6 hook
        assert!(settings.get("hooks").is_some());
        let shell_hooks = settings["hooks"]["beforeShellExecution"]
            .as_array()
            .unwrap();
        assert_eq!(shell_hooks.len(), 1);
        assert!(
            shell_hooks[0]["command"]
                .as_str()
                .unwrap()
                .contains("other-tool")
        );
    }

    #[test]
    fn test_remove_hooks_no_mi6() {
        let adapter = CursorAdapter;
        let existing = serde_json::json!({
            "version": 1,
            "hooks": {
                "beforeShellExecution": [{"command": "other-tool log"}]
            }
        });

        let result = adapter.remove_hooks(existing);

        // Should return None since no mi6 hooks were found
        assert!(result.is_none());
    }
}
