//! KubeBuilder: Provision build environments in Kubernetes
//!
//! This module provides functionality to provision and manage builder pods
//! in Kubernetes that have buildah installed. For actual image building,
//! use the `buildah` module with `KubectlExecutor`.
//!
//! # Features
//!
//! - Create privileged builder pods with buildah pre-installed
//! - Reuse existing builder pods for efficiency
//! - Configurable CPU, memory, and registry credentials
//!
//! # Rust Example
//!
//! ```rust,no_run
//! use herolib_virt::kubernetes::BuildMachine;
//! use herolib_virt::buildah::{Builder, KubectlExecutor};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Provision a build environment
//!     let machine = BuildMachine::new("build-ns", "my-builder")
//!         .cpu("2")
//!         .memory("4Gi");
//!
//!     let handle = machine.provision().await?;
//!
//!     // Create executor using the pod info from handle
//!     let executor = KubectlExecutor::new(handle.namespace(), handle.pod_name());
//!
//!     // Use with buildah Builder for image building
//!     let builder = Builder::with_executor("my-container", "alpine:latest", executor)?;
//!     builder.run("apk add curl")?;
//!     builder.commit("my-image:latest")?;
//!     builder.remove()?;
//!
//!     // Cleanup the build environment
//!     handle.destroy().await?;
//!     Ok(())
//! }
//! ```

use super::KubernetesManager;
use super::error::KubernetesError;
use k8s_openapi::api::core::v1::{
    Container, EmptyDirVolumeSource, Pod, PodSpec, ResourceRequirements, SecurityContext, Volume,
    VolumeMount,
};
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use kube::Client;
use kube::api::{Api, DeleteParams, PostParams};
use std::collections::BTreeMap;
use std::time::Duration;

/// Error type for kubebuilder operations
#[derive(Debug)]
pub enum KubeBuilderError {
    /// Failed to provision build machine
    ProvisionFailed(String),
    /// Build machine not ready
    NotReady(String),
    /// Command execution failed
    ExecutionFailed(String),
    /// Kubernetes error
    Kubernetes(KubernetesError),
    /// Timeout waiting for pod
    Timeout(String),
}

impl std::fmt::Display for KubeBuilderError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KubeBuilderError::ProvisionFailed(msg) => write!(f, "Provision failed: {}", msg),
            KubeBuilderError::NotReady(msg) => write!(f, "Build machine not ready: {}", msg),
            KubeBuilderError::ExecutionFailed(msg) => write!(f, "Execution failed: {}", msg),
            KubeBuilderError::Kubernetes(e) => write!(f, "Kubernetes error: {}", e),
            KubeBuilderError::Timeout(msg) => write!(f, "Timeout: {}", msg),
        }
    }
}

impl std::error::Error for KubeBuilderError {}

impl From<KubernetesError> for KubeBuilderError {
    fn from(e: KubernetesError) -> Self {
        KubeBuilderError::Kubernetes(e)
    }
}

/// Result type for kubebuilder operations
pub type KubeBuilderResult<T> = Result<T, KubeBuilderError>;

/// Build machine configuration for provisioning builder pods in Kubernetes
///
/// This struct configures and provisions a Kubernetes pod with buildah installed,
/// ready for container image building operations.
#[derive(Debug, Clone)]
pub struct BuildMachine {
    /// Kubernetes namespace
    namespace: String,
    /// Pod name
    name: String,
    /// Base image (default: ubuntu:24.04)
    image: String,
    /// CPU limit (e.g., "2")
    cpu: Option<String>,
    /// Memory limit (e.g., "4Gi")
    memory: Option<String>,
    /// Registry secret name for pulling/pushing images
    registry_secret: Option<String>,
    /// Whether the pod should be privileged (required for buildah)
    privileged: bool,
    /// Debug mode
    debug: bool,
}

impl BuildMachine {
    /// Create a new build machine configuration
    ///
    /// # Arguments
    ///
    /// * `namespace` - Kubernetes namespace for the builder pod
    /// * `name` - Name for the builder pod
    pub fn new(namespace: impl Into<String>, name: impl Into<String>) -> Self {
        Self {
            namespace: namespace.into(),
            name: name.into(),
            image: "ubuntu:24.04".to_string(),
            cpu: Some("2".to_string()),
            memory: Some("4Gi".to_string()),
            registry_secret: None,
            privileged: true,
            debug: false,
        }
    }

    /// Set the base image for the builder pod
    pub fn image(mut self, image: impl Into<String>) -> Self {
        self.image = image.into();
        self
    }

    /// Set CPU limit
    pub fn cpu(mut self, cpu: impl Into<String>) -> Self {
        self.cpu = Some(cpu.into());
        self
    }

    /// Set memory limit
    pub fn memory(mut self, memory: impl Into<String>) -> Self {
        self.memory = Some(memory.into());
        self
    }

    /// Set registry secret for pulling/pushing images
    pub fn registry_secret(mut self, secret: impl Into<String>) -> Self {
        self.registry_secret = Some(secret.into());
        self
    }

    /// Set whether the pod should be privileged
    pub fn privileged(mut self, privileged: bool) -> Self {
        self.privileged = privileged;
        self
    }

    /// Enable debug mode
    pub fn debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }

    /// Get the namespace
    pub fn namespace(&self) -> &str {
        &self.namespace
    }

    /// Get the pod name
    pub fn name(&self) -> &str {
        &self.name
    }

    // Mutable setters for Rhai property access

    /// Set the base image (mutable setter for Rhai)
    pub fn set_image(&mut self, image: impl Into<String>) {
        self.image = image.into();
    }

    /// Set CPU limit (mutable setter for Rhai)
    pub fn set_cpu(&mut self, cpu: impl Into<String>) {
        self.cpu = Some(cpu.into());
    }

    /// Set memory limit (mutable setter for Rhai)
    pub fn set_memory(&mut self, memory: impl Into<String>) {
        self.memory = Some(memory.into());
    }

    /// Set registry secret (mutable setter for Rhai)
    pub fn set_registry_secret(&mut self, secret: impl Into<String>) {
        self.registry_secret = Some(secret.into());
    }

    /// Set debug mode (mutable setter for Rhai)
    pub fn set_debug(&mut self, debug: bool) {
        self.debug = debug;
    }

    /// Provision the build machine in Kubernetes
    ///
    /// Creates a pod with buildah installed and waits for it to be ready.
    /// Returns a handle with namespace/pod_name to use with `buildah::KubectlExecutor`.
    pub async fn provision(&self) -> KubeBuilderResult<BuildMachineHandle> {
        // Use KubernetesManager for client creation and namespace management
        let km = KubernetesManager::new(&self.namespace).await?;
        km.namespace_create(&self.namespace).await?;

        let client = km.client().clone();
        let pods: Api<Pod> = Api::namespaced(client.clone(), &self.namespace);

        // Check if pod already exists and is running
        if let Ok(existing) = pods.get(&self.name).await {
            if let Some(status) = &existing.status {
                if status.phase.as_deref() == Some("Running") {
                    log::info!("Reusing existing running pod '{}'", self.name);
                    return Ok(BuildMachineHandle {
                        namespace: self.namespace.clone(),
                        pod_name: self.name.clone(),
                        client,
                    });
                }
            }
            // Pod exists but not running, delete it
            let _ = pods.delete(&self.name, &DeleteParams::default()).await;
            tokio::time::sleep(Duration::from_secs(2)).await;
        }

        // Build resource requirements
        let mut limits = BTreeMap::new();
        if let Some(cpu) = &self.cpu {
            limits.insert("cpu".to_string(), Quantity(cpu.clone()));
        }
        if let Some(memory) = &self.memory {
            limits.insert("memory".to_string(), Quantity(memory.clone()));
        }

        // Create the pod specification
        let pod = Pod {
            metadata: ObjectMeta {
                name: Some(self.name.clone()),
                namespace: Some(self.namespace.clone()),
                labels: Some(BTreeMap::from([
                    ("app".to_string(), "kubebuilder".to_string()),
                    ("builder".to_string(), self.name.clone()),
                ])),
                ..Default::default()
            },
            spec: Some(PodSpec {
                containers: vec![Container {
                    name: "builder".to_string(),
                    image: Some(self.image.clone()),
                    command: Some(vec!["/bin/bash".to_string()]),
                    args: Some(vec![
                        "-c".to_string(),
                        "apt-get update && apt-get install -y ca-certificates buildah fuse-overlayfs uidmap && sleep infinity".to_string(),
                    ]),
                    security_context: if self.privileged {
                        Some(SecurityContext {
                            privileged: Some(true),
                            ..Default::default()
                        })
                    } else {
                        None
                    },
                    volume_mounts: Some(vec![VolumeMount {
                        name: "containers".to_string(),
                        mount_path: "/var/lib/containers".to_string(),
                        ..Default::default()
                    }]),
                    resources: if !limits.is_empty() {
                        Some(ResourceRequirements {
                            limits: Some(limits.clone()),
                            requests: Some(limits),
                            ..Default::default()
                        })
                    } else {
                        None
                    },
                    ..Default::default()
                }],
                volumes: Some(vec![Volume {
                    name: "containers".to_string(),
                    empty_dir: Some(EmptyDirVolumeSource::default()),
                    ..Default::default()
                }]),
                image_pull_secrets: self.registry_secret.as_ref().map(|s| {
                    vec![k8s_openapi::api::core::v1::LocalObjectReference {
                        name: s.clone(),
                    }]
                }),
                ..Default::default()
            }),
            ..Default::default()
        };

        // Create the pod
        pods.create(&PostParams::default(), &pod)
            .await
            .map_err(|e| {
                KubeBuilderError::ProvisionFailed(format!("Failed to create pod: {}", e))
            })?;

        let handle = BuildMachineHandle {
            namespace: self.namespace.clone(),
            pod_name: self.name.clone(),
            client,
        };

        // Wait for pod to be ready
        handle.wait_ready(Duration::from_secs(300)).await?;

        // Wait for buildah installation to complete
        tokio::time::sleep(Duration::from_secs(15)).await;

        Ok(handle)
    }
}

/// Handle to a provisioned build machine
///
/// Provides namespace and pod_name for use with `buildah::KubectlExecutor`.
#[derive(Clone)]
pub struct BuildMachineHandle {
    namespace: String,
    pod_name: String,
    client: Client,
}

impl BuildMachineHandle {
    /// Get the namespace
    pub fn namespace(&self) -> &str {
        &self.namespace
    }

    /// Get the pod name
    pub fn pod_name(&self) -> &str {
        &self.pod_name
    }

    /// Wait for the pod to be ready
    async fn wait_ready(&self, timeout: Duration) -> KubeBuilderResult<()> {
        let pods: Api<Pod> = Api::namespaced(self.client.clone(), &self.namespace);
        let start = std::time::Instant::now();

        loop {
            if start.elapsed() > timeout {
                return Err(KubeBuilderError::Timeout(format!(
                    "Pod '{}' not ready after {:?}",
                    self.pod_name, timeout
                )));
            }

            match pods.get(&self.pod_name).await {
                Ok(pod) => {
                    if let Some(status) = &pod.status {
                        let phase = status.phase.as_deref().unwrap_or("Unknown");

                        if phase == "Running" {
                            if let Some(container_statuses) = &status.container_statuses {
                                let ready_count =
                                    container_statuses.iter().filter(|cs| cs.ready).count();
                                if ready_count == container_statuses.len()
                                    && !container_statuses.is_empty()
                                {
                                    return Ok(());
                                }
                            }
                        } else if phase == "Failed" {
                            return Err(KubeBuilderError::ProvisionFailed(format!(
                                "Pod '{}' failed: {:?}",
                                self.pod_name, status.message
                            )));
                        }
                    }
                }
                Err(_) => {}
            }

            tokio::time::sleep(Duration::from_secs(2)).await;
        }
    }

    /// Destroy the build machine (delete the pod)
    pub async fn destroy(&self) -> KubeBuilderResult<()> {
        let pods: Api<Pod> = Api::namespaced(self.client.clone(), &self.namespace);

        pods.delete(&self.pod_name, &DeleteParams::default())
            .await
            .map_err(|e| {
                KubeBuilderError::ExecutionFailed(format!("Failed to delete pod: {}", e))
            })?;

        Ok(())
    }
}

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

    #[test]
    fn test_build_machine_builder() {
        let machine = BuildMachine::new("test-ns", "test-builder")
            .image("ubuntu:22.04")
            .cpu("4")
            .memory("8Gi")
            .registry_secret("my-secret")
            .debug(true);

        assert_eq!(machine.namespace(), "test-ns");
        assert_eq!(machine.name(), "test-builder");
        assert_eq!(machine.image, "ubuntu:22.04");
        assert_eq!(machine.cpu, Some("4".to_string()));
        assert_eq!(machine.memory, Some("8Gi".to_string()));
        assert_eq!(machine.registry_secret, Some("my-secret".to_string()));
        assert!(machine.debug);
    }
}
