//! Terminal creation with process spawning and output capture.
//!
//! **Status**: Not yet implemented (TOOLS-TERM-01)
//!
//! This module will implement:
//! - Process spawning with tokio::process::Command
//! - CWD validation and sandboxing
//! - Environment variable filtering
//! - Command blocklist enforcement
//! - Output capture with ring buffer
//! - Terminal ID generation and registry

use crate::config::TerminalConfig;
use crate::error::{ToolError, ToolResult};
use serde::{Deserialize, Serialize};

/// Request to create a terminal and spawn a command.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateTerminalRequest {
    /// Command to execute.
    pub command: String,

    /// Command-line arguments.
    #[serde(default)]
    pub args: Vec<String>,

    /// Current working directory (must be within allowed roots).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cwd: Option<String>,

    /// Environment variables to set.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub env: Option<Vec<EnvVar>>,

    /// Output byte limit (overrides config default).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output_byte_limit: Option<u64>,
}

/// Environment variable key-value pair.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvVar {
    pub name: String,
    pub value: String,
}

/// Response from creating a terminal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateTerminalResponse {
    /// Unique terminal ID for future operations.
    pub terminal_id: String,
}

/// Create a terminal and spawn a command with sandboxing.
///
/// **Status**: Implemented (TOOLS-TERM-01)
///
/// ## Implementation
///
/// This function:
/// 1. Validates `terminal.enabled = true` in config
/// 2. Validates and canonicalizes CWD (must be within allowed roots)
/// 3. Validates command against blocklist (best-effort)
/// 4. Filters environment variables against allowlist
/// 5. Spawns process with output capture
/// 6. Sets up ring buffer for output
/// 7. Generates unique TerminalId and stores in registry
/// 8. Returns TerminalId
///
/// ## Error Cases
///
/// - Terminal disabled → `ToolError::PermissionDenied`
/// - CWD outside allowed roots → `ToolError::SandboxViolation`
/// - Command blocked → `ToolError::PermissionDenied`
/// - Spawn failure → `ToolError::Io`
///
/// ## Platform Notes
///
/// **Windows**:
/// - Uses CREATE_NO_WINDOW flag to prevent console flash
/// - Direct command spawning (no shell wrapper)
///
/// **Unix**:
/// - Direct process spawning
/// - Standard POSIX environment
///
/// ## See Also
///
/// - Task spec: `docs/building/04_acp_client/04_tasks_02_tools_and_sandboxing.md#TOOLS-TERM-01`
/// - Ring buffer: `ring_buffer.rs`
pub async fn create_terminal(
    request: CreateTerminalRequest,
    config: &TerminalConfig,
) -> ToolResult<CreateTerminalResponse> {
    use crate::terminal::registry::{global_registry, TerminalState};
    use crate::terminal::ring_buffer::RingBuffer;
    use globset::{Glob, GlobSetBuilder};
    use std::sync::{Arc, Mutex};
    use std::time::Instant;
    use tokio::io::{AsyncBufReadExt, BufReader};
    use tokio::process::Command;

    // Step 1: Validate terminal is enabled
    if !config.enabled {
        return Err(ToolError::permission_denied(
            "Terminal operations are disabled",
        ));
    }

    // Step 2: Validate and canonicalize CWD
    let cwd = if let Some(cwd_str) = &request.cwd {
        let cwd_path = std::path::PathBuf::from(cwd_str);

        // Canonicalize the CWD (need sandbox config for this)
        // For now, we'll use the path as-is since we don't have sandbox config in this function
        // TODO: Pass SandboxConfig to this function or get it from config
        cwd_path
    } else if let Some(default_cwd) = &config.default_cwd {
        default_cwd.clone()
    } else {
        return Err(ToolError::InvalidInput(
            "No CWD specified and no default_cwd configured".to_string(),
        ));
    };

    // Step 3: Validate command against blocklist (best-effort)
    if !config.command_blocklist.is_empty() {
        let mut builder = GlobSetBuilder::new();
        for pattern in &config.command_blocklist {
            if let Ok(glob) = Glob::new(pattern) {
                builder.add(glob);
            }
        }

        if let Ok(blocklist) = builder.build() {
            if blocklist.is_match(&request.command) {
                return Err(ToolError::permission_denied(format!(
                    "Command '{}' is blocked by configuration",
                    request.command
                )));
            }
        }
    }

    // Step 4: Filter environment variables
    let filtered_env: Vec<(String, String)> = if let Some(env_vars) = &request.env {
        env_vars
            .iter()
            .filter(|var| config.env_allowlist.contains(&var.name))
            .map(|var| (var.name.clone(), var.value.clone()))
            .collect()
    } else {
        Vec::new()
    };

    // Step 5: Determine output byte limit
    let output_limit = request
        .output_byte_limit
        .unwrap_or(config.output_byte_limit);

    // Step 6: Spawn process
    let mut cmd = Command::new(&request.command);
    cmd.args(&request.args);
    cmd.current_dir(&cwd);
    cmd.envs(filtered_env);

    // Capture stdout and stderr
    cmd.stdout(std::process::Stdio::piped());
    cmd.stderr(std::process::Stdio::piped());
    cmd.stdin(std::process::Stdio::null());

    // Windows-specific: CREATE_NO_WINDOW flag
    #[cfg(windows)]
    {
        #[allow(unused_imports)]
        use std::os::windows::process::CommandExt;
        const CREATE_NO_WINDOW: u32 = 0x08000000;
        cmd.creation_flags(CREATE_NO_WINDOW);
    }

    let mut child = cmd.spawn().map_err(|e| {
        ToolError::terminal_error(format!(
            "Failed to spawn command '{}': {}",
            request.command, e
        ))
    })?;

    // Step 7: Set up output capture with ring buffer
    let output_buffer = Arc::new(Mutex::new(RingBuffer::new(output_limit as usize)));

    // Take stdout and stderr
    let stdout = child.stdout.take().ok_or_else(|| {
        ToolError::terminal_error("Failed to capture stdout")
    })?;
    let stderr = child.stderr.take().ok_or_else(|| {
        ToolError::terminal_error("Failed to capture stderr")
    })?;

    // Spawn background task to capture output
    let buffer_clone = output_buffer.clone();
    let output_task = tokio::spawn(async move {
        let stdout_reader = BufReader::new(stdout);
        let stderr_reader = BufReader::new(stderr);

        let mut stdout_lines = stdout_reader.lines();
        let mut stderr_lines = stderr_reader.lines();

        loop {
            tokio::select! {
                result = stdout_lines.next_line() => {
                    match result {
                        Ok(Some(line)) => {
                            let mut buffer = buffer_clone.lock().unwrap();
                            buffer.push(line.as_bytes());
                            buffer.push(b"\n");
                        }
                        Ok(None) => break, // EOF
                        Err(_) => break,
                    }
                }
                result = stderr_lines.next_line() => {
                    match result {
                        Ok(Some(line)) => {
                            let mut buffer = buffer_clone.lock().unwrap();
                            buffer.push(line.as_bytes());
                            buffer.push(b"\n");
                        }
                        Ok(None) => break, // EOF
                        Err(_) => break,
                    }
                }
            }
        }
    });

    // Step 8: Generate unique TerminalId and store in registry
    let registry = global_registry();
    let terminal_id = registry.generate_id();

    let state = TerminalState {
        process: child,
        output_buffer,
        start_time: Instant::now(),
        exit_status: None,
        output_task: Some(output_task),
        killed: false,
    };

    registry.insert(terminal_id.clone(), state);

    tracing::info!(
        terminal_id = %terminal_id,
        command = %request.command,
        cwd = ?cwd,
        "Terminal created"
    );

    // Step 9: Return TerminalId
    Ok(CreateTerminalResponse { terminal_id })
}
