//! ConfigMap manager implementation.

use crate::configmap::ConfigMapSpec;
use crate::error::{Error, Result};
use k8s_openapi::ByteString;
use k8s_openapi::api::core::v1::ConfigMap;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use kube::Client as KubeClient;
use kube::api::{Api, DeleteParams, ListParams, Patch, PatchParams};
use std::collections::BTreeMap;

/// Manager for Kubernetes ConfigMap operations.
///
/// This manager provides methods to create, update, delete, and retrieve ConfigMaps
/// in a Kubernetes cluster.
pub struct ConfigMapManager {
    api: Api<ConfigMap>,
    namespace: String,
}

impl ConfigMapManager {
    /// Creates a new ConfigMapManager.
    ///
    /// # Arguments
    ///
    /// * `client` - The Kubernetes client
    /// * `namespace` - The namespace to operate in
    pub fn new(client: KubeClient, namespace: impl Into<String>) -> Self {
        let namespace = namespace.into();
        let api = Api::namespaced(client, &namespace);
        Self { api, namespace }
    }

    /// Creates or updates a ConfigMap using server-side apply.
    ///
    /// # Arguments
    ///
    /// * `spec` - The ConfigMap specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated ConfigMap name on success.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig, ConfigMapSpec};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let configmap = ConfigMapSpec::new("app-config")
    ///     .with_data("database.url", "postgres://localhost:5432/mydb");
    ///
    /// client.configmaps().apply(&configmap).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn apply(&self, spec: &ConfigMapSpec) -> Result<String> {
        // Validate spec
        spec.validate().map_err(Error::ValidationError)?;

        let configmap = self.spec_to_k8s(spec)?;
        let name = spec.name();

        let params = PatchParams::apply("kubernetes-manager").force();
        let patch = Patch::Apply(&configmap);

        self.api.patch(name, &params, &patch).await.map_err(|e| {
            Error::ConfigMapError(format!("Failed to apply ConfigMap '{}': {}", name, e))
        })?;

        Ok(name.to_string())
    }

    /// Gets a ConfigMap by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The ConfigMap name
    ///
    /// # Returns
    ///
    /// Returns the ConfigMap if found.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let configmap = client.configmaps().get("app-config").await?;
    /// println!("ConfigMap: {:?}", configmap.metadata.name);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get(&self, name: &str) -> Result<ConfigMap> {
        self.api
            .get(name)
            .await
            .map_err(|_| Error::ResourceNotFound {
                kind: "ConfigMap".to_string(),
                name: name.to_string(),
                namespace: self.namespace.clone(),
            })
    }

    /// Lists all ConfigMaps in the namespace.
    ///
    /// # Returns
    ///
    /// Returns a vector of ConfigMaps.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let configmaps = client.configmaps().list().await?;
    /// println!("Found {} ConfigMaps", configmaps.len());
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list(&self) -> Result<Vec<ConfigMap>> {
        let lp = ListParams::default();
        let list = self
            .api
            .list(&lp)
            .await
            .map_err(|e| Error::ConfigMapError(format!("Failed to list ConfigMaps: {}", e)))?;

        Ok(list.items)
    }

    /// Lists ConfigMaps matching the given label selector.
    ///
    /// # Arguments
    ///
    /// * `label_selector` - The label selector (e.g., "app=myapp,env=prod")
    ///
    /// # Returns
    ///
    /// Returns a vector of matching ConfigMaps.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let configmaps = client.configmaps()
    ///     .list_by_label("app=myapp")
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list_by_label(&self, label_selector: &str) -> Result<Vec<ConfigMap>> {
        let lp = ListParams::default().labels(label_selector);
        let list = self
            .api
            .list(&lp)
            .await
            .map_err(|e| Error::ConfigMapError(format!("Failed to list ConfigMaps: {}", e)))?;

        Ok(list.items)
    }

    /// Deletes a ConfigMap by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The ConfigMap name
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// client.configmaps().delete("app-config").await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn delete(&self, name: &str) -> Result<()> {
        let dp = DeleteParams::default();
        self.api.delete(name, &dp).await.map_err(|e| {
            Error::ConfigMapError(format!("Failed to delete ConfigMap '{}': {}", name, e))
        })?;

        Ok(())
    }

    /// Checks if a ConfigMap exists.
    ///
    /// # Arguments
    ///
    /// * `name` - The ConfigMap name
    ///
    /// # Returns
    ///
    /// Returns `true` if the ConfigMap exists, `false` otherwise.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// if client.configmaps().exists("app-config").await? {
    ///     println!("ConfigMap exists");
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn exists(&self, name: &str) -> Result<bool> {
        match self.get(name).await {
            Ok(_) => Ok(true),
            Err(Error::ResourceNotFound { .. }) => Ok(false),
            Err(e) => Err(e),
        }
    }

    /// Converts ConfigMapSpec to Kubernetes API ConfigMap.
    fn spec_to_k8s(&self, spec: &ConfigMapSpec) -> Result<ConfigMap> {
        let mut labels = BTreeMap::new();
        for (k, v) in spec.labels() {
            labels.insert(k.clone(), v.clone());
        }

        let mut data = BTreeMap::new();
        for (k, v) in spec.data() {
            data.insert(k.clone(), v.clone());
        }

        let mut binary_data = BTreeMap::new();
        for (k, v) in spec.binary_data() {
            binary_data.insert(k.clone(), ByteString(v.clone()));
        }

        Ok(ConfigMap {
            metadata: ObjectMeta {
                name: Some(spec.name().to_string()),
                namespace: Some(self.namespace.clone()),
                labels: if labels.is_empty() {
                    None
                } else {
                    Some(labels)
                },
                ..Default::default()
            },
            data: if data.is_empty() { None } else { Some(data) },
            binary_data: if binary_data.is_empty() {
                None
            } else {
                Some(binary_data)
            },
            immutable: if spec.is_immutable() {
                Some(true)
            } else {
                None
            },
        })
    }
}
