//! HorizontalPodAutoscaler manager implementation.

use crate::error::{Error, Result};
use crate::hpa::{HpaSpec, MetricTarget, MetricType};
use k8s_openapi::api::autoscaling::v2::{
    CrossVersionObjectReference, HorizontalPodAutoscaler,
    HorizontalPodAutoscalerSpec as K8sHpaSpec, MetricSpec, MetricTarget as K8sMetricTarget,
    ObjectMetricSource, PodsMetricSource, ResourceMetricSource,
};
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
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 HorizontalPodAutoscaler operations.
///
/// This manager provides methods to create, update, delete, and retrieve HPAs.
#[derive(Clone)]
pub struct HpaManager {
    client: KubeClient,
    namespace: String,
    field_manager: String,
}

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

    /// Apply an HPA to the cluster (create or update).
    ///
    /// # Arguments
    ///
    /// * `spec` - The HPA specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated HPA.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_kubernetes::{Client, ClientConfig};
    /// # use lmrc_kubernetes::hpa::{HpaSpec, ScaleTargetRef, MetricType};
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// # let config = ClientConfig::infer().await?;
    /// # let client = Client::new(config, "default").await?;
    /// let hpa = HpaSpec::new("web-hpa", ScaleTargetRef::deployment("web-app"))
    ///     .with_min_replicas(2)
    ///     .with_max_replicas(10)
    ///     .with_metric(MetricType::cpu_utilization(80));
    ///
    /// client.hpas().apply(&hpa).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn apply(&self, spec: &HpaSpec) -> Result<HorizontalPodAutoscaler> {
        spec.validate()
            .map_err(|e| Error::ValidationError(e.to_string()))?;

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

        let hpa = self.build_hpa(spec);

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

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

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

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

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

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

        Ok(hpas.items)
    }

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

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

        Ok(hpas.items)
    }

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

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

        Ok(())
    }

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

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

        let scale_target_ref = CrossVersionObjectReference {
            api_version: Some(spec.scale_target_ref().api_version.clone()),
            kind: spec.scale_target_ref().kind.clone(),
            name: spec.scale_target_ref().name.clone(),
        };

        let metrics: Vec<MetricSpec> = spec
            .metrics()
            .iter()
            .filter_map(|metric| self.build_metric_spec(metric))
            .collect();

        let hpa_spec = K8sHpaSpec {
            scale_target_ref,
            min_replicas: spec.min_replicas(),
            max_replicas: spec.max_replicas(),
            metrics: Some(metrics),
            ..Default::default()
        };

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

    /// Build a metric spec from our metric type.
    fn build_metric_spec(&self, metric: &MetricType) -> Option<MetricSpec> {
        match metric {
            MetricType::Resource { name, target } => {
                let k8s_target = self.build_metric_target(target);
                Some(MetricSpec {
                    type_: "Resource".to_string(),
                    resource: Some(ResourceMetricSource {
                        name: name.clone(),
                        target: k8s_target,
                    }),
                    ..Default::default()
                })
            }
            MetricType::Pods {
                metric_name,
                target_average_value,
            } => {
                let target = K8sMetricTarget {
                    type_: "AverageValue".to_string(),
                    average_value: Some(Quantity(target_average_value.clone())),
                    ..Default::default()
                };

                Some(MetricSpec {
                    type_: "Pods".to_string(),
                    pods: Some(PodsMetricSource {
                        metric: k8s_openapi::api::autoscaling::v2::MetricIdentifier {
                            name: metric_name.clone(),
                            selector: None,
                        },
                        target,
                    }),
                    ..Default::default()
                })
            }
            MetricType::Object {
                metric_name,
                target,
                target_value,
            } => {
                let described_object = CrossVersionObjectReference {
                    api_version: Some(target.api_version.clone()),
                    kind: target.kind.clone(),
                    name: target.name.clone(),
                };

                let metric_target = K8sMetricTarget {
                    type_: "Value".to_string(),
                    value: Some(Quantity(target_value.clone())),
                    ..Default::default()
                };

                Some(MetricSpec {
                    type_: "Object".to_string(),
                    object: Some(ObjectMetricSource {
                        described_object,
                        metric: k8s_openapi::api::autoscaling::v2::MetricIdentifier {
                            name: metric_name.clone(),
                            selector: None,
                        },
                        target: metric_target,
                    }),
                    ..Default::default()
                })
            }
            MetricType::External {
                metric_name,
                target_value,
            } => {
                let target = K8sMetricTarget {
                    type_: "Value".to_string(),
                    value: Some(Quantity(target_value.clone())),
                    ..Default::default()
                };

                Some(MetricSpec {
                    type_: "External".to_string(),
                    external: Some(k8s_openapi::api::autoscaling::v2::ExternalMetricSource {
                        metric: k8s_openapi::api::autoscaling::v2::MetricIdentifier {
                            name: metric_name.clone(),
                            selector: None,
                        },
                        target,
                    }),
                    ..Default::default()
                })
            }
        }
    }

    /// Build a Kubernetes metric target from our metric target.
    fn build_metric_target(&self, target: &MetricTarget) -> K8sMetricTarget {
        match target {
            MetricTarget::Utilization(percent) => K8sMetricTarget {
                type_: "Utilization".to_string(),
                average_utilization: Some(*percent),
                ..Default::default()
            },
            MetricTarget::AverageValue(value) => K8sMetricTarget {
                type_: "AverageValue".to_string(),
                average_value: Some(Quantity(value.clone())),
                ..Default::default()
            },
            MetricTarget::Value(value) => K8sMetricTarget {
                type_: "Value".to_string(),
                value: Some(Quantity(value.clone())),
                ..Default::default()
            },
        }
    }
}
