//! Service management for Kubernetes.

use crate::error::{Error, Result};
use k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec as K8sServiceSpec};
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;
use kube::Client;
use kube::api::{Api, DeleteParams, ListParams, Patch, PatchParams, PostParams};
use std::collections::BTreeMap;
use tracing::info;

/// Type of Kubernetes Service.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ServiceType {
    /// ClusterIP: Exposes the service on a cluster-internal IP.
    ClusterIP,
    /// NodePort: Exposes the service on each Node's IP at a static port.
    NodePort,
    /// LoadBalancer: Exposes the service externally using a cloud provider's load balancer.
    LoadBalancer,
}

impl ServiceType {
    fn as_str(&self) -> &'static str {
        match self {
            ServiceType::ClusterIP => "ClusterIP",
            ServiceType::NodePort => "NodePort",
            ServiceType::LoadBalancer => "LoadBalancer",
        }
    }
}

/// Specification for a Kubernetes Service.
#[derive(Debug, Clone)]
pub struct ServiceSpec {
    name: String,
    selector: BTreeMap<String, String>,
    ports: Vec<ServicePortSpec>,
    service_type: ServiceType,
}

/// Port specification for a service.
#[derive(Debug, Clone)]
pub struct ServicePortSpec {
    /// Port on the service.
    pub port: i32,
    /// Target port on the pod.
    pub target_port: i32,
    /// Name of the port.
    pub name: Option<String>,
    /// Protocol (TCP or UDP).
    pub protocol: Option<String>,
}

impl ServiceSpec {
    /// Create a new service specification.
    pub fn new<S: Into<String>>(name: S) -> Self {
        Self {
            name: name.into(),
            selector: BTreeMap::new(),
            ports: Vec::new(),
            service_type: ServiceType::ClusterIP,
        }
    }

    /// Add a selector label.
    pub fn with_selector<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
        self.selector.insert(key.into(), value.into());
        self
    }

    /// Add a port.
    pub fn with_port(mut self, port: i32, target_port: i32) -> Self {
        self.ports.push(ServicePortSpec {
            port,
            target_port,
            name: Some("http".to_string()),
            protocol: Some("TCP".to_string()),
        });
        self
    }

    /// Add a named port.
    pub fn with_named_port<S: Into<String>>(
        mut self,
        name: S,
        port: i32,
        target_port: i32,
    ) -> Self {
        self.ports.push(ServicePortSpec {
            port,
            target_port,
            name: Some(name.into()),
            protocol: Some("TCP".to_string()),
        });
        self
    }

    /// Set the service type.
    pub fn with_type(mut self, service_type: ServiceType) -> Self {
        self.service_type = service_type;
        self
    }

    /// Get the service name.
    pub fn name(&self) -> &str {
        &self.name
    }
}

/// Manager for Kubernetes Service operations.
#[derive(Clone)]
pub struct ServiceManager {
    client: Client,
    namespace: String,
    field_manager: String,
}

impl ServiceManager {
    pub(crate) fn new(client: Client, namespace: String, field_manager: String) -> Self {
        Self {
            client,
            namespace,
            field_manager,
        }
    }

    /// Create a new service.
    pub async fn create(&self, spec: &ServiceSpec) -> Result<()> {
        let api: Api<Service> = Api::namespaced(self.client.clone(), &self.namespace);
        let k8s_service = self.spec_to_k8s(spec)?;

        info!("Creating service: {}", spec.name());

        api.create(&PostParams::default(), &k8s_service)
            .await
            .map_err(|e| Error::service(format!("Failed to create service: {}", e)))?;

        info!("Service {} created successfully", spec.name());
        Ok(())
    }

    /// Apply a service (create or update).
    pub async fn apply(&self, spec: &ServiceSpec) -> Result<()> {
        let api: Api<Service> = Api::namespaced(self.client.clone(), &self.namespace);
        let k8s_service = self.spec_to_k8s(spec)?;

        info!("Applying service: {}", spec.name());

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

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

        info!("Service {} applied successfully", spec.name());
        Ok(())
    }

    /// Delete a service.
    pub async fn delete(&self, name: &str) -> Result<()> {
        let api: Api<Service> = Api::namespaced(self.client.clone(), &self.namespace);

        info!("Deleting service: {}", name);

        api.delete(name, &DeleteParams::default())
            .await
            .map_err(|e| Error::service(format!("Failed to delete service: {}", e)))?;

        info!("Service {} deleted", name);
        Ok(())
    }

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

        let list = api
            .list(&ListParams::default())
            .await
            .map_err(|e| Error::service(format!("Failed to list services: {}", e)))?;

        Ok(list
            .items
            .iter()
            .filter_map(|s| s.metadata.name.clone())
            .collect())
    }

    /// Check if a service exists.
    pub async fn exists(&self, name: &str) -> Result<bool> {
        let api: Api<Service> = Api::namespaced(self.client.clone(), &self.namespace);
        Ok(api.get(name).await.is_ok())
    }

    fn spec_to_k8s(&self, spec: &ServiceSpec) -> Result<Service> {
        if spec.ports.is_empty() {
            return Err(Error::invalid_spec("Service must have at least one port"));
        }

        let ports: Vec<ServicePort> = spec
            .ports
            .iter()
            .map(|p| ServicePort {
                port: p.port,
                target_port: Some(IntOrString::Int(p.target_port)),
                name: p.name.clone(),
                protocol: p.protocol.clone(),
                ..Default::default()
            })
            .collect();

        let service_spec = K8sServiceSpec {
            selector: Some(spec.selector.clone()),
            ports: Some(ports),
            type_: Some(spec.service_type.as_str().to_string()),
            ..Default::default()
        };

        Ok(Service {
            metadata: ObjectMeta {
                name: Some(spec.name.clone()),
                namespace: Some(self.namespace.clone()),
                ..Default::default()
            },
            spec: Some(service_spec),
            ..Default::default()
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_service_spec_builder() {
        let spec = ServiceSpec::new("my-service")
            .with_selector("app", "myapp")
            .with_port(80, 8080)
            .with_type(ServiceType::LoadBalancer);

        assert_eq!(spec.name(), "my-service");
        assert_eq!(spec.selector.get("app"), Some(&"myapp".to_string()));
        assert_eq!(spec.ports.len(), 1);
        assert_eq!(spec.service_type, ServiceType::LoadBalancer);
    }

    #[test]
    fn test_service_type_str() {
        assert_eq!(ServiceType::ClusterIP.as_str(), "ClusterIP");
        assert_eq!(ServiceType::NodePort.as_str(), "NodePort");
        assert_eq!(ServiceType::LoadBalancer.as_str(), "LoadBalancer");
    }
}
