// Docker Pool - Tier 2 Sandbox (target: 80-150ms from warm pool)
// Pre-warmed container pool for complex tasks
// Pool management: auto-replenish, graceful shutdown

use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Instant;

use bollard::container::{
    Config, CreateContainerOptions, RemoveContainerOptions, StartContainerOptions,
    StopContainerOptions,
};
use bollard::exec::CreateExecOptions;
use bollard::Docker;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};

use crate::metrics::{SANDBOX_EXECUTIONS, SANDBOX_POOL_SIZE};
use crate::sandbox::{SandboxCommand, SandboxResult, SandboxTier};
use crate::RLMError;

/// Docker pool configuration
#[derive(Debug, Clone)]
pub struct DockerPoolConfig {
    /// Pool size (standby containers)
    pub pool_size: usize,
    /// Container image name
    pub image: String,
    /// Maximum execution time in seconds
    pub max_execution_secs: u64,
    /// Auto-replenish pool when claimed
    pub auto_replenish: bool,
}

impl Default for DockerPoolConfig {
    fn default() -> Self {
        Self {
            pool_size: 10,
            image: "vapora-rlm-executor:latest".to_string(),
            max_execution_secs: 30,
            auto_replenish: true,
        }
    }
}

/// Container in the pool
struct PooledContainer {
    container_id: String,
    #[allow(dead_code)] // Will be used for pool aging/refresh in future iterations
    created_at: Instant,
}

/// Docker container pool for executing complex commands
pub struct DockerPool {
    docker: Arc<Docker>,
    config: DockerPoolConfig,
    pool: Arc<Mutex<VecDeque<PooledContainer>>>,
}

impl DockerPool {
    /// Create a new Docker pool
    ///
    /// # Returns
    /// DockerPool instance or error if Docker is unavailable
    pub async fn new(config: DockerPoolConfig) -> crate::Result<Self> {
        let docker = Docker::connect_with_local_defaults()
            .map_err(|e| RLMError::SandboxError(format!("Failed to connect to Docker: {}", e)))?;

        let pool = Arc::new(Mutex::new(VecDeque::new()));

        let docker_pool = Self {
            docker: Arc::new(docker),
            config,
            pool,
        };

        // Pre-warm the pool
        docker_pool.warm_pool().await?;

        Ok(docker_pool)
    }

    /// Pre-warm the pool by creating standby containers
    async fn warm_pool(&self) -> crate::Result<()> {
        info!(
            "Warming Docker pool with {} containers",
            self.config.pool_size
        );

        for i in 0..self.config.pool_size {
            match self.create_container().await {
                Ok(container_id) => {
                    let mut pool = self.pool.lock();
                    pool.push_back(PooledContainer {
                        container_id,
                        created_at: Instant::now(),
                    });
                    debug!(
                        "Created standby container {}/{}",
                        i + 1,
                        self.config.pool_size
                    );
                }
                Err(e) => {
                    warn!("Failed to create standby container: {}", e);
                }
            }
        }

        let pool_size = self.pool.lock().len();
        SANDBOX_POOL_SIZE
            .with_label_values(&["docker"])
            .set(pool_size as i64);
        info!("Docker pool warmed with {} containers", pool_size);

        Ok(())
    }

    /// Create a new container
    async fn create_container(&self) -> crate::Result<String> {
        let options = Some(CreateContainerOptions {
            name: format!("vapora-rlm-{}", uuid::Uuid::new_v4()),
            ..Default::default()
        });

        let config = Config {
            image: Some(self.config.image.clone()),
            tty: Some(false),
            attach_stdin: Some(true),
            attach_stdout: Some(true),
            attach_stderr: Some(true),
            open_stdin: Some(true),
            cmd: Some(vec!["/bin/sh".to_string()]), // Keep alive
            ..Default::default()
        };

        let container = self
            .docker
            .create_container(options, config)
            .await
            .map_err(|e| RLMError::SandboxError(format!("Failed to create container: {}", e)))?;

        // Start the container
        self.docker
            .start_container(&container.id, None::<StartContainerOptions<String>>)
            .await
            .map_err(|e| RLMError::SandboxError(format!("Failed to start container: {}", e)))?;

        Ok(container.id)
    }

    /// Execute a command in a pooled container
    ///
    /// # Arguments
    /// - `command`: Sandbox command to execute
    ///
    /// # Returns
    /// SandboxResult with output, stderr, exit code, duration, and tier
    pub async fn execute(&self, command: &SandboxCommand) -> crate::Result<SandboxResult> {
        let start = Instant::now();

        // Claim a container from the pool
        let container = self.claim_container().await?;

        // Execute command in container
        let result = self
            .execute_in_container(&container.container_id, command)
            .await;

        // Return container to pool or destroy if execution failed
        match &result {
            Ok(_) => {
                self.return_container(container).await?;
            }
            Err(_) => {
                // Destroy failed container
                let _ = self.destroy_container(&container.container_id).await;
                // Replenish pool
                if self.config.auto_replenish {
                    let _ = self.replenish_pool().await;
                }
            }
        }

        let duration_ms = start.elapsed().as_millis() as u64;

        let mut sandbox_result = result?;
        sandbox_result.duration_ms = duration_ms;
        sandbox_result.tier = SandboxTier::Docker;

        SANDBOX_EXECUTIONS
            .with_label_values(&[
                "docker",
                if sandbox_result.is_success() {
                    "success"
                } else {
                    "error"
                },
            ])
            .inc();

        debug!(
            "Docker execution: command={}, duration={}ms, exit_code={}",
            command.command, duration_ms, sandbox_result.exit_code
        );

        Ok(sandbox_result)
    }

    /// Claim a container from the pool
    async fn claim_container(&self) -> crate::Result<PooledContainer> {
        // Try to get from pool
        if let Some(container) = self.pool.lock().pop_front() {
            SANDBOX_POOL_SIZE.with_label_values(&["docker"]).dec();
            return Ok(container);
        }

        // Pool empty, create on demand
        warn!("Docker pool empty, creating container on demand");
        let container_id = self.create_container().await?;
        Ok(PooledContainer {
            container_id,
            created_at: Instant::now(),
        })
    }

    /// Return a container to the pool
    async fn return_container(&self, container: PooledContainer) -> crate::Result<()> {
        let mut pool = self.pool.lock();
        pool.push_back(container);
        SANDBOX_POOL_SIZE.with_label_values(&["docker"]).inc();
        Ok(())
    }

    /// Execute command in a specific container
    async fn execute_in_container(
        &self,
        container_id: &str,
        command: &SandboxCommand,
    ) -> crate::Result<SandboxResult> {
        // Create executor command JSON
        let executor_input = ExecutorInput {
            command: command.command.clone(),
            args: command.args.clone(),
            stdin: command.stdin.clone(),
        };

        let _input_json = serde_json::to_string(&executor_input)
            .map_err(|e| RLMError::SandboxError(format!("Failed to serialize input: {}", e)))?;

        // Create exec instance
        let exec_config = CreateExecOptions {
            attach_stdin: Some(true),
            attach_stdout: Some(true),
            attach_stderr: Some(true),
            cmd: Some(vec!["/executor".to_string(), command.command.clone()]),
            ..Default::default()
        };

        let exec = self
            .docker
            .create_exec(container_id, exec_config)
            .await
            .map_err(|e| RLMError::SandboxError(format!("Failed to create exec: {}", e)))?;

        // Start exec and capture output
        let _start_exec = self
            .docker
            .start_exec(&exec.id, None)
            .await
            .map_err(|e| RLMError::SandboxError(format!("Failed to start exec: {}", e)))?;

        // For Phase 4, we'll use a simplified approach
        // Real implementation would stream stdin/stdout/stderr
        // For now, return a placeholder result

        Ok(SandboxResult {
            output: format!("Docker execution: {}", command.command),
            stderr: String::new(),
            exit_code: 0,
            duration_ms: 0, // Set by caller
            tier: SandboxTier::Docker,
        })
    }

    /// Replenish pool with one new container
    async fn replenish_pool(&self) -> crate::Result<()> {
        let container_id = self.create_container().await?;
        let mut pool = self.pool.lock();
        pool.push_back(PooledContainer {
            container_id,
            created_at: Instant::now(),
        });
        SANDBOX_POOL_SIZE.with_label_values(&["docker"]).inc();
        Ok(())
    }

    /// Destroy a container
    async fn destroy_container(&self, container_id: &str) -> crate::Result<()> {
        // Stop container
        let _ = self
            .docker
            .stop_container(container_id, None::<StopContainerOptions>)
            .await;

        // Remove container
        self.docker
            .remove_container(
                container_id,
                Some(RemoveContainerOptions {
                    force: true,
                    ..Default::default()
                }),
            )
            .await
            .map_err(|e| RLMError::SandboxError(format!("Failed to remove container: {}", e)))?;

        Ok(())
    }

    /// Gracefully shutdown the pool (drain all containers)
    pub async fn shutdown(&self) -> crate::Result<()> {
        info!("Shutting down Docker pool");

        let containers: Vec<_> = {
            let mut pool = self.pool.lock();
            pool.drain(..).collect()
        };

        for container in containers {
            let _ = self.destroy_container(&container.container_id).await;
        }

        SANDBOX_POOL_SIZE.with_label_values(&["docker"]).set(0);
        info!("Docker pool shutdown complete");

        Ok(())
    }

    /// Get current pool size
    pub fn pool_size(&self) -> usize {
        self.pool.lock().len()
    }
}

/// Executor input format (JSON)
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ExecutorInput {
    command: String,
    args: Vec<String>,
    stdin: Option<String>,
}

/// Executor output format (JSON)
#[allow(dead_code)] // Will be used when Docker exec streaming is implemented
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ExecutorOutput {
    stdout: String,
    stderr: String,
    exit_code: i32,
}

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

    #[test]
    fn test_docker_pool_config_default() {
        let config = DockerPoolConfig::default();
        assert_eq!(config.pool_size, 10);
        assert_eq!(config.image, "vapora-rlm-executor:latest");
        assert_eq!(config.max_execution_secs, 30);
        assert!(config.auto_replenish);
    }

    #[test]
    fn test_executor_input_serialization() {
        let input = ExecutorInput {
            command: "grep".to_string(),
            args: vec!["error".to_string()],
            stdin: Some("line1\nerror: failed\nline3".to_string()),
        };

        let json = serde_json::to_string(&input).unwrap();
        assert!(json.contains("grep"));
        assert!(json.contains("error"));
    }

    // Note: Integration tests with real Docker are marked #[ignore] in
    // integration_test.rs These would test:
    // - DockerPool::new() with real Docker connection
    // - execute() with real container execution
    // - Pool warming and replenishment
    // - Graceful shutdown
}
