use crate::manifest::AppManifest;
use crate::podman_client::{ContainerStatus, ContainerState, PodmanClient};
use anyhow::{Context, Result};
use async_trait::async_trait;
use std::process::Command;
use tokio::process::Command as TokioCommand;

#[async_trait]
pub trait ContainerRuntime: Send + Sync {
    async fn pull_image(&self, image: &str, signature: Option<&str>) -> Result<()>;
    async fn create_container(
        &self,
        manifest: &AppManifest,
        name: &str,
        port_offset: u16,
    ) -> Result<String>;
    async fn start_container(&self, name: &str) -> Result<()>;
    async fn stop_container(&self, name: &str) -> Result<()>;
    async fn remove_container(&self, name: &str) -> Result<()>;
    async fn get_container_status(&self, name: &str) -> Result<ContainerStatus>;
    async fn get_container_logs(&self, name: &str, lines: u32) -> Result<Vec<String>>;
    async fn list_containers(&self) -> Result<Vec<ContainerStatus>>;
}

pub struct PodmanRuntime {
    client: PodmanClient,
}

impl PodmanRuntime {
    pub fn new(user: String) -> Self {
        Self {
            client: PodmanClient::new(user),
        }
    }
}

#[async_trait]
impl ContainerRuntime for PodmanRuntime {
    async fn pull_image(&self, image: &str, signature: Option<&str>) -> Result<()> {
        self.client.pull_image(image, signature).await
    }

    async fn create_container(
        &self,
        manifest: &AppManifest,
        name: &str,
        port_offset: u16,
    ) -> Result<String> {
        // Apply port offset to manifest ports
        let mut dev_manifest = manifest.clone();
        for port in &mut dev_manifest.app.ports {
            port.host = port.host + port_offset;
        }

        // PodmanClient doesn't take port_offset, so we use the modified manifest
        self.client.create_container(&dev_manifest, name).await
    }

    async fn start_container(&self, name: &str) -> Result<()> {
        self.client.start_container(name).await
    }

    async fn stop_container(&self, name: &str) -> Result<()> {
        self.client.stop_container(name).await
    }

    async fn remove_container(&self, name: &str) -> Result<()> {
        self.client.remove_container(name).await
    }

    async fn get_container_status(&self, name: &str) -> Result<ContainerStatus> {
        self.client.get_container_status(name).await
    }

    async fn get_container_logs(&self, name: &str, lines: u32) -> Result<Vec<String>> {
        self.client.get_container_logs(name, lines).await
    }

    async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
        self.client.list_containers().await
    }
}

pub struct DockerRuntime {
    _user: String,
}

impl DockerRuntime {
    pub fn new(user: String) -> Self {
        Self { _user: user }
    }

    fn docker_async(&self) -> TokioCommand {
        let mut cmd = TokioCommand::new("docker");
        // Use actual HOME environment variable instead of hardcoded /home
        if let Ok(home) = std::env::var("HOME") {
            cmd.env("HOME", home);
        }
        cmd
    }

    #[allow(dead_code)]
    fn docker_command(&self) -> Command {
        let mut cmd = Command::new("docker");
        // Use actual HOME environment variable instead of hardcoded /home
        if let Ok(home) = std::env::var("HOME") {
            cmd.env("HOME", home);
        }
        cmd
    }
}

#[async_trait]
impl ContainerRuntime for DockerRuntime {
    async fn pull_image(&self, image: &str, _signature: Option<&str>) -> Result<()> {
        let mut cmd = self.docker_async();
        cmd.arg("pull").arg(image);

        let output = cmd
            .output()
            .await
            .context("Failed to execute docker pull")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("Failed to pull image: {}", stderr));
        }

        Ok(())
    }

    async fn create_container(
        &self,
        manifest: &AppManifest,
        name: &str,
        port_offset: u16,
    ) -> Result<String> {
        let mut cmd = self.docker_async();
        cmd.arg("create");

        cmd.arg("--name").arg(name);

        if manifest.app.security.readonly_root {
            cmd.arg("--read-only");
        }

        match manifest.app.security.network_policy.as_str() {
            "host" => {
                cmd.arg("--network").arg("host");
            }
            "isolated" => {
                // Docker uses bridge network by default
            }
            _ => {
                cmd.arg("--network").arg(&manifest.app.security.network_policy);
            }
        }

        // Port mappings with offset
        for port in &manifest.app.ports {
            let host_port = port.host + port_offset;
            cmd.arg("-p").arg(format!("{}:{}", host_port, 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
        cmd.arg("--cap-drop").arg("ALL");
        for cap in &manifest.app.security.capabilities {
            cmd.arg("--cap-add").arg(cap);
        }

        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)
    }

    async fn start_container(&self, name: &str) -> Result<()> {
        let mut cmd = self.docker_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(())
    }

    async fn stop_container(&self, name: &str) -> Result<()> {
        let mut cmd = self.docker_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(())
    }

    async fn remove_container(&self, name: &str) -> Result<()> {
        let mut cmd = self.docker_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(())
    }

    async fn get_container_status(&self, name: &str) -> Result<ContainerStatus> {
        let mut cmd = self.docker_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: crate::podman_client::ContainerState::from(parts[2]),
            image: parts[3].to_string(),
            created: parts[4].to_string(),
            ports: vec![],
            lan_address: None,
        })
    }

    async fn get_container_logs(&self, name: &str, lines: u32) -> Result<Vec<String>> {
        let mut cmd = self.docker_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())
    }

    async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
        let mut cmd = self.docker_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);
            return Err(anyhow::anyhow!("Failed to list containers: {}", stderr));
        }

        let json = String::from_utf8_lossy(&output.stdout);
        let mut result = Vec::new();
        
        // Docker returns NDJSON (newline-delimited JSON), not a JSON array
        for line in json.lines() {
            if line.trim().is_empty() {
                continue;
            }
            
            let container: serde_json::Value = serde_json::from_str(line)
                .context(format!("Failed to parse container JSON: {}", line))?;
            
            // Extract ports from JSON
            let ports_value = &container["Ports"];
            let ports_str = ports_value.as_str().unwrap_or("");
            let ports: Vec<String> = if !ports_str.is_empty() {
                ports_str.split(", ").map(|s| s.to_string()).collect()
            } else {
                vec![]
            };
            
            result.push(ContainerStatus {
                id: container["ID"].as_str().unwrap_or("").to_string(),
                name: container["Names"].as_str().unwrap_or("").to_string(),
                state: ContainerState::from(
                    container["State"].as_str().unwrap_or("unknown")
                ),
                image: container["Image"].as_str().unwrap_or("").to_string(),
                created: container["CreatedAt"].as_str().unwrap_or("").to_string(),
                ports,
                lan_address: None,
            });
        }

        Ok(result)
    }
}

pub struct AutoRuntime {
    runtime: Box<dyn ContainerRuntime>,
}

impl AutoRuntime {
    pub async fn new(user: String) -> Result<Self> {
        // Try Podman first
        if Self::check_podman_available() {
            Ok(Self {
                runtime: Box::new(PodmanRuntime::new(user)),
            })
        } else if Self::check_docker_available() {
            Ok(Self {
                runtime: Box::new(DockerRuntime::new(user)),
            })
        } else {
            Err(anyhow::anyhow!(
                "Neither Podman nor Docker is available"
            ))
        }
    }

    fn check_podman_available() -> bool {
        Command::new("podman")
            .arg("--version")
            .output()
            .is_ok()
    }

    fn check_docker_available() -> bool {
        Command::new("docker")
            .arg("--version")
            .output()
            .is_ok()
    }
}

#[async_trait]
impl ContainerRuntime for AutoRuntime {
    async fn pull_image(&self, image: &str, signature: Option<&str>) -> Result<()> {
        self.runtime.pull_image(image, signature).await
    }

    async fn create_container(
        &self,
        manifest: &AppManifest,
        name: &str,
        port_offset: u16,
    ) -> Result<String> {
        self.runtime.create_container(manifest, name, port_offset).await
    }

    async fn start_container(&self, name: &str) -> Result<()> {
        self.runtime.start_container(name).await
    }

    async fn stop_container(&self, name: &str) -> Result<()> {
        self.runtime.stop_container(name).await
    }

    async fn remove_container(&self, name: &str) -> Result<()> {
        self.runtime.remove_container(name).await
    }

    async fn get_container_status(&self, name: &str) -> Result<ContainerStatus> {
        self.runtime.get_container_status(name).await
    }

    async fn get_container_logs(&self, name: &str, lines: u32) -> Result<Vec<String>> {
        self.runtime.get_container_logs(name, lines).await
    }

    async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
        self.runtime.list_containers().await
    }
}

// Runtime factory functions will be provided by the archipelago crate
// that imports this library and has access to Config
