//! Utilities for spawning and managing the ACP mocker in tests.

use std::process::{Child, Command, Stdio};
use std::time::Duration;
use tokio::time::sleep;

/// Handle to a running mocker process.
pub struct MockerProcess {
    process: Child,
    mode: MockerMode,
}

/// Mocker execution mode.
#[derive(Debug, Clone, Copy)]
pub enum MockerMode {
    /// Stdio mode (stdin/stdout communication)
    Stdio,
    /// HTTP mode (HTTP + SSE communication)
    Http { port: u16 },
}

impl MockerProcess {
    /// Spawn a mocker in stdio mode.
    ///
    /// # Example
    /// ```no_run
    /// use dirigent_core::tests::acp_integration::MockerProcess;
    ///
    /// #[tokio::test]
    /// async fn test_stdio_mocker() {
    ///     let mocker = MockerProcess::spawn_stdio().await.unwrap();
    ///     // Use mocker...
    ///     mocker.kill().await.unwrap();
    /// }
    /// ```
    pub async fn spawn_stdio() -> Result<Self, String> {
        Self::spawn_stdio_with_args(&[]).await
    }

    /// Spawn a mocker in stdio mode with custom arguments.
    pub async fn spawn_stdio_with_args(args: &[&str]) -> Result<Self, String> {
        let mut cmd_args = vec!["serve", "--stdio"];
        cmd_args.extend_from_slice(args);

        let process = Command::new("cargo")
            .args(&["run", "--package", "dirigate", "--"])
            .args(&cmd_args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::inherit())
            .spawn()
            .map_err(|e| format!("Failed to spawn mocker: {}", e))?;

        // Give the mocker time to start
        sleep(Duration::from_millis(500)).await;

        Ok(Self {
            process,
            mode: MockerMode::Stdio,
        })
    }

    /// Spawn a mocker in HTTP mode on a specific port.
    ///
    /// # Example
    /// ```no_run
    /// use dirigent_core::tests::acp_integration::MockerProcess;
    ///
    /// #[tokio::test]
    /// async fn test_http_mocker() {
    ///     let mocker = MockerProcess::spawn_http(8888).await.unwrap();
    ///     // Use mocker at http://localhost:8888
    ///     mocker.kill().await.unwrap();
    /// }
    /// ```
    pub async fn spawn_http(port: u16) -> Result<Self, String> {
        Self::spawn_http_with_args(port, &[]).await
    }

    /// Spawn a mocker in HTTP mode with custom arguments.
    pub async fn spawn_http_with_args(port: u16, args: &[&str]) -> Result<Self, String> {
        let port_str = port.to_string();
        let mut cmd_args = vec!["serve", "--port", port_str.as_str()];
        cmd_args.extend_from_slice(args);

        let process = Command::new("cargo")
            .args(&["run", "--package", "dirigate", "--"])
            .args(&cmd_args)
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::inherit())
            .spawn()
            .map_err(|e| format!("Failed to spawn mocker: {}", e))?;

        // Give the HTTP server time to start
        sleep(Duration::from_millis(1000)).await;

        // TODO: Add health check to verify mocker is ready

        Ok(Self {
            process,
            mode: MockerMode::Http { port },
        })
    }

    /// Get the mocker mode.
    pub fn mode(&self) -> MockerMode {
        self.mode
    }

    /// Get the HTTP URL if in HTTP mode.
    pub fn http_url(&self) -> Option<String> {
        match self.mode {
            MockerMode::Http { port } => Some(format!("http://localhost:{}", port)),
            MockerMode::Stdio => None,
        }
    }

    /// Kill the mocker process.
    pub async fn kill(mut self) -> Result<(), String> {
        self.process
            .kill()
            .map_err(|e| format!("Failed to kill mocker: {}", e))?;

        // Wait for process to exit
        sleep(Duration::from_millis(100)).await;

        Ok(())
    }
}

impl Drop for MockerProcess {
    fn drop(&mut self) {
        // Best-effort cleanup on drop
        let _ = self.process.kill();
    }
}

/// Configuration for mocker test scenarios.
#[derive(Debug, Clone)]
pub struct MockerConfig {
    /// Preset configuration name
    pub preset: Option<String>,
    /// Custom configuration JSON/TOML
    pub config: Option<String>,
}

impl MockerConfig {
    /// Create a default mocker configuration.
    pub fn default() -> Self {
        Self {
            preset: None,
            config: None,
        }
    }

    /// Create a mocker configuration with a preset.
    pub fn with_preset(preset: impl Into<String>) -> Self {
        Self {
            preset: Some(preset.into()),
            config: None,
        }
    }

    /// Create a mocker configuration with custom config.
    // Test utility - kept for future mocker configuration test development
    #[allow(dead_code)]
    pub fn with_config(config: impl Into<String>) -> Self {
        Self {
            preset: None,
            config: Some(config.into()),
        }
    }

    /// Convert to command-line arguments for the mocker.
    pub fn to_args(&self) -> Vec<String> {
        let mut args = Vec::new();

        if let Some(preset) = &self.preset {
            args.push("--preset".to_string());
            args.push(preset.clone());
        }

        if let Some(config) = &self.config {
            args.push("--config".to_string());
            args.push(config.clone());
        }

        args
    }
}

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

    #[test]
    fn test_mocker_config_default() {
        let config = MockerConfig::default();
        assert!(config.preset.is_none());
        assert!(config.config.is_none());
        assert!(config.to_args().is_empty());
    }

    #[test]
    fn test_mocker_config_with_preset() {
        let config = MockerConfig::with_preset("basic");
        assert_eq!(config.preset, Some("basic".to_string()));
        assert_eq!(config.to_args(), vec!["--preset", "basic"]);
    }
}
