//! NetworkPolicy manager implementation.

use crate::error::{Error, Result};
use crate::networkpolicy::{NetworkPolicySpec, PolicyType};
use k8s_openapi::api::networking::v1::{
    IPBlock, NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyIngressRule, NetworkPolicyPeer,
    NetworkPolicyPort, NetworkPolicySpec as K8sNetworkPolicySpec,
};
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{LabelSelector, ObjectMeta};
use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;
use kube::Client as KubeClient;
use kube::api::{Api, DeleteParams, ListParams, Patch, PatchParams};
use std::collections::BTreeMap;

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

impl NetworkPolicyManager {
    /// Creates a new NetworkPolicyManager.
    ///
    /// # 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 NetworkPolicy using server-side apply.
    ///
    /// # Arguments
    ///
    /// * `spec` - The NetworkPolicy specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated NetworkPolicy name on success.
    pub async fn apply(&self, spec: &NetworkPolicySpec) -> Result<String> {
        // Validate spec
        spec.validate().map_err(Error::ValidationError)?;

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

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

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

        Ok(name.to_string())
    }

    /// Gets a NetworkPolicy by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The NetworkPolicy name
    ///
    /// # Returns
    ///
    /// Returns the NetworkPolicy if found.
    pub async fn get(&self, name: &str) -> Result<NetworkPolicy> {
        self.api
            .get(name)
            .await
            .map_err(|_| Error::ResourceNotFound {
                kind: "NetworkPolicy".to_string(),
                name: name.to_string(),
                namespace: self.namespace.clone(),
            })
    }

    /// Lists all NetworkPolicies in the namespace.
    ///
    /// # Returns
    ///
    /// Returns a vector of NetworkPolicies.
    pub async fn list(&self) -> Result<Vec<NetworkPolicy>> {
        let lp = ListParams::default();
        let list = self.api.list(&lp).await.map_err(|e| {
            Error::NetworkPolicyError(format!("Failed to list NetworkPolicies: {}", e))
        })?;

        Ok(list.items)
    }

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

        Ok(list.items)
    }

    /// Deletes a NetworkPolicy by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The NetworkPolicy name
    pub async fn delete(&self, name: &str) -> Result<()> {
        let dp = DeleteParams::default();
        self.api.delete(name, &dp).await.map_err(|e| {
            Error::NetworkPolicyError(format!("Failed to delete NetworkPolicy '{}': {}", name, e))
        })?;

        Ok(())
    }

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

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

        // Convert ingress rules
        let ingress = if !spec.ingress_rules().is_empty() {
            Some(
                spec.ingress_rules()
                    .iter()
                    .map(|rule| {
                        let from: Vec<NetworkPolicyPeer> = rule
                            .peers
                            .iter()
                            .map(|peer| {
                                let mut k8s_peer = NetworkPolicyPeer::default();

                                if let Some(ref pod_sel) = peer.pod_selector {
                                    let mut sel = BTreeMap::new();
                                    for (k, v) in pod_sel {
                                        sel.insert(k.clone(), v.clone());
                                    }
                                    k8s_peer.pod_selector = Some(LabelSelector {
                                        match_labels: Some(sel),
                                        ..Default::default()
                                    });
                                }

                                if let Some(ref ns_sel) = peer.namespace_selector {
                                    let mut sel = BTreeMap::new();
                                    for (k, v) in ns_sel {
                                        sel.insert(k.clone(), v.clone());
                                    }
                                    k8s_peer.namespace_selector = Some(LabelSelector {
                                        match_labels: Some(sel),
                                        ..Default::default()
                                    });
                                }

                                if let Some(ref cidr) = peer.ip_block_cidr {
                                    k8s_peer.ip_block = Some(IPBlock {
                                        cidr: cidr.clone(),
                                        except: if peer.ip_block_except.is_empty() {
                                            None
                                        } else {
                                            Some(peer.ip_block_except.clone())
                                        },
                                    });
                                }

                                k8s_peer
                            })
                            .collect();

                        let ports: Vec<NetworkPolicyPort> = rule
                            .ports
                            .iter()
                            .map(|port| NetworkPolicyPort {
                                protocol: Some(port.protocol.as_str().to_string()),
                                port: port.port.map(IntOrString::Int),
                                ..Default::default()
                            })
                            .collect();

                        NetworkPolicyIngressRule {
                            from: if from.is_empty() { None } else { Some(from) },
                            ports: if ports.is_empty() { None } else { Some(ports) },
                        }
                    })
                    .collect(),
            )
        } else {
            None
        };

        // Convert egress rules
        let egress = if !spec.egress_rules().is_empty() {
            Some(
                spec.egress_rules()
                    .iter()
                    .map(|rule| {
                        let to: Vec<NetworkPolicyPeer> = rule
                            .peers
                            .iter()
                            .map(|peer| {
                                let mut k8s_peer = NetworkPolicyPeer::default();

                                if let Some(ref pod_sel) = peer.pod_selector {
                                    let mut sel = BTreeMap::new();
                                    for (k, v) in pod_sel {
                                        sel.insert(k.clone(), v.clone());
                                    }
                                    k8s_peer.pod_selector = Some(LabelSelector {
                                        match_labels: Some(sel),
                                        ..Default::default()
                                    });
                                }

                                if let Some(ref ns_sel) = peer.namespace_selector {
                                    let mut sel = BTreeMap::new();
                                    for (k, v) in ns_sel {
                                        sel.insert(k.clone(), v.clone());
                                    }
                                    k8s_peer.namespace_selector = Some(LabelSelector {
                                        match_labels: Some(sel),
                                        ..Default::default()
                                    });
                                }

                                if let Some(ref cidr) = peer.ip_block_cidr {
                                    k8s_peer.ip_block = Some(IPBlock {
                                        cidr: cidr.clone(),
                                        except: if peer.ip_block_except.is_empty() {
                                            None
                                        } else {
                                            Some(peer.ip_block_except.clone())
                                        },
                                    });
                                }

                                k8s_peer
                            })
                            .collect();

                        let ports: Vec<NetworkPolicyPort> = rule
                            .ports
                            .iter()
                            .map(|port| NetworkPolicyPort {
                                protocol: Some(port.protocol.as_str().to_string()),
                                port: port.port.map(IntOrString::Int),
                                ..Default::default()
                            })
                            .collect();

                        NetworkPolicyEgressRule {
                            to: if to.is_empty() { None } else { Some(to) },
                            ports: if ports.is_empty() { None } else { Some(ports) },
                        }
                    })
                    .collect(),
            )
        } else {
            None
        };

        // Policy types
        let policy_types: Vec<String> = spec
            .policy_types()
            .iter()
            .map(|pt| match pt {
                PolicyType::Ingress => "Ingress".to_string(),
                PolicyType::Egress => "Egress".to_string(),
            })
            .collect();

        Ok(NetworkPolicy {
            metadata: ObjectMeta {
                name: Some(spec.name().to_string()),
                namespace: Some(self.namespace.clone()),
                labels: if labels.is_empty() {
                    None
                } else {
                    Some(labels)
                },
                ..Default::default()
            },
            spec: Some(K8sNetworkPolicySpec {
                pod_selector: Some(LabelSelector {
                    match_labels: if pod_selector.is_empty() {
                        None
                    } else {
                        Some(pod_selector)
                    },
                    ..Default::default()
                }),
                ingress,
                egress,
                policy_types: if policy_types.is_empty() {
                    None
                } else {
                    Some(policy_types)
                },
            }),
        })
    }
}
