//! Container Manager
//!
//! Manages Docker containers for test environments using bollard.
//! Provides container lifecycle management, networking, and resource control.

use std::collections::HashMap;
use std::default::Default;

use anyhow::{anyhow, Context, Result};
use bollard::{
    exec::{CreateExecOptions, StartExecResults},
    models::{
        ContainerCreateBody, ContainerSummary, EndpointSettings, HostConfig, Ipam, IpamConfig,
        NetworkingConfig, RestartPolicy, RestartPolicyNameEnum,
    },
    query_parameters::{
        CreateContainerOptions, CreateImageOptions, ListContainersOptions, LogsOptions,
        RemoveContainerOptions, StartContainerOptions, StopContainerOptions,
    },
    Docker,
};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use tracing::{debug, error, info, warn};

use crate::test_environment::{
    ContainerInstance, NetworkConfig, ResourceLimits, TestEnvironment, TestEnvironmentStatus,
};

/// Container runtime type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ContainerRuntime {
    Docker,
    Podman,
}

/// Container manager for test environments
pub struct ContainerManager {
    docker: Docker,
    runtime: ContainerRuntime,
}

impl ContainerManager {
    /// Create new container manager
    pub fn new() -> Result<Self> {
        // Try to connect to Docker socket
        let docker = Docker::connect_with_local_defaults()
            .context("Failed to connect to Docker. Ensure Docker daemon is running.")?;

        Ok(Self {
            docker,
            runtime: ContainerRuntime::Docker,
        })
    }

    /// Create network for test environment
    pub async fn create_network(&self, config: &NetworkConfig) -> Result<String> {
        use bollard::models::NetworkCreateRequest;

        let ipam_config = IpamConfig {
            subnet: Some(config.subnet.clone()),
            gateway: None,
            ip_range: None,
            auxiliary_addresses: None,
        };

        let ipam = Ipam {
            driver: Some("default".to_string()),
            config: Some(vec![ipam_config]),
            options: None,
        };

        let mut labels = HashMap::new();
        labels.insert(
            "managed_by".to_string(),
            "provisioning_orchestrator".to_string(),
        );
        labels.insert("type".to_string(), "test_environment".to_string());

        let create_request = NetworkCreateRequest {
            name: config.name.clone(),
            driver: Some("bridge".to_string()),
            internal: Some(false),
            attachable: Some(true),
            ipam: Some(ipam),
            enable_ipv4: Some(true),
            options: Some(HashMap::new()),
            labels: Some(labels),
            ..Default::default()
        };

        let response = self
            .docker
            .create_network(create_request)
            .await
            .context("Failed to create Docker network")?;

        let network_id = response.id;

        info!("Created network {} ({})", config.name, network_id);
        Ok(network_id)
    }

    /// Remove network
    pub async fn remove_network(&self, network_id: &str) -> Result<()> {
        self.docker
            .remove_network(network_id)
            .await
            .context("Failed to remove Docker network")?;

        info!("Removed network {}", network_id);
        Ok(())
    }

    /// Pull image if not exists
    pub async fn ensure_image(&self, image: &str) -> Result<()> {
        let options = CreateImageOptions {
            from_image: Some(image.to_string()),
            ..Default::default()
        };

        let mut stream = self.docker.create_image(Some(options), None, None);

        while let Some(result) = stream.next().await {
            match result {
                Ok(info) => {
                    if let Some(status) = info.status {
                        debug!("Image pull: {}", status);
                    }
                }
                Err(e) => {
                    error!("Error pulling image: {}", e);
                    return Err(anyhow!("Failed to pull image {}: {}", image, e));
                }
            }
        }

        info!("Image {} ready", image);
        Ok(())
    }

    /// Create container
    pub async fn create_container(
        &self,
        name: &str,
        image: &str,
        network_id: Option<&str>,
        resources: &ResourceLimits,
        environment: &HashMap<String, String>,
        command: Option<Vec<String>>,
    ) -> Result<ContainerInstance> {
        // Ensure image exists
        self.ensure_image(image).await?;

        // Configure host settings
        let mut host_config = HostConfig {
            // CPU: nano_cpus = cores * 1_000_000_000
            nano_cpus: Some((resources.cpu_millicores as i64) * 1_000_000),
            // Memory in bytes
            memory: Some((resources.memory_mb as i64) * 1024 * 1024),
            restart_policy: Some(RestartPolicy {
                name: Some(RestartPolicyNameEnum::UNLESS_STOPPED),
                maximum_retry_count: Some(3),
            }),
            ..Default::default()
        };

        // Network configuration
        let mut endpoint_config = HashMap::new();
        if let Some(net_id) = network_id {
            endpoint_config.insert(
                net_id.to_string(),
                EndpointSettings {
                    ..Default::default()
                },
            );
            host_config.network_mode = Some(net_id.to_string());
        }

        // Environment variables
        let env: Vec<String> = environment
            .iter()
            .map(|(k, v)| format!("{}={}", k, v))
            .collect();

        // Container configuration
        let mut labels = HashMap::new();
        labels.insert(
            "managed_by".to_string(),
            "provisioning_orchestrator".to_string(),
        );
        labels.insert("type".to_string(), "test_container".to_string());
        labels.insert("test_name".to_string(), name.to_string());

        let config = ContainerCreateBody {
            image: Some(image.to_string()),
            hostname: Some(name.to_string()),
            env: if env.is_empty() { None } else { Some(env) },
            cmd: command,
            host_config: Some(host_config),
            networking_config: Some(NetworkingConfig {
                endpoints_config: Some(endpoint_config),
            }),
            labels: Some(labels),
            ..Default::default()
        };

        let options = CreateContainerOptions {
            name: Some(name.to_string()),
            platform: String::new(),
        };

        let response = self
            .docker
            .create_container(Some(options), config)
            .await
            .context(format!("Failed to create container {}", name))?;

        let container_id = response.id;

        info!("Created container {} ({})", name, container_id);

        Ok(ContainerInstance {
            container_id,
            container_name: name.to_string(),
            node_name: None,
            ip_address: None,
            ports: HashMap::new(),
            status: "created".to_string(),
        })
    }

    /// Start container
    pub async fn start_container(&self, container_id: &str) -> Result<()> {
        self.docker
            .start_container(container_id, None::<StartContainerOptions>)
            .await
            .context(format!("Failed to start container {}", container_id))?;

        info!("Started container {}", container_id);
        Ok(())
    }

    /// Stop container
    pub async fn stop_container(&self, container_id: &str, timeout: Option<i64>) -> Result<()> {
        let options = StopContainerOptions {
            t: Some(timeout.unwrap_or(10) as i32),
            signal: None,
        };

        self.docker
            .stop_container(container_id, Some(options))
            .await
            .context(format!("Failed to stop container {}", container_id))?;

        info!("Stopped container {}", container_id);
        Ok(())
    }

    /// Remove container
    pub async fn remove_container(&self, container_id: &str, force: bool) -> Result<()> {
        let options = RemoveContainerOptions {
            force,
            v: true, // Remove volumes
            ..Default::default()
        };

        self.docker
            .remove_container(container_id, Some(options))
            .await
            .context(format!("Failed to remove container {}", container_id))?;

        info!("Removed container {}", container_id);
        Ok(())
    }

    /// Execute command in container
    pub async fn exec_in_container(
        &self,
        container_id: &str,
        command: Vec<String>,
    ) -> Result<String> {
        let exec_options = CreateExecOptions {
            attach_stdout: Some(true),
            attach_stderr: Some(true),
            cmd: Some(command.clone()),
            ..Default::default()
        };

        let exec = self
            .docker
            .create_exec(container_id, exec_options)
            .await
            .context("Failed to create exec")?;

        let output = if let StartExecResults::Attached { mut output, .. } =
            self.docker.start_exec(&exec.id, None).await?
        {
            let mut result = String::new();
            while let Some(chunk) = output.next().await {
                match chunk {
                    Ok(chunk) => {
                        result.push_str(&chunk.to_string());
                    }
                    Err(e) => {
                        error!("Error reading exec output: {}", e);
                        break;
                    }
                }
            }
            result
        } else {
            return Err(anyhow!("Failed to attach to exec"));
        };

        debug!("Exec in {}: {:?} -> {}", container_id, command, output);
        Ok(output)
    }

    /// Get container logs
    pub async fn get_container_logs(
        &self,
        container_id: &str,
        tail: Option<&str>,
    ) -> Result<String> {
        let options = LogsOptions {
            stdout: true,
            stderr: true,
            tail: tail.unwrap_or("100").to_string(),
            ..Default::default()
        };

        let mut stream = self.docker.logs(container_id, Some(options));
        let mut logs = String::new();

        while let Some(chunk) = stream.next().await {
            match chunk {
                Ok(chunk) => {
                    logs.push_str(&chunk.to_string());
                }
                Err(e) => {
                    error!("Error reading logs: {}", e);
                    break;
                }
            }
        }

        Ok(logs)
    }

    /// Get container IP address
    pub async fn get_container_ip(
        &self,
        container_id: &str,
        network_id: &str,
    ) -> Result<Option<String>> {
        let inspect = self
            .docker
            .inspect_container(container_id, None)
            .await
            .context("Failed to inspect container")?;

        if let Some(network_settings) = inspect.network_settings {
            if let Some(networks) = network_settings.networks {
                if let Some(network) = networks.get(network_id) {
                    return Ok(network.ip_address.clone());
                }
            }
        }

        Ok(None)
    }

    /// List containers by label
    pub async fn list_containers_by_label(
        &self,
        label: &str,
        value: &str,
    ) -> Result<Vec<ContainerSummary>> {
        let mut filters = HashMap::new();
        filters.insert("label".to_string(), vec![format!("{}={}", label, value)]);

        let options = ListContainersOptions {
            all: true,
            filters: Some(filters),
            ..Default::default()
        };

        let containers = self
            .docker
            .list_containers(Some(options))
            .await
            .context("Failed to list containers")?;

        Ok(containers)
    }

    /// Cleanup test environment
    pub async fn cleanup_environment(
        &self,
        environment: &mut TestEnvironment,
        force: bool,
    ) -> Result<()> {
        environment.update_status(TestEnvironmentStatus::Cleaning);

        // Stop and remove containers
        for container in &environment.containers {
            if let Err(e) = self.stop_container(&container.container_id, Some(5)).await {
                warn!("Failed to stop container {}: {}", container.container_id, e);
            }

            if let Err(e) = self.remove_container(&container.container_id, force).await {
                error!(
                    "Failed to remove container {}: {}",
                    container.container_id, e
                );
            }
        }

        // Remove network
        if let Some(network_id) = &environment.network_id {
            if let Err(e) = self.remove_network(network_id).await {
                error!("Failed to remove network {}: {}", network_id, e);
            }
        }

        environment.update_status(TestEnvironmentStatus::Destroyed);
        info!("Cleaned up environment {}", environment.id);

        Ok(())
    }
}

impl Default for ContainerManager {
    fn default() -> Self {
        Self::new().expect("Failed to create Container Manager")
    }
}
