use crate::manifest::AppManifest;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::process::Command;
use thiserror::Error;
use tokio::process::Command as TokioCommand;

#[derive(Debug, Error)]
pub enum PodmanError {
    #[error("Podman command failed: {0}")]
    CommandFailed(String),
    #[error("Container not found: {0}")]
    NotFound(String),
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerStatus {
    pub id: String,
    pub name: String,
    pub state: ContainerState,
    pub image: String,
    pub created: String,
    pub ports: Vec<String>,
    pub lan_address: Option<String>, // Launch URL for UI access
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ContainerState {
    Created,
    Running,
    Stopped,
    Exited,
    Paused,
    Unknown(String),
}

impl From<&str> for ContainerState {
    fn from(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "created" => ContainerState::Created,
            "running" => ContainerState::Running,
            "stopped" => ContainerState::Stopped,
            "exited" => ContainerState::Exited,
            "paused" => ContainerState::Paused,
            other => ContainerState::Unknown(other.to_string()),
        }
    }
}

pub struct PodmanClient {
    _user: String,
    rootless: bool,
}

impl PodmanClient {
    pub fn new(user: String) -> Self {
        // If running as root, use root podman context
        let is_root = std::env::var("USER").unwrap_or_default() == "root" || 
                     std::env::var("HOME").unwrap_or_default() == "/root";
        
        Self {
            _user: user,
            rootless: !is_root,
        }
    }
    
    #[allow(dead_code)]
    fn podman_command(&self) -> Command {
        let mut cmd = Command::new("podman");
        if self.rootless {
            // Use actual HOME environment variable instead of hardcoded /home
            if let Ok(home) = std::env::var("HOME") {
                cmd.env("HOME", home);
            }
        }
        cmd
    }
    
    fn podman_async(&self) -> TokioCommand {
        // Always use sudo podman to access system-wide containers
        let mut cmd = TokioCommand::new("sudo");
        cmd.arg("podman");
        cmd
    }
    
    pub async fn pull_image(&self, image: &str, signature: Option<&str>) -> Result<()> {
        let mut cmd = self.podman_async();
        cmd.arg("pull").arg(image);
        
        if let Some(sig) = signature {
            // Verify signature with cosign if provided
            cmd.arg("--signature-policy").arg("default");
            // TODO: Implement cosign verification
            log::warn!("Signature verification not yet implemented: {}", sig);
        }
        
        let output = cmd
            .output()
            .await
            .context("Failed to execute podman pull")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to pull image: {}", stderr));
        }
        
        Ok(())
    }
    
    pub async fn create_container(
        &self,
        manifest: &AppManifest,
        name: &str,
    ) -> Result<String> {
        let mut cmd = self.podman_async();
        cmd.arg("create");
        
        // Container name
        cmd.arg("--name").arg(name);
        
        // Read-only root filesystem
        if manifest.app.security.readonly_root {
            cmd.arg("--read-only");
        }
        
        // Network policy
        match manifest.app.security.network_policy.as_str() {
            "host" => {
                cmd.arg("--network").arg("host");
            }
            "isolated" => {
                // Create isolated network (default)
            }
            _ => {
                cmd.arg("--network").arg(&manifest.app.security.network_policy);
            }
        }
        
        // Port mappings
        for port in &manifest.app.ports {
            cmd.arg("-p").arg(format!("{}:{}", port.host, port.container));
        }
        
        // Volumes
        for volume in &manifest.app.volumes {
            let mut mount = format!("{}:{}", volume.source, volume.target);
            if !volume.options.is_empty() {
                mount.push_str(&format!(":{}", volume.options.join(",")));
            }
            cmd.arg("-v").arg(mount);
        }
        
        // Devices
        for device in &manifest.app.devices {
            cmd.arg("--device").arg(device);
        }
        
        // Environment variables
        for env in &manifest.app.environment {
            cmd.arg("-e").arg(env);
        }
        
        // Resource limits
        if let Some(cpu) = manifest.app.resources.cpu_limit {
            cmd.arg("--cpus").arg(cpu.to_string());
        }
        
        if let Some(memory) = &manifest.app.resources.memory_limit {
            cmd.arg("--memory").arg(memory);
        }
        
        // Capabilities (drop all, add specified)
        cmd.arg("--cap-drop").arg("ALL");
        for cap in &manifest.app.security.capabilities {
            cmd.arg("--cap-add").arg(cap);
        }
        
        // Image
        cmd.arg(&manifest.app.container.image);
        
        let output = cmd
            .output()
            .await
            .context("Failed to create container")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to create container: {}", stderr));
        }
        
        let container_id = String::from_utf8_lossy(&output.stdout)
            .trim()
            .to_string();
        
        Ok(container_id)
    }
    
    pub async fn start_container(&self, name: &str) -> Result<()> {
        let mut cmd = self.podman_async();
        cmd.arg("start").arg(name);
        
        let output = cmd
            .output()
            .await
            .context("Failed to start container")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to start container: {}", stderr));
        }
        
        Ok(())
    }
    
    pub async fn stop_container(&self, name: &str) -> Result<()> {
        let mut cmd = self.podman_async();
        cmd.arg("stop").arg(name);
        
        let output = cmd
            .output()
            .await
            .context("Failed to stop container")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to stop container: {}", stderr));
        }
        
        Ok(())
    }
    
    pub async fn remove_container(&self, name: &str) -> Result<()> {
        let mut cmd = self.podman_async();
        cmd.arg("rm").arg("-f").arg(name);
        
        let output = cmd
            .output()
            .await
            .context("Failed to remove container")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to remove container: {}", stderr));
        }
        
        Ok(())
    }
    
    pub async fn get_container_status(&self, name: &str) -> Result<ContainerStatus> {
        let mut cmd = self.podman_async();
        cmd.arg("inspect")
            .arg("--format")
            .arg("{{.Id}}|{{.Name}}|{{.State.Status}}|{{.Config.Image}}|{{.Created}}|{{.NetworkSettings.Ports}}")
            .arg(name);
        
        let output = cmd
            .output()
            .await
            .context("Failed to inspect container")?;
        
        if !output.status.success() {
            return Err(anyhow::anyhow!("Container not found: {}", name));
        }
        
        let info = String::from_utf8_lossy(&output.stdout);
        let parts: Vec<&str> = info.trim().split('|').collect();
        
        if parts.len() < 5 {
            return Err(anyhow::anyhow!("Invalid container inspect output"));
        }
        
        Ok(ContainerStatus {
            id: parts[0].to_string(),
            name: parts[1].to_string(),
            state: ContainerState::from(parts[2]),
            image: parts[3].to_string(),
            created: parts[4].to_string(),
            ports: vec![], // TODO: Parse ports from parts[5]
            lan_address: None, // Set by docker_packages scanner
        })
    }
    
    pub async fn get_container_logs(&self, name: &str, lines: u32) -> Result<Vec<String>> {
        let mut cmd = self.podman_async();
        cmd.arg("logs")
            .arg("--tail")
            .arg(lines.to_string())
            .arg(name);
        
        let output = cmd
            .output()
            .await
            .context("Failed to get container logs")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to get logs: {}", stderr));
        }
        
        let logs = String::from_utf8_lossy(&output.stdout);
        Ok(logs.lines().map(|s| s.to_string()).collect())
    }
    
    pub async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
        let mut cmd = self.podman_async();
        cmd.arg("ps")
            .arg("-a")
            .arg("--format")
            .arg("json");
        
        let output = cmd
            .output()
            .await
            .context("Failed to list containers")?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            log::error!("Podman list failed: {}", stderr);
            return Err(anyhow::anyhow!("Failed to list containers: {}", stderr));
        }
        
        let json = String::from_utf8_lossy(&output.stdout);
        log::debug!("Podman JSON output ({} bytes): {}", json.len(), 
            if json.len() > 200 { &json[..200] } else { &json });
        
        // Podman can return either a JSON array or NDJSON (newline-delimited JSON)
        let mut result = Vec::new();
        
        // Try parsing as a JSON array first
        if let Ok(containers) = serde_json::from_str::<Vec<serde_json::Value>>(&json) {
            log::debug!("Parsed as JSON array with {} items", containers.len());
            for container in containers {
                // Handle both Names as array and Names as string
                let name = if let Some(names_array) = container["Names"].as_array() {
                    names_array.get(0).and_then(|v| v.as_str()).unwrap_or("").to_string()
                } else {
                    container["Names"].as_str().unwrap_or("").to_string()
                };
                
                // Parse ports from the Ports array
                let ports = if let Some(ports_array) = container["Ports"].as_array() {
                    ports_array.iter().filter_map(|port| {
                        // Podman format: {"host_ip":"","container_port":8123,"host_port":8123,"range":1,"protocol":"tcp"}
                        if let (Some(host_port), Some(container_port), Some(protocol)) = (
                            port["host_port"].as_u64(),
                            port["container_port"].as_u64(),
                            port["protocol"].as_str()
                        ) {
                            Some(format!("0.0.0.0:{}->{}/{}", host_port, container_port, protocol))
                        } else {
                            None
                        }
                    }).collect()
                } else {
                    vec![]
                };
                
                result.push(ContainerStatus {
                    id: container["Id"].as_str().unwrap_or("").to_string(),
                    name,
                    state: ContainerState::from(container["State"].as_str().unwrap_or("unknown")),
                    image: container["Image"].as_str().unwrap_or("").to_string(),
                    created: container["Created"].as_str().unwrap_or("").to_string(),
                    ports,
                    lan_address: None, // Set by docker_packages scanner
                });
            }
        } else {
            log::debug!("Failed to parse as JSON array, trying NDJSON");
            // Try parsing as NDJSON (newline-delimited JSON)
            for line in json.lines() {
                if line.trim().is_empty() {
                    continue;
                }
                
                if let Ok(container) = serde_json::from_str::<serde_json::Value>(line) {
                    // Handle both Names as array and Names as string
                    let name = if let Some(names_array) = container["Names"].as_array() {
                        names_array.get(0).and_then(|v| v.as_str()).unwrap_or("").to_string()
                    } else {
                        container["Names"].as_str().unwrap_or("").to_string()
                    };
                    
                    // Parse ports from the Ports array
                    let ports = if let Some(ports_array) = container["Ports"].as_array() {
                        ports_array.iter().filter_map(|port| {
                            if let (Some(host_port), Some(container_port), Some(protocol)) = (
                                port["host_port"].as_u64(),
                                port["container_port"].as_u64(),
                                port["protocol"].as_str()
                            ) {
                                Some(format!("0.0.0.0:{}->{}/{}", host_port, container_port, protocol))
                            } else {
                                None
                            }
                        }).collect()
                    } else {
                        vec![]
                    };
                    
                    result.push(ContainerStatus {
                        id: container["Id"].as_str().unwrap_or("").to_string(),
                        name,
                        state: ContainerState::from(container["State"].as_str().unwrap_or("unknown")),
                        image: container["Image"].as_str().unwrap_or("").to_string(),
                        created: container["Created"].as_str().unwrap_or("").to_string(),
                        ports,
                        lan_address: None, // Set by docker_packages scanner
                    });
                }
            }
        }
        
        log::debug!("Returning {} containers", result.len());
        Ok(result)
    }
}
