//! Configuration types for the Kubernetes manager.
//!
//! This module provides configuration options for connecting to Kubernetes clusters
//! and customizing behavior of the library.

use crate::error::{Error, Result};
use kube::{Client, Config as KubeConfig};
use std::path::Path;

/// Configuration for the Kubernetes client.
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::ClientConfig;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// // Use default kubeconfig
/// let config = ClientConfig::infer().await?;
///
/// // Use specific kubeconfig file
/// let config = ClientConfig::from_kubeconfig("/path/to/kubeconfig").await?;
///
/// // Use in-cluster configuration (when running inside a pod)
/// let config = ClientConfig::in_cluster()?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct ClientConfig {
    pub(crate) kube_config: KubeConfig,
    pub(crate) field_manager: String,
}

impl ClientConfig {
    /// Create configuration by inferring from environment.
    ///
    /// This will try to load configuration in the following order:
    /// 1. In-cluster environment (service account token)
    /// 2. `KUBECONFIG` environment variable
    /// 3. `~/.kube/config` file
    ///
    /// # Errors
    ///
    /// Returns an error if no valid configuration can be found.
    pub async fn infer() -> Result<Self> {
        let kube_config = KubeConfig::infer()
            .await
            .map_err(|e| Error::config(format!("Failed to infer kubeconfig: {}", e)))?;

        Ok(Self {
            kube_config,
            field_manager: "kubernetes-manager".to_string(),
        })
    }

    /// Create configuration from a specific kubeconfig file.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the kubeconfig file
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be read or parsed.
    pub async fn from_kubeconfig<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        let kubeconfig = kube::config::Kubeconfig::read_from(path).map_err(|e| {
            Error::config(format!("Failed to read kubeconfig from {:?}: {}", path, e))
        })?;

        let kube_config = KubeConfig::from_custom_kubeconfig(kubeconfig, &Default::default())
            .await
            .map_err(|e| Error::config(format!("Failed to load kubeconfig: {}", e)))?;

        Ok(Self {
            kube_config,
            field_manager: "kubernetes-manager".to_string(),
        })
    }

    /// Create configuration for in-cluster usage.
    ///
    /// This should be used when the code is running inside a Kubernetes pod
    /// and needs to use the pod's service account.
    ///
    /// # Errors
    ///
    /// Returns an error if in-cluster configuration is not available.
    pub fn in_cluster() -> Result<Self> {
        let kube_config = KubeConfig::incluster_env()
            .map_err(|e| Error::config(format!("Failed to load in-cluster config: {}", e)))?;

        Ok(Self {
            kube_config,
            field_manager: "kubernetes-manager".to_string(),
        })
    }

    /// Set a custom field manager name.
    ///
    /// The field manager is used for server-side apply operations to track
    /// which tool is managing which fields. Default is "kubernetes-manager".
    ///
    /// # Arguments
    ///
    /// * `name` - The field manager name to use
    pub fn with_field_manager<S: Into<String>>(mut self, name: S) -> Self {
        self.field_manager = name.into();
        self
    }

    /// Create a Kubernetes client from this configuration.
    pub(crate) fn build_client(&self) -> Result<Client> {
        Client::try_from(self.kube_config.clone())
            .map_err(|e| Error::connection(format!("Failed to create Kubernetes client: {}", e)))
    }

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

/// Options for deployment operations.
#[derive(Debug, Clone)]
pub struct DeploymentOptions {
    /// Timeout in seconds for waiting for deployment to be ready.
    pub timeout_secs: u64,
    /// Whether to wait for the deployment to be ready.
    pub wait: bool,
    /// Whether to force the operation (for server-side apply).
    pub force: bool,
}

impl Default for DeploymentOptions {
    fn default() -> Self {
        Self {
            timeout_secs: 300,
            wait: true,
            force: true,
        }
    }
}

impl DeploymentOptions {
    /// Create new deployment options with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the timeout for waiting for deployment to be ready.
    pub fn timeout_secs(mut self, secs: u64) -> Self {
        self.timeout_secs = secs;
        self
    }

    /// Set whether to wait for the deployment to be ready.
    pub fn wait(mut self, wait: bool) -> Self {
        self.wait = wait;
        self
    }

    /// Set whether to force the operation.
    pub fn force(mut self, force: bool) -> Self {
        self.force = force;
        self
    }
}

/// Options for rollback operations.
#[derive(Debug, Clone)]
pub struct RollbackOptions {
    /// Revision to roll back to. If None, rolls back to previous revision.
    pub revision: Option<i64>,
    /// Timeout in seconds for waiting for rollback to complete.
    pub timeout_secs: u64,
}

impl Default for RollbackOptions {
    fn default() -> Self {
        Self {
            revision: None,
            timeout_secs: 300,
        }
    }
}

impl RollbackOptions {
    /// Create new rollback options with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the revision to roll back to.
    pub fn revision(mut self, rev: i64) -> Self {
        self.revision = Some(rev);
        self
    }

    /// Set the timeout for waiting for rollback to complete.
    pub fn timeout_secs(mut self, secs: u64) -> Self {
        self.timeout_secs = secs;
        self
    }
}

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

    #[test]
    fn test_deployment_options_builder() {
        let opts = DeploymentOptions::new()
            .timeout_secs(600)
            .wait(false)
            .force(false);

        assert_eq!(opts.timeout_secs, 600);
        assert!(!opts.wait);
        assert!(!opts.force);
    }

    #[test]
    fn test_deployment_options_defaults() {
        let opts = DeploymentOptions::default();
        assert_eq!(opts.timeout_secs, 300);
        assert!(opts.wait);
        assert!(opts.force);
    }

    #[test]
    fn test_rollback_options_builder() {
        let opts = RollbackOptions::new().revision(5).timeout_secs(180);

        assert_eq!(opts.revision, Some(5));
        assert_eq!(opts.timeout_secs, 180);
    }

    #[test]
    fn test_client_config_field_manager() {
        // We can't test the actual config creation without a cluster,
        // but we can test the field manager setting would work
        let field_manager = "custom-manager";
        assert_eq!(field_manager, "custom-manager");
    }
}
