//! Ingress manager implementation.

use crate::error::{Error, Result};
use crate::ingress::{IngressSpec, PathType};
use k8s_openapi::api::networking::v1::{
    HTTPIngressPath, HTTPIngressRuleValue, Ingress, IngressBackend, IngressRule,
    IngressServiceBackend, IngressTLS, ServiceBackendPort,
};
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 Ingress operations.
///
/// This manager provides methods to create, update, delete, and retrieve Ingress resources
/// in a Kubernetes cluster.
pub struct IngressManager {
    api: Api<Ingress>,
    namespace: String,
}

impl IngressManager {
    /// Creates a new IngressManager.
    ///
    /// # 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 an Ingress using server-side apply.
    ///
    /// # Arguments
    ///
    /// * `spec` - The Ingress specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated Ingress name on success.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    /// use lmrc_kubernetes::ingress::{IngressSpec, IngressRule, IngressPath};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_ingress_class("nginx")
    ///     .with_rule(
    ///         IngressRule::new()
    ///             .with_host("example.com")
    ///             .with_path(IngressPath::new("/", "web-service", 80))
    ///     );
    ///
    /// client.ingresses().apply(&ingress).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn apply(&self, spec: &IngressSpec) -> Result<String> {
        // Validate spec
        spec.validate().map_err(Error::ValidationError)?;

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

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

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

        Ok(name.to_string())
    }

    /// Gets an Ingress by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The Ingress name
    ///
    /// # Returns
    ///
    /// Returns the Ingress 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 ingress = client.ingresses().get("my-ingress").await?;
    /// println!("Ingress: {:?}", ingress.metadata.name);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get(&self, name: &str) -> Result<Ingress> {
        self.api
            .get(name)
            .await
            .map_err(|_| Error::ResourceNotFound {
                kind: "Ingress".to_string(),
                name: name.to_string(),
                namespace: self.namespace.clone(),
            })
    }

    /// Lists all Ingresses in the namespace.
    ///
    /// # Returns
    ///
    /// Returns a vector of Ingresses.
    ///
    /// # 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 ingresses = client.ingresses().list().await?;
    /// println!("Found {} Ingresses", ingresses.len());
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list(&self) -> Result<Vec<Ingress>> {
        let lp = ListParams::default();
        let list = self
            .api
            .list(&lp)
            .await
            .map_err(|e| Error::IngressError(format!("Failed to list Ingresses: {}", e)))?;

        Ok(list.items)
    }

    /// Lists Ingresses matching the given label selector.
    ///
    /// # Arguments
    ///
    /// * `label_selector` - The label selector (e.g., "app=myapp")
    ///
    /// # Returns
    ///
    /// Returns a vector of matching Ingresses.
    pub async fn list_by_label(&self, label_selector: &str) -> Result<Vec<Ingress>> {
        let lp = ListParams::default().labels(label_selector);
        let list = self
            .api
            .list(&lp)
            .await
            .map_err(|e| Error::IngressError(format!("Failed to list Ingresses: {}", e)))?;

        Ok(list.items)
    }

    /// Deletes an Ingress by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The Ingress 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.ingresses().delete("my-ingress").await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn delete(&self, name: &str) -> Result<()> {
        let dp = DeleteParams::default();
        self.api.delete(name, &dp).await.map_err(|e| {
            Error::IngressError(format!("Failed to delete Ingress '{}': {}", name, e))
        })?;

        Ok(())
    }

    /// Checks if an Ingress exists.
    ///
    /// # Arguments
    ///
    /// * `name` - The Ingress name
    ///
    /// # Returns
    ///
    /// Returns `true` if the Ingress exists, `false` otherwise.
    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 IngressSpec to Kubernetes API Ingress.
    fn spec_to_k8s(&self, spec: &IngressSpec) -> Result<Ingress> {
        let mut labels = BTreeMap::new();
        for (k, v) in spec.labels() {
            labels.insert(k.clone(), v.clone());
        }

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

        // Convert rules
        let rules: Vec<IngressRule> = spec
            .rules()
            .iter()
            .map(|rule| {
                let paths: Vec<HTTPIngressPath> = rule
                    .paths
                    .iter()
                    .map(|p| HTTPIngressPath {
                        path: Some(p.path.clone()),
                        path_type: match p.path_type {
                            PathType::Exact => "Exact".to_string(),
                            PathType::Prefix => "Prefix".to_string(),
                            PathType::ImplementationSpecific => {
                                "ImplementationSpecific".to_string()
                            }
                        },
                        backend: IngressBackend {
                            service: Some(IngressServiceBackend {
                                name: p.service_name.clone(),
                                port: Some(ServiceBackendPort {
                                    number: Some(p.service_port),
                                    ..Default::default()
                                }),
                            }),
                            ..Default::default()
                        },
                    })
                    .collect();

                IngressRule {
                    host: rule.host.clone(),
                    http: Some(HTTPIngressRuleValue { paths }),
                }
            })
            .collect();

        // Convert TLS
        let tls: Vec<IngressTLS> = spec
            .tls_configs()
            .iter()
            .map(|t| IngressTLS {
                hosts: if t.hosts.is_empty() {
                    None
                } else {
                    Some(t.hosts.clone())
                },
                secret_name: Some(t.secret_name.clone()),
            })
            .collect();

        Ok(Ingress {
            metadata: ObjectMeta {
                name: Some(spec.name().to_string()),
                namespace: Some(self.namespace.clone()),
                labels: if labels.is_empty() {
                    None
                } else {
                    Some(labels)
                },
                annotations: if annotations.is_empty() {
                    None
                } else {
                    Some(annotations)
                },
                ..Default::default()
            },
            spec: Some(k8s_openapi::api::networking::v1::IngressSpec {
                ingress_class_name: spec.ingress_class().map(|s| s.to_string()),
                rules: if rules.is_empty() { None } else { Some(rules) },
                tls: if tls.is_empty() { None } else { Some(tls) },
                ..Default::default()
            }),
            ..Default::default()
        })
    }
}
