//! Command parsing and execution for the Gateway connector
//!
//! This module handles the built-in commands that can be invoked in
//! Gateway connector sessions using the `/command` syntax.

use super::{ConnectorListCallback, ConnectorSummaryInfo, GatewaySession, SessionTransferCallback};
use serde::{Deserialize, Serialize};

/// Information about a connector for transfer display
#[derive(Clone, Debug)]
pub struct ConnectorTransferInfo {
    pub kind: String,      // "ACP", "OpenCode", "Gateway"
    pub title: String,
    pub model: Option<String>,
}

/// Commands that can be executed in a Gateway session
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Command {
    /// Enable or disable echo mode
    Echo(bool),

    /// List all available connectors
    ListConnectors,

    /// Select a specific connector to transfer the session to
    /// Tuple: (connector_id, optional_session_id)
    SelectConnector(String, Option<String>),

    /// Shortcut to transfer to a Claude connector
    Claude,

    /// Show help for available commands
    Help,
}

/// Result of executing a command
#[derive(Clone, Debug)]
pub enum CommandResult {
    /// A message to display to the user
    Message(String),

    /// An error message
    Error(String),

    /// Session was transferred to another connector
    SessionTransferred {
        to_connector: String,
        /// Optional confirmation message
        message: Option<String>,
        /// Information about the connector for rich display
        connector_info: Option<ConnectorTransferInfo>,
    },
}

/// Parse a command from message text
///
/// Commands must start with `/` and may have arguments.
/// Returns None if the message is not a command.
///
/// # Examples
///
/// ```
/// use dirigent_core::connectors::gateway::commands::parse_command;
///
/// assert!(parse_command("/help").is_some());
/// assert!(parse_command("/echo on").is_some());
/// assert!(parse_command("hello").is_none());
/// ```
pub fn parse_command(text: &str) -> Option<Command> {
    let trimmed = text.trim();

    // Commands must start with /
    if !trimmed.starts_with('/') {
        return None;
    }

    // Split into command and arguments
    let parts: Vec<&str> = trimmed[1..].splitn(2, char::is_whitespace).collect();
    let command_name = parts[0].to_lowercase();
    let args = parts.get(1).map(|s| s.trim()).unwrap_or("");

    match command_name.as_str() {
        "echo" => {
            let arg = parse_arguments(args).into_iter().next().unwrap_or_default().to_lowercase();
            match arg.as_str() {
                "on" | "true" | "1" | "enable" | "yes" => Some(Command::Echo(true)),
                "off" | "false" | "0" | "disable" | "no" => Some(Command::Echo(false)),
                "" => {
                    // No argument - invalid usage
                    None
                }
                _ => None,
            }
        }
        "list-connectors" | "listconnectors" | "connectors" | "list" => {
            Some(Command::ListConnectors)
        }
        "select-connector" | "selectconnector" | "select" | "use" => {
            let args = parse_arguments(args);
            let connector_id = args.first().map(|s| sanitize_connector_id(s));
            let session_id = args.get(1).map(|s| s.trim().to_string());
            connector_id.map(|id| Command::SelectConnector(id, session_id))
        }
        "claude" => Some(Command::Claude),
        "help" | "?" => Some(Command::Help),
        _ => None,
    }
}

/// Parse command arguments, handling quoted strings
///
/// # Examples
///
/// - `arg1 arg2` -> ["arg1", "arg2"]
/// - `"arg with spaces" arg2` -> ["arg with spaces", "arg2"]
fn parse_arguments(args: &str) -> Vec<&str> {
    let mut result = Vec::new();
    let mut chars = args.char_indices().peekable();
    let mut in_quotes = false;
    let mut start = 0;
    let mut current_quote = ' ';

    while let Some((i, c)) = chars.next() {
        if in_quotes {
            if c == current_quote {
                result.push(&args[start..i]);
                in_quotes = false;
                // Skip whitespace after closing quote
                while chars.peek().map(|(_, c)| c.is_whitespace()).unwrap_or(false) {
                    chars.next();
                }
                if let Some((next_i, _)) = chars.peek() {
                    start = *next_i;
                }
            }
        } else if c == '"' || c == '\'' {
            in_quotes = true;
            current_quote = c;
            start = i + 1;
        } else if c.is_whitespace() {
            if start < i {
                result.push(&args[start..i]);
            }
            // Skip additional whitespace
            while chars.peek().map(|(_, c)| c.is_whitespace()).unwrap_or(false) {
                chars.next();
            }
            if let Some((next_i, _)) = chars.peek() {
                start = *next_i;
            } else {
                start = args.len();
            }
        }
    }

    // Handle remaining text
    if start < args.len() && !in_quotes {
        let remaining = args[start..].trim();
        if !remaining.is_empty() {
            result.push(remaining);
        }
    }

    result
}

/// Sanitize a connector ID by keeping only alphanumeric, dash, underscore
/// Takes the first whitespace-delimited token and filters characters.
fn sanitize_connector_id(id: &str) -> String {
    id.split_whitespace()
        .next()
        .unwrap_or("")
        .chars()
        .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_')
        .collect()
}

/// Execute a command and return the result
pub async fn execute_command(
    command: Command,
    gateway_connector_id: &str,
    session_id: &str,
    session: &mut GatewaySession,
    connector_list_callback: Option<&ConnectorListCallback>,
    session_transfer_callback: Option<&SessionTransferCallback>,
) -> CommandResult {
    match command {
        Command::Echo(enabled) => {
            execute_echo(enabled, session)
        }
        Command::ListConnectors => {
            execute_list_connectors(connector_list_callback)
        }
        Command::SelectConnector(connector_id, target_session_id) => {
            execute_select_connector(
                gateway_connector_id,
                session_id,
                &connector_id,
                target_session_id.as_deref(),
                &session.current_mode_id,
                &session.current_model_id,
                connector_list_callback,
                session_transfer_callback,
            ).await
        }
        Command::Claude => {
            execute_claude(
                gateway_connector_id,
                session_id,
                &session.current_mode_id,
                &session.current_model_id,
                connector_list_callback,
                session_transfer_callback,
            ).await
        }
        Command::Help => {
            execute_help()
        }
    }
}

/// Execute the /echo command
fn execute_echo(enabled: bool, session: &mut GatewaySession) -> CommandResult {
    session.echo_enabled = enabled;
    if enabled {
        CommandResult::Message("Echo mode enabled. Your messages will be echoed back.".to_string())
    } else {
        CommandResult::Message("Echo mode disabled.".to_string())
    }
}

/// Execute the /list-connectors command
fn execute_list_connectors(callback: Option<&ConnectorListCallback>) -> CommandResult {
    match callback {
        Some(cb) => {
            let connectors: Vec<ConnectorSummaryInfo> = cb()
                .into_iter()
                .filter(|c| c.supports_session_transfer)
                .collect();
            if connectors.is_empty() {
                CommandResult::Message("No connectors available.".to_string())
            } else {
                let mut message = String::from("Available connectors:\n\n");
                for (i, conn) in connectors.iter().enumerate() {
                    message.push_str(&format!(
                        "{}. **{}** ({})\n   ID: `{}`\n   State: {}\n\n",
                        i + 1,
                        conn.title,
                        conn.kind,
                        conn.id,
                        conn.state
                    ));
                }
                message.push_str("Use `/select-connector <id>` to transfer this session to a connector.");
                CommandResult::Message(message)
            }
        }
        None => {
            CommandResult::Error("Connector listing is not available.".to_string())
        }
    }
}

/// Execute the /select-connector command
async fn execute_select_connector(
    gateway_connector_id: &str,
    session_id: &str,
    connector_id: &str,
    target_session_id: Option<&str>,
    current_mode_id: &str,
    current_model_id: &str,
    list_callback: Option<&ConnectorListCallback>,
    transfer_callback: Option<&SessionTransferCallback>,
) -> CommandResult {
    use super::SessionTransferRequest;
    use super::SessionTransferResult;
    use tokio::sync::oneshot;

    match transfer_callback {
        Some(cb) => {
            let (result_tx, result_rx) = oneshot::channel();

            let request = SessionTransferRequest {
                gateway_connector_id: gateway_connector_id.to_string(),
                gateway_session_id: session_id.to_string(),
                target_connector_id: connector_id.to_string(),
                target_session_id: target_session_id.map(String::from),
                current_mode_id: current_mode_id.to_string(),
                current_model_id: current_model_id.to_string(),
                result_tx,
            };

            // Fire the callback (non-blocking)
            cb(request);

            // Wait for result with timeout
            match tokio::time::timeout(
                std::time::Duration::from_secs(30),
                result_rx
            ).await {
                Ok(Ok(SessionTransferResult::Transferred { connector_id, session_id: _, is_new, .. })) => {
                    // Look up connector info if list callback is available
                    let connector_info = list_callback.and_then(|list_cb| {
                        let connectors = list_cb();
                        connectors.into_iter()
                            .find(|c| c.id == connector_id)
                            .map(|summary| ConnectorTransferInfo {
                                kind: summary.kind.clone(),
                                title: summary.title.clone(),
                                model: None, // Model comes later via SessionMetadataReceived
                            })
                    });

                    let mode = if is_new { "new session" } else { "loaded session" };
                    CommandResult::SessionTransferred {
                        to_connector: connector_id.clone(),
                        message: Some(format!("Transferred to {} ({})", connector_id, mode)),
                        connector_info,
                    }
                }
                Ok(Ok(SessionTransferResult::Failed(reason))) => {
                    CommandResult::Error(format!("Transfer failed: {}", reason))
                }
                Ok(Err(_)) => {
                    CommandResult::Error("Transfer was cancelled".to_string())
                }
                Err(_) => {
                    CommandResult::Error("Transfer timed out".to_string())
                }
            }
        }
        None => {
            CommandResult::Error("Session transfer is not available.".to_string())
        }
    }
}

/// Execute the /claude command
async fn execute_claude(
    gateway_connector_id: &str,
    session_id: &str,
    current_mode_id: &str,
    current_model_id: &str,
    list_callback: Option<&ConnectorListCallback>,
    transfer_callback: Option<&SessionTransferCallback>,
) -> CommandResult {
    use crate::connectors::acp::config::ConnectorAgentType;

    // First, find a Claude connector by agent_type, with fallback to name matching
    let claude_connector = match list_callback {
        Some(cb) => {
            let connectors = cb();
            // Priority 1: Find by agent_type = Claude
            connectors.iter().find(|c| c.agent_type == Some(ConnectorAgentType::Claude))
                .cloned()
                // Priority 2: Fallback to name matching (for backwards compatibility)
                .or_else(|| {
                    connectors.into_iter().find(|c| {
                        c.title.to_lowercase().contains("claude") ||
                        c.id.to_lowercase().contains("claude")
                    })
                })
        }
        None => None,
    };

    match claude_connector {
        Some(connector) => {
            execute_select_connector(
                gateway_connector_id,
                session_id,
                &connector.id,
                None,
                current_mode_id,
                current_model_id,
                list_callback,
                transfer_callback,
            ).await
        }
        None => {
            CommandResult::Error(
                "No Claude connector found. Use `/list-connectors` to see available connectors.".to_string()
            )
        }
    }
}

/// Execute the /help command
fn execute_help() -> CommandResult {
    let help_text = r#"**Available Commands**

`/echo on|off`
Enable or disable echo mode. When enabled, your messages will be echoed back.

`/list-connectors`
Show all available connectors that you can transfer this session to.

`/select-connector <id>`
Transfer this session to the specified connector. Use the connector ID from `/list-connectors`.

`/claude`
Shortcut to transfer this session to a Claude connector (finds a connector with "claude" in its name).

`/help`
Show this help message.

**Notes**
- Commands start with `/`
- Arguments can be quoted if they contain spaces
- Echo mode lets you test the connection without using an external agent"#;

    CommandResult::Message(help_text.to_string())
}

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

    #[test]
    fn test_parse_command_help() {
        assert_eq!(parse_command("/help"), Some(Command::Help));
        assert_eq!(parse_command("/HELP"), Some(Command::Help));
        assert_eq!(parse_command("/?"), Some(Command::Help));
        assert_eq!(parse_command("/help extra args"), Some(Command::Help));
    }

    #[test]
    fn test_parse_command_echo() {
        assert_eq!(parse_command("/echo on"), Some(Command::Echo(true)));
        assert_eq!(parse_command("/echo off"), Some(Command::Echo(false)));
        assert_eq!(parse_command("/echo ON"), Some(Command::Echo(true)));
        assert_eq!(parse_command("/echo true"), Some(Command::Echo(true)));
        assert_eq!(parse_command("/echo false"), Some(Command::Echo(false)));
        assert_eq!(parse_command("/echo enable"), Some(Command::Echo(true)));
        assert_eq!(parse_command("/echo disable"), Some(Command::Echo(false)));
        assert_eq!(parse_command("/echo"), None); // Missing argument
        assert_eq!(parse_command("/echo invalid"), None); // Invalid argument
    }

    #[test]
    fn test_parse_command_list_connectors() {
        assert_eq!(parse_command("/list-connectors"), Some(Command::ListConnectors));
        assert_eq!(parse_command("/listconnectors"), Some(Command::ListConnectors));
        assert_eq!(parse_command("/connectors"), Some(Command::ListConnectors));
        assert_eq!(parse_command("/list"), Some(Command::ListConnectors));
    }

    #[test]
    fn test_parse_command_select_connector() {
        assert_eq!(
            parse_command("/select-connector my-connector"),
            Some(Command::SelectConnector("my-connector".to_string(), None))
        );
        assert_eq!(
            parse_command("/select my-connector"),
            Some(Command::SelectConnector("my-connector".to_string(), None))
        );
        assert_eq!(
            parse_command("/use my-connector"),
            Some(Command::SelectConnector("my-connector".to_string(), None))
        );
        assert_eq!(parse_command("/select-connector"), None); // Missing ID
    }

    #[test]
    fn test_parse_command_claude() {
        assert_eq!(parse_command("/claude"), Some(Command::Claude));
    }

    #[test]
    fn test_parse_command_not_a_command() {
        assert_eq!(parse_command("hello"), None);
        assert_eq!(parse_command("not a command"), None);
        assert_eq!(parse_command(""), None);
        assert_eq!(parse_command("  "), None);
    }

    #[test]
    fn test_parse_command_unknown_command() {
        assert_eq!(parse_command("/unknown"), None);
        assert_eq!(parse_command("/foobar"), None);
    }

    #[test]
    fn test_parse_arguments_simple() {
        assert_eq!(parse_arguments("arg1 arg2"), vec!["arg1", "arg2"]);
        assert_eq!(parse_arguments("single"), vec!["single"]);
        assert_eq!(parse_arguments(""), Vec::<&str>::new());
    }

    #[test]
    fn test_parse_arguments_quoted() {
        assert_eq!(
            parse_arguments("\"arg with spaces\" arg2"),
            vec!["arg with spaces", "arg2"]
        );
        assert_eq!(
            parse_arguments("'single quoted' arg2"),
            vec!["single quoted", "arg2"]
        );
    }

    #[test]
    fn test_parse_arguments_whitespace() {
        assert_eq!(parse_arguments("  arg1   arg2  "), vec!["arg1", "arg2"]);
    }

    #[test]
    fn test_execute_echo_on() {
        let mut session = GatewaySession::new(
            "test".to_string(),
            "Test".to_string(),
            false,
            None,
        );
        let result = execute_echo(true, &mut session);
        assert!(session.echo_enabled);
        match result {
            CommandResult::Message(msg) => assert!(msg.contains("enabled")),
            _ => panic!("Expected Message result"),
        }
    }

    #[test]
    fn test_execute_echo_off() {
        let mut session = GatewaySession::new(
            "test".to_string(),
            "Test".to_string(),
            true,
            None,
        );
        let result = execute_echo(false, &mut session);
        assert!(!session.echo_enabled);
        match result {
            CommandResult::Message(msg) => assert!(msg.contains("disabled")),
            _ => panic!("Expected Message result"),
        }
    }

    #[test]
    fn test_execute_help() {
        let result = execute_help();
        match result {
            CommandResult::Message(msg) => {
                assert!(msg.contains("/echo"));
                assert!(msg.contains("/help"));
                assert!(msg.contains("/list-connectors"));
                assert!(msg.contains("/select-connector"));
                assert!(msg.contains("/claude"));
            }
            _ => panic!("Expected Message result"),
        }
    }

    #[test]
    fn test_execute_list_connectors_no_callback() {
        let result = execute_list_connectors(None);
        match result {
            CommandResult::Error(msg) => assert!(msg.contains("not available")),
            _ => panic!("Expected Error result"),
        }
    }

    #[tokio::test]
    async fn test_execute_select_connector_no_callback() {
        let result = execute_select_connector(
            "gateway-1",
            "session-1",
            "connector-1",
            None,
            "ask",
            "default",
            None,
            None,
        ).await;
        match result {
            CommandResult::Error(msg) => assert!(msg.contains("not available")),
            _ => panic!("Expected Error result"),
        }
    }

    #[test]
    fn test_execute_list_connectors_with_callback() {
        use std::sync::Arc;

        // Create a callback that returns test connectors
        let callback: super::ConnectorListCallback = Arc::new(|| {
            use crate::connectors::acp::config::ConnectorAgentType;
            vec![
                ConnectorSummaryInfo {
                    id: "opencode-1".to_string(),
                    title: "OpenCode Local".to_string(),
                    kind: "OpenCode".to_string(),
                    state: "Ready".to_string(),
                    supports_session_transfer: true,
                    agent_type: None,
                },
                ConnectorSummaryInfo {
                    id: "claude-acp".to_string(),
                    title: "Claude (ACP)".to_string(),
                    kind: "Acp".to_string(),
                    state: "Ready".to_string(),
                    supports_session_transfer: true,
                    agent_type: Some(ConnectorAgentType::Claude),
                },
                ConnectorSummaryInfo {
                    id: "gateway-1".to_string(),
                    title: "Gateway".to_string(),
                    kind: "Gateway".to_string(),
                    state: "Ready".to_string(),
                    supports_session_transfer: true,
                    agent_type: None,
                },
            ]
        });

        let result = execute_list_connectors(Some(&callback));
        match result {
            CommandResult::Message(msg) => {
                // Verify the output contains all connectors
                assert!(msg.contains("Available connectors:"));
                assert!(msg.contains("opencode-1"));
                assert!(msg.contains("OpenCode Local"));
                assert!(msg.contains("claude-acp"));
                assert!(msg.contains("Claude (ACP)"));
                assert!(msg.contains("gateway-1"));
                assert!(msg.contains("Gateway"));
                assert!(msg.contains("/select-connector"));
            }
            _ => panic!("Expected Message result, got {:?}", result),
        }
    }

    #[test]
    fn test_execute_list_connectors_empty() {
        use std::sync::Arc;

        // Create a callback that returns empty list
        let callback: super::ConnectorListCallback = Arc::new(|| Vec::new());

        let result = execute_list_connectors(Some(&callback));
        match result {
            CommandResult::Message(msg) => {
                assert!(msg.contains("No connectors available"));
            }
            _ => panic!("Expected Message result, got {:?}", result),
        }
    }

    #[test]
    fn test_sanitize_connector_id() {
        assert_eq!(sanitize_connector_id("valid-id_123"), "valid-id_123");
        assert_eq!(sanitize_connector_id("has spaces"), "has");
        assert_eq!(sanitize_connector_id("weird@#$chars"), "weirdchars");
        assert_eq!(sanitize_connector_id(""), "");
    }

    #[test]
    fn test_parse_select_connector_with_session_id() {
        assert_eq!(
            parse_command("/select-connector conn-1 session-abc"),
            Some(Command::SelectConnector("conn-1".to_string(), Some("session-abc".to_string())))
        );
        assert_eq!(
            parse_command("/select conn-1"),
            Some(Command::SelectConnector("conn-1".to_string(), None))
        );
    }

    #[tokio::test]
    async fn test_execute_select_connector_with_connector_info() {
        use std::sync::Arc;
        use crate::connectors::gateway::{SessionTransferCallback, SessionTransferResult};

        // Create a list callback that returns test connectors
        let list_callback: ConnectorListCallback = Arc::new(|| {
            use crate::connectors::acp::config::ConnectorAgentType;
            vec![
                ConnectorSummaryInfo {
                    id: "opencode-1".to_string(),
                    title: "OpenCode Local".to_string(),
                    kind: "OpenCode".to_string(),
                    state: "Ready".to_string(),
                    supports_session_transfer: true,
                    agent_type: None,
                },
                ConnectorSummaryInfo {
                    id: "claude-acp".to_string(),
                    title: "Claude (ACP)".to_string(),
                    kind: "Acp".to_string(),
                    state: "Ready".to_string(),
                    supports_session_transfer: true,
                    agent_type: Some(ConnectorAgentType::Claude),
                },
            ]
        });

        // Create a transfer callback that simulates a successful transfer
        let transfer_callback: SessionTransferCallback = Arc::new(|request| {
            let result = SessionTransferResult::Transferred {
                connector_id: request.target_connector_id.clone(),
                session_id: "new-session-123".to_string(),
                is_new: true,
                models: None,
                modes: None,
            };
            let _ = request.result_tx.send(result);
        });

        let result = execute_select_connector(
            "gateway-1",
            "session-1",
            "opencode-1",
            None,
            "ask",
            "default",
            Some(&list_callback),
            Some(&transfer_callback),
        )
        .await;

        match result {
            CommandResult::SessionTransferred {
                to_connector,
                message,
                connector_info,
            } => {
                assert_eq!(to_connector, "opencode-1");
                assert!(message.is_some());

                // Verify connector info is populated
                let info = connector_info.expect("connector_info should be populated");
                assert_eq!(info.kind, "OpenCode");
                assert_eq!(info.title, "OpenCode Local");
                assert_eq!(info.model, None); // Model comes later via SessionMetadataReceived
            }
            _ => panic!("Expected SessionTransferred result, got {:?}", result),
        }
    }

    #[tokio::test]
    async fn test_execute_select_connector_without_list_callback() {
        use std::sync::Arc;
        use crate::connectors::gateway::{SessionTransferCallback, SessionTransferResult};

        // Create a transfer callback that simulates a successful transfer
        let transfer_callback: SessionTransferCallback = Arc::new(|request| {
            let result = SessionTransferResult::Transferred {
                connector_id: request.target_connector_id.clone(),
                session_id: "new-session-123".to_string(),
                is_new: true,
                models: None,
                modes: None,
            };
            let _ = request.result_tx.send(result);
        });

        let result = execute_select_connector(
            "gateway-1",
            "session-1",
            "opencode-1",
            None,
            "ask",
            "default",
            None, // No list callback
            Some(&transfer_callback),
        )
        .await;

        match result {
            CommandResult::SessionTransferred {
                to_connector,
                message,
                connector_info,
            } => {
                assert_eq!(to_connector, "opencode-1");
                assert!(message.is_some());

                // Without list callback, connector info should be None
                assert!(connector_info.is_none(), "connector_info should be None when list_callback is not available");
            }
            _ => panic!("Expected SessionTransferred result, got {:?}", result),
        }
    }
}
