//! Gateway API manager implementation.
//!
//! Note: This implementation uses dynamic client for Gateway API resources
//! since they are not part of the core Kubernetes API.

use crate::error::{Error, Result};
use crate::gateway::{GatewaySpec, HTTPRoute};
use kube::Client as KubeClient;
use kube::api::{Api, DeleteParams, DynamicObject, ListParams, Patch, PatchParams};
use kube::discovery::ApiResource;
use serde_json::json;

/// Manager for Kubernetes Gateway API operations.
///
/// This manager provides methods to create, update, delete, and retrieve Gateway
/// and HTTPRoute resources using the Kubernetes Gateway API.
///
/// Note: The Gateway API must be installed in the cluster for this to work.
pub struct GatewayManager {
    client: KubeClient,
    namespace: String,
}

impl GatewayManager {
    /// Creates a new GatewayManager.
    ///
    /// # Arguments
    ///
    /// * `client` - The Kubernetes client
    /// * `namespace` - The namespace to operate in
    pub fn new(client: KubeClient, namespace: impl Into<String>) -> Self {
        Self {
            client,
            namespace: namespace.into(),
        }
    }

    /// Creates or updates a Gateway using server-side apply.
    ///
    /// # Arguments
    ///
    /// * `spec` - The Gateway specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated Gateway name on success.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    /// use lmrc_kubernetes::gateway::{GatewaySpec, GatewayListener, Protocol};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let gateway = GatewaySpec::new("my-gateway")
    ///     .with_gateway_class("istio")
    ///     .with_listener(GatewayListener::new("http", 80, Protocol::HTTP));
    ///
    /// client.gateways().apply_gateway(&gateway).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn apply_gateway(&self, spec: &GatewaySpec) -> Result<String> {
        // Validate spec
        spec.validate().map_err(Error::ValidationError)?;

        let gateway_resource = ApiResource {
            group: "gateway.networking.k8s.io".to_string(),
            version: "v1".to_string(),
            api_version: "gateway.networking.k8s.io/v1".to_string(),
            kind: "Gateway".to_string(),
            plural: "gateways".to_string(),
        };

        let api: Api<DynamicObject> =
            Api::namespaced_with(self.client.clone(), &self.namespace, &gateway_resource);

        let name = spec.name();

        // Build listeners
        let listeners: Vec<_> = spec
            .listeners()
            .iter()
            .map(|l| {
                json!({
                    "name": l.name,
                    "port": l.port,
                    "protocol": l.protocol.as_str(),
                    "hostname": l.hostname,
                })
            })
            .collect();

        let gateway = json!({
            "apiVersion": "gateway.networking.k8s.io/v1",
            "kind": "Gateway",
            "metadata": {
                "name": name,
                "namespace": self.namespace,
                "labels": spec.labels(),
            },
            "spec": {
                "gatewayClassName": spec.gateway_class(),
                "listeners": listeners,
            }
        });

        let gateway_obj: DynamicObject = serde_json::from_value(gateway).map_err(|e| {
            Error::SerializationError(format!("Failed to serialize Gateway: {}", e))
        })?;

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

        api.patch(name, &params, &patch).await.map_err(|e| {
            Error::GatewayError(format!(
                "Failed to apply Gateway '{}': {}. Make sure Gateway API CRDs are installed.",
                name, e
            ))
        })?;

        Ok(name.to_string())
    }

    /// Creates or updates an HTTPRoute using server-side apply.
    ///
    /// # Arguments
    ///
    /// * `spec` - The HTTPRoute specification
    ///
    /// # Returns
    ///
    /// Returns the created/updated HTTPRoute name on success.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_kubernetes::{Client, ClientConfig};
    /// use lmrc_kubernetes::gateway::{HTTPRoute, HTTPRouteRule, HTTPRouteMatch, HTTPRouteBackend};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let config = ClientConfig::infer().await?;
    /// let client = Client::new(config, "default").await?;
    ///
    /// let route = HTTPRoute::new("my-route")
    ///     .with_gateway("my-gateway")
    ///     .with_rule(
    ///         HTTPRouteRule::new()
    ///             .with_match(HTTPRouteMatch::prefix("/api"))
    ///             .with_backend(HTTPRouteBackend::new("api-service", 8080))
    ///     );
    ///
    /// client.gateways().apply_http_route(&route).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn apply_http_route(&self, spec: &HTTPRoute) -> Result<String> {
        // Validate spec
        spec.validate().map_err(Error::ValidationError)?;

        let httproute_resource = ApiResource {
            group: "gateway.networking.k8s.io".to_string(),
            version: "v1".to_string(),
            api_version: "gateway.networking.k8s.io/v1".to_string(),
            kind: "HTTPRoute".to_string(),
            plural: "httproutes".to_string(),
        };

        let api: Api<DynamicObject> =
            Api::namespaced_with(self.client.clone(), &self.namespace, &httproute_resource);

        let name = spec.name();

        // Build parent refs (gateway references)
        let parent_refs = if let Some(gw_name) = spec.gateway_name() {
            vec![json!({
                "name": gw_name,
                "namespace": self.namespace,
            })]
        } else {
            vec![]
        };

        // Build rules
        let rules: Vec<_> = spec
            .rules()
            .iter()
            .map(|rule| {
                let matches: Vec<_> = rule
                    .matches
                    .iter()
                    .map(|m| {
                        json!({
                            "path": {
                                "type": m.path_type,
                                "value": m.path,
                            }
                        })
                    })
                    .collect();

                let backend_refs: Vec<_> = rule
                    .backends
                    .iter()
                    .map(|b| {
                        json!({
                            "name": b.name,
                            "port": b.port,
                            "weight": b.weight,
                        })
                    })
                    .collect();

                json!({
                    "matches": matches,
                    "backendRefs": backend_refs,
                })
            })
            .collect();

        let httproute = json!({
            "apiVersion": "gateway.networking.k8s.io/v1",
            "kind": "HTTPRoute",
            "metadata": {
                "name": name,
                "namespace": self.namespace,
                "labels": spec.labels(),
            },
            "spec": {
                "parentRefs": parent_refs,
                "hostnames": spec.hostnames(),
                "rules": rules,
            }
        });

        let httproute_obj: DynamicObject = serde_json::from_value(httproute).map_err(|e| {
            Error::SerializationError(format!("Failed to serialize HTTPRoute: {}", e))
        })?;

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

        api.patch(name, &params, &patch).await.map_err(|e| {
            Error::GatewayError(format!(
                "Failed to apply HTTPRoute '{}': {}. Make sure Gateway API CRDs are installed.",
                name, e
            ))
        })?;

        Ok(name.to_string())
    }

    /// Deletes a Gateway by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The Gateway name
    pub async fn delete_gateway(&self, name: &str) -> Result<()> {
        let gateway_resource = ApiResource {
            group: "gateway.networking.k8s.io".to_string(),
            version: "v1".to_string(),
            api_version: "gateway.networking.k8s.io/v1".to_string(),
            kind: "Gateway".to_string(),
            plural: "gateways".to_string(),
        };

        let api: Api<DynamicObject> =
            Api::namespaced_with(self.client.clone(), &self.namespace, &gateway_resource);

        let dp = DeleteParams::default();
        api.delete(name, &dp).await.map_err(|e| {
            Error::GatewayError(format!("Failed to delete Gateway '{}': {}", name, e))
        })?;

        Ok(())
    }

    /// Deletes an HTTPRoute by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The HTTPRoute name
    pub async fn delete_http_route(&self, name: &str) -> Result<()> {
        let httproute_resource = ApiResource {
            group: "gateway.networking.k8s.io".to_string(),
            version: "v1".to_string(),
            api_version: "gateway.networking.k8s.io/v1".to_string(),
            kind: "HTTPRoute".to_string(),
            plural: "httproutes".to_string(),
        };

        let api: Api<DynamicObject> =
            Api::namespaced_with(self.client.clone(), &self.namespace, &httproute_resource);

        let dp = DeleteParams::default();
        api.delete(name, &dp).await.map_err(|e| {
            Error::GatewayError(format!("Failed to delete HTTPRoute '{}': {}", name, e))
        })?;

        Ok(())
    }

    /// Lists all Gateways in the namespace.
    pub async fn list_gateways(&self) -> Result<Vec<DynamicObject>> {
        let gateway_resource = ApiResource {
            group: "gateway.networking.k8s.io".to_string(),
            version: "v1".to_string(),
            api_version: "gateway.networking.k8s.io/v1".to_string(),
            kind: "Gateway".to_string(),
            plural: "gateways".to_string(),
        };

        let api: Api<DynamicObject> =
            Api::namespaced_with(self.client.clone(), &self.namespace, &gateway_resource);

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

        Ok(list.items)
    }

    /// Lists all HTTPRoutes in the namespace.
    pub async fn list_http_routes(&self) -> Result<Vec<DynamicObject>> {
        let httproute_resource = ApiResource {
            group: "gateway.networking.k8s.io".to_string(),
            version: "v1".to_string(),
            api_version: "gateway.networking.k8s.io/v1".to_string(),
            kind: "HTTPRoute".to_string(),
            plural: "httproutes".to_string(),
        };

        let api: Api<DynamicObject> =
            Api::namespaced_with(self.client.clone(), &self.namespace, &httproute_resource);

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

        Ok(list.items)
    }
}
