//! PersistentVolumeClaim manager implementation.

use crate::error::{Error, Result};
use crate::pvc::PersistentVolumeClaimSpec;
use k8s_openapi::api::core::v1::{
    PersistentVolumeClaim, PersistentVolumeClaimSpec as K8sPvcSpec, VolumeResourceRequirements,
};
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{LabelSelector, ObjectMeta};
use kube::Client as KubeClient;
use kube::api::{Api, DeleteParams, ListParams, Patch, PatchParams};
use std::collections::BTreeMap;

/// Manager for Kubernetes PersistentVolumeClaim operations.
///
/// This manager provides methods to create, update, delete, and retrieve PVCs.
#[derive(Clone)]
pub struct PvcManager {
    client: KubeClient,
    namespace: String,
    field_manager: String,
}

impl PvcManager {
    /// Create a new PvcManager.
    pub(crate) fn new(client: KubeClient, namespace: String, field_manager: String) -> Self {
        Self {
            client,
            namespace,
            field_manager,
        }
    }

    /// Apply a PVC to the cluster (create or update).
    ///
    /// # Arguments
    ///
    /// * `spec` - The PVC specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated PVC.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_kubernetes::{Client, ClientConfig};
    /// # use lmrc_kubernetes::pvc::{PersistentVolumeClaimSpec, AccessMode};
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// # let config = ClientConfig::infer().await?;
    /// # let client = Client::new(config, "default").await?;
    /// let pvc = PersistentVolumeClaimSpec::new("data-volume", "10Gi")
    ///     .with_access_mode(AccessMode::ReadWriteOnce);
    ///
    /// client.pvcs().apply(&pvc).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn apply(&self, spec: &PersistentVolumeClaimSpec) -> Result<PersistentVolumeClaim> {
        spec.validate()
            .map_err(|e| Error::ValidationError(e.to_string()))?;

        let api: Api<PersistentVolumeClaim> = Api::namespaced(self.client.clone(), &self.namespace);

        let pvc = self.build_pvc(spec);

        let patch_params = PatchParams::apply(&self.field_manager).force();
        let patch = Patch::Apply(&pvc);

        api.patch(spec.name(), &patch_params, &patch)
            .await
            .map_err(|e| Error::pvc(e.to_string()))
    }

    /// Get a PVC by name.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the PVC
    ///
    /// # Returns
    ///
    /// Returns the PVC if found.
    pub async fn get(&self, name: &str) -> Result<PersistentVolumeClaim> {
        let api: Api<PersistentVolumeClaim> = Api::namespaced(self.client.clone(), &self.namespace);

        api.get(name).await.map_err(|_| Error::ResourceNotFound {
            kind: "PersistentVolumeClaim".to_string(),
            name: name.to_string(),
            namespace: self.namespace.clone(),
        })
    }

    /// List all PVCs in the namespace.
    pub async fn list(&self) -> Result<Vec<PersistentVolumeClaim>> {
        let api: Api<PersistentVolumeClaim> = Api::namespaced(self.client.clone(), &self.namespace);

        let list_params = ListParams::default();
        let pvcs = api
            .list(&list_params)
            .await
            .map_err(|e| Error::pvc(e.to_string()))?;

        Ok(pvcs.items)
    }

    /// List PVCs by label selector.
    ///
    /// # Arguments
    ///
    /// * `selector` - Label selector (e.g., "app=myapp,tier=database")
    pub async fn list_by_label(&self, selector: &str) -> Result<Vec<PersistentVolumeClaim>> {
        let api: Api<PersistentVolumeClaim> = Api::namespaced(self.client.clone(), &self.namespace);

        let list_params = ListParams::default().labels(selector);
        let pvcs = api
            .list(&list_params)
            .await
            .map_err(|e| Error::pvc(e.to_string()))?;

        Ok(pvcs.items)
    }

    /// Delete a PVC by name.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the PVC to delete
    pub async fn delete(&self, name: &str) -> Result<()> {
        let api: Api<PersistentVolumeClaim> = Api::namespaced(self.client.clone(), &self.namespace);

        let delete_params = DeleteParams::default();
        api.delete(name, &delete_params)
            .await
            .map_err(|e| Error::pvc(e.to_string()))?;

        Ok(())
    }

    /// Check if a PVC exists.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the PVC
    pub async fn exists(&self, name: &str) -> Result<bool> {
        let api: Api<PersistentVolumeClaim> = Api::namespaced(self.client.clone(), &self.namespace);
        Ok(api.get(name).await.is_ok())
    }

    /// Build a Kubernetes PVC from our spec.
    fn build_pvc(&self, spec: &PersistentVolumeClaimSpec) -> PersistentVolumeClaim {
        let mut labels = BTreeMap::new();
        for (k, v) in spec.labels() {
            labels.insert(k.clone(), v.clone());
        }

        let access_modes = spec
            .access_modes()
            .iter()
            .map(|mode| mode.to_k8s_string().to_string())
            .collect();

        let mut requests = BTreeMap::new();
        requests.insert("storage".to_string(), Quantity(spec.storage().to_string()));

        let resources = VolumeResourceRequirements {
            requests: Some(requests),
            ..Default::default()
        };

        let selector = spec.selector().map(|sel| {
            let mut match_labels = BTreeMap::new();
            for (k, v) in sel {
                match_labels.insert(k.clone(), v.clone());
            }
            LabelSelector {
                match_labels: Some(match_labels),
                ..Default::default()
            }
        });

        let pvc_spec = K8sPvcSpec {
            access_modes: Some(access_modes),
            resources: Some(resources),
            storage_class_name: spec.storage_class().name().map(String::from),
            selector,
            volume_name: spec.volume_name().map(String::from),
            ..Default::default()
        };

        PersistentVolumeClaim {
            metadata: ObjectMeta {
                name: Some(spec.name().to_string()),
                labels: Some(labels),
                ..Default::default()
            },
            spec: Some(pvc_spec),
            ..Default::default()
        }
    }
}
