//! Client configuration structures for different cloud platforms
//!
//! This module contains the configuration structs for all supported cloud platforms.
//! These structs define the authentication and platform-specific settings needed
//! to connect to cloud services, but do not contain implementation logic (which
//! remains in the respective client crates).

use crate::Platform;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Service endpoint overrides for testing AWS services
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AwsServiceOverrides {
    /// Override endpoints for specific AWS services
    /// Key is the service name (e.g., "lambda", "s3"), value is the base URL
    pub endpoints: HashMap<String, String>,
}

/// Configuration for AWS role impersonation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AwsImpersonationConfig {
    /// The ARN of the role to assume
    pub role_arn: String,
    /// Optional session name for the assumed role session
    pub session_name: Option<String>,
    /// Optional duration for the assumed role credentials (in seconds)
    pub duration_seconds: Option<i32>,
    /// Optional external ID for the assume role operation
    pub external_id: Option<String>,
    /// Optional target region override. When provided, the impersonated config
    /// uses this region instead of inheriting the caller's region. Required for
    /// cross-region impersonation (e.g., management in us-east-1 targeting us-east-2).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target_region: Option<String>,
}

/// Configuration for AWS Web Identity Token authentication
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AwsWebIdentityConfig {
    /// The ARN of the role to assume
    pub role_arn: String,
    /// Optional session name for the assumed role session
    pub session_name: Option<String>,
    /// The path to the web identity token file
    pub web_identity_token_file: String,
    /// Optional duration for the assumed role credentials (in seconds)
    pub duration_seconds: Option<i32>,
}

/// Supported AWS authentication methods
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum AwsCredentials {
    /// Static direct access keys.
    AccessKeys {
        /// AWS Access Key ID
        access_key_id: String,
        /// AWS Secret Access Key
        secret_access_key: String,
        /// Optional AWS Session Token
        session_token: Option<String>,
    },
    /// Temporary AWS session credentials with an expiration time.
    SessionCredentials {
        /// AWS Access Key ID
        access_key_id: String,
        /// AWS Secret Access Key
        secret_access_key: String,
        /// AWS Session Token
        session_token: String,
        /// Credential expiration as an RFC3339 timestamp
        expires_at: String,
    },
    /// AWS Instance Metadata Service credentials.
    Imds {
        /// Optional IMDS endpoint override
        endpoint: Option<String>,
    },
    /// AWS profile credentials loaded via the AWS CLI.
    Profile {
        /// AWS profile name
        name: String,
    },
    /// Web Identity Token for OIDC authentication
    WebIdentity {
        /// Web identity configuration
        config: AwsWebIdentityConfig,
    },
}

impl std::fmt::Debug for AwsCredentials {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AwsCredentials::AccessKeys {
                access_key_id,
                session_token,
                ..
            } => f
                .debug_struct("AwsCredentials::AccessKeys")
                .field("access_key_id", access_key_id)
                .field("secret_access_key", &"[REDACTED]")
                .field(
                    "session_token",
                    &session_token.as_ref().map(|_| "[REDACTED]"),
                )
                .finish(),
            AwsCredentials::SessionCredentials {
                access_key_id,
                expires_at,
                ..
            } => f
                .debug_struct("AwsCredentials::SessionCredentials")
                .field("access_key_id", access_key_id)
                .field("secret_access_key", &"[REDACTED]")
                .field("session_token", &"[REDACTED]")
                .field("expires_at", expires_at)
                .finish(),
            AwsCredentials::Imds { endpoint } => f
                .debug_struct("AwsCredentials::Imds")
                .field("endpoint", endpoint)
                .finish(),
            AwsCredentials::Profile { name } => f
                .debug_struct("AwsCredentials::Profile")
                .field("name", name)
                .finish(),
            AwsCredentials::WebIdentity { config } => f
                .debug_struct("AwsCredentials::WebIdentity")
                .field("config", config)
                .finish(),
        }
    }
}

/// AWS client configuration
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AwsClientConfig {
    /// The AWS Account ID.
    pub account_id: String,
    /// The AWS region.
    pub region: String,
    /// AWS authentication credentials.
    pub credentials: AwsCredentials,
    /// Service endpoint overrides for testing
    #[serde(skip_serializing_if = "Option::is_none")]
    pub service_overrides: Option<AwsServiceOverrides>,
}

/// Service endpoint overrides for testing GCP services
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GcpServiceOverrides {
    /// Override endpoints for specific GCP services
    /// Key is the service name (e.g., "cloudrun", "storage"), value is the base URL
    pub endpoints: HashMap<String, String>,
}

/// Authentication options for talking to GCP APIs.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum GcpCredentials {
    /// Use an already-minted OAuth2 access token.
    AccessToken { token: String },

    /// Use a refreshable service account impersonation source.
    ImpersonatedServiceAccount {
        /// Source configuration used to call IAMCredentials.
        #[cfg_attr(feature = "openapi", schema(value_type = Object))]
        source: Box<GcpClientConfig>,
        /// Service account impersonation request.
        config: GcpImpersonationConfig,
    },

    /// Use a full Service Account JSON key (as string). A short-lived JWT will
    /// be created and exchanged for a bearer token automatically.
    ServiceAccountKey { json: String },

    /// Use GCP metadata server for authentication (for instances running on GCP)
    ServiceMetadata,

    /// Use projected service account token (for Kubernetes workload identity)
    ProjectedServiceAccount {
        /// Path to the projected service account token
        token_file: String,
        /// Service account email
        service_account_email: String,
    },

    /// Use an external account credential configuration.
    ExternalAccount {
        /// Workload identity audience.
        audience: String,
        /// Subject token type for STS token exchange.
        subject_token_type: String,
        /// STS token exchange URL.
        token_url: String,
        /// Path to the subject token file.
        credential_source_file: String,
        /// Optional service account impersonation URL.
        service_account_impersonation_url: Option<String>,
    },

    /// Use gcloud Application Default Credentials (authorized_user).
    /// Exchanges refresh_token for an access_token via Google's OAuth2 endpoint.
    AuthorizedUser {
        /// OAuth2 client ID
        client_id: String,
        /// OAuth2 client secret
        client_secret: String,
        /// OAuth2 refresh token
        refresh_token: String,
    },
}

impl std::fmt::Debug for GcpCredentials {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GcpCredentials::AccessToken { .. } => f
                .debug_struct("GcpCredentials::AccessToken")
                .field("token", &"[REDACTED]")
                .finish(),
            GcpCredentials::ImpersonatedServiceAccount { source, config } => f
                .debug_struct("GcpCredentials::ImpersonatedServiceAccount")
                .field("source_project_id", &source.project_id)
                .field("source_region", &source.region)
                .field("service_account_email", &config.service_account_email)
                .finish(),
            GcpCredentials::ServiceAccountKey { .. } => f
                .debug_struct("GcpCredentials::ServiceAccountKey")
                .field("json", &"[REDACTED]")
                .finish(),
            GcpCredentials::ServiceMetadata => write!(f, "GcpCredentials::ServiceMetadata"),
            GcpCredentials::ProjectedServiceAccount {
                token_file,
                service_account_email,
            } => f
                .debug_struct("GcpCredentials::ProjectedServiceAccount")
                .field("token_file", token_file)
                .field("service_account_email", service_account_email)
                .finish(),
            GcpCredentials::ExternalAccount {
                audience,
                subject_token_type,
                token_url,
                credential_source_file,
                service_account_impersonation_url,
            } => f
                .debug_struct("GcpCredentials::ExternalAccount")
                .field("audience", audience)
                .field("subject_token_type", subject_token_type)
                .field("token_url", token_url)
                .field("credential_source_file", credential_source_file)
                .field(
                    "service_account_impersonation_url",
                    service_account_impersonation_url,
                )
                .finish(),
            GcpCredentials::AuthorizedUser { client_id, .. } => f
                .debug_struct("GcpCredentials::AuthorizedUser")
                .field("client_id", client_id)
                .field("client_secret", &"[REDACTED]")
                .field("refresh_token", &"[REDACTED]")
                .finish(),
        }
    }
}

/// Configuration for GCP service account impersonation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GcpImpersonationConfig {
    /// The email of the service account to impersonate
    pub service_account_email: String,
    /// The OAuth 2.0 scopes that define the access token's permissions
    pub scopes: Vec<String>,
    /// Optional sequence of service accounts in a delegation chain
    pub delegates: Option<Vec<String>>,
    /// Optional desired lifetime duration of the access token (max 3600s)
    pub lifetime: Option<String>,
    /// Optional target project ID override. When provided, the impersonated config
    /// uses this project ID instead of inheriting the caller's project.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target_project_id: Option<String>,
    /// Optional target region override. When provided, the impersonated config
    /// uses this region instead of inheriting the caller's region.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target_region: Option<String>,
}

impl Default for GcpImpersonationConfig {
    fn default() -> Self {
        Self {
            service_account_email: String::new(),
            scopes: vec!["https://www.googleapis.com/auth/cloud-platform".to_string()],
            delegates: None,
            lifetime: Some("3600s".to_string()),
            target_project_id: None,
            target_region: None,
        }
    }
}

/// GCP client configuration
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GcpClientConfig {
    /// The GCP Project ID.
    pub project_id: String,
    /// The GCP region for resources.
    pub region: String,
    /// GCP authentication credentials.
    pub credentials: GcpCredentials,
    /// Service endpoint overrides for testing
    #[serde(skip_serializing_if = "Option::is_none")]
    pub service_overrides: Option<GcpServiceOverrides>,
    /// The GCP project number (numeric). Resolved at runtime via Resource Manager API.
    /// Used in IAM condition expressions where resource.name uses project number.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub project_number: Option<String>,
}

/// Service endpoint overrides for testing Azure services
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AzureServiceOverrides {
    /// Override endpoints for specific Azure services
    /// Key is the service name (e.g., "management", "storage", "containerApps"), value is the base URL
    pub endpoints: HashMap<String, String>,
}

/// Represents Azure authentication credentials
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum AzureCredentials {
    /// Service principal with client secret
    ServicePrincipal {
        /// The client ID (application ID)
        client_id: String,
        /// The client secret
        client_secret: String,
    },
    /// Direct access token
    AccessToken {
        /// The bearer token to use for authentication
        token: String,
    },
    /// Azure VM IMDS managed identity.
    VmManagedIdentity {
        /// The client ID of the user-assigned managed identity
        client_id: String,
        /// Optional IMDS endpoint override
        identity_endpoint: Option<String>,
    },
    /// Azure AD Workload Identity (federated identity)
    WorkloadIdentity {
        /// The client ID of the managed identity or application
        client_id: String,
        /// The tenant ID for authentication
        tenant_id: String,
        /// Path to the federated token file
        federated_token_file: String,
        /// The authority host URL
        authority_host: String,
    },
    /// Azure Managed Identity (Container Apps / App Service)
    /// Uses IDENTITY_ENDPOINT + IDENTITY_HEADER injected by the platform
    ManagedIdentity {
        /// The client ID of the user-assigned managed identity
        client_id: String,
        /// The identity endpoint URL (from IDENTITY_ENDPOINT env var)
        identity_endpoint: String,
        /// The identity header secret (from IDENTITY_HEADER env var)
        identity_header: String,
    },
}

impl std::fmt::Debug for AzureCredentials {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AzureCredentials::ServicePrincipal { client_id, .. } => f
                .debug_struct("AzureCredentials::ServicePrincipal")
                .field("client_id", client_id)
                .field("client_secret", &"[REDACTED]")
                .finish(),
            AzureCredentials::AccessToken { .. } => f
                .debug_struct("AzureCredentials::AccessToken")
                .field("token", &"[REDACTED]")
                .finish(),
            AzureCredentials::VmManagedIdentity {
                client_id,
                identity_endpoint,
            } => f
                .debug_struct("AzureCredentials::VmManagedIdentity")
                .field("client_id", client_id)
                .field("identity_endpoint", identity_endpoint)
                .finish(),
            AzureCredentials::WorkloadIdentity {
                client_id,
                tenant_id,
                federated_token_file,
                authority_host,
            } => f
                .debug_struct("AzureCredentials::WorkloadIdentity")
                .field("client_id", client_id)
                .field("tenant_id", tenant_id)
                .field("federated_token_file", federated_token_file)
                .field("authority_host", authority_host)
                .finish(),
            AzureCredentials::ManagedIdentity {
                client_id,
                identity_endpoint,
                ..
            } => f
                .debug_struct("AzureCredentials::ManagedIdentity")
                .field("client_id", client_id)
                .field("identity_endpoint", identity_endpoint)
                .field("identity_header", &"[REDACTED]")
                .finish(),
        }
    }
}

/// Configuration for Azure managed identity impersonation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AzureImpersonationConfig {
    /// The client ID of the managed identity or service principal to impersonate
    pub client_id: String,
    /// The scope for the access token (e.g., "https://management.azure.com/.default")
    pub scope: String,
    /// Optional tenant ID for cross-tenant impersonation
    pub tenant_id: Option<String>,
    /// Optional target subscription ID override. When provided, the impersonated config
    /// uses this subscription instead of inheriting the caller's subscription.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target_subscription_id: Option<String>,
    /// Optional target region override. When provided, the impersonated config
    /// uses this region instead of inheriting the caller's region.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target_region: Option<String>,
}

impl Default for AzureImpersonationConfig {
    fn default() -> Self {
        Self {
            client_id: String::new(),
            scope: "https://management.azure.com/.default".to_string(),
            tenant_id: None,
            target_subscription_id: None,
            target_region: None,
        }
    }
}

/// Azure client configuration
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AzureClientConfig {
    /// The Azure Subscription ID where resources will be deployed.
    pub subscription_id: String,
    /// The customer's Azure Tenant ID.
    pub tenant_id: String,
    /// Azure region for resources.
    pub region: Option<String>,
    /// Azure authentication credentials.
    pub credentials: AzureCredentials,
    /// Service endpoint overrides for testing
    #[serde(skip_serializing_if = "Option::is_none")]
    pub service_overrides: Option<AzureServiceOverrides>,
}

/// Configuration mode for Kubernetes access
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "mode")]
pub enum KubernetesClientConfig {
    /// Use in-cluster configuration (service account tokens, etc.)
    InCluster {
        /// The namespace to operate in
        #[serde(skip_serializing_if = "Option::is_none")]
        namespace: Option<String>,
        /// Additional headers to include in requests
        #[serde(skip_serializing_if = "Option::is_none")]
        additional_headers: Option<HashMap<String, String>>,
    },
    /// Use kubeconfig file for configuration
    Kubeconfig {
        /// Path to kubeconfig file (optional, defaults to standard locations)
        #[serde(skip_serializing_if = "Option::is_none")]
        kubeconfig_path: Option<String>,
        /// Context name to use (optional, defaults to current-context)
        #[serde(skip_serializing_if = "Option::is_none")]
        context: Option<String>,
        /// Cluster name to use (optional, defaults to context's cluster)
        #[serde(skip_serializing_if = "Option::is_none")]
        cluster: Option<String>,
        /// User name to use (optional, defaults to context's user)
        #[serde(skip_serializing_if = "Option::is_none")]
        user: Option<String>,
        /// The namespace to operate in
        #[serde(skip_serializing_if = "Option::is_none")]
        namespace: Option<String>,
        /// Additional headers to include in requests
        #[serde(skip_serializing_if = "Option::is_none")]
        additional_headers: Option<HashMap<String, String>>,
    },
    /// Manual configuration with explicit values
    Manual {
        /// The Kubernetes cluster server URL
        server_url: String,
        /// The cluster certificate authority data (base64 encoded)
        certificate_authority_data: Option<String>,
        /// Skip TLS verification (insecure)
        insecure_skip_tls_verify: Option<bool>,
        /// Client certificate data (base64 encoded) for mutual TLS
        client_certificate_data: Option<String>,
        /// Client key data (base64 encoded) for mutual TLS
        client_key_data: Option<String>,
        /// Bearer token for authentication
        token: Option<String>,
        /// Username for basic authentication
        username: Option<String>,
        /// Password for basic authentication
        password: Option<String>,
        /// The namespace to operate in
        namespace: Option<String>,
        /// Additional headers to include in requests
        additional_headers: HashMap<String, String>,
    },
}

impl std::fmt::Debug for KubernetesClientConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KubernetesClientConfig::InCluster {
                namespace,
                additional_headers,
            } => f
                .debug_struct("KubernetesClientConfig::InCluster")
                .field("namespace", namespace)
                .field("additional_headers", additional_headers)
                .finish(),
            KubernetesClientConfig::Kubeconfig {
                kubeconfig_path,
                context,
                cluster,
                user,
                namespace,
                additional_headers,
            } => f
                .debug_struct("KubernetesClientConfig::Kubeconfig")
                .field("kubeconfig_path", kubeconfig_path)
                .field("context", context)
                .field("cluster", cluster)
                .field("user", user)
                .field("namespace", namespace)
                .field("additional_headers", additional_headers)
                .finish(),
            KubernetesClientConfig::Manual {
                server_url,
                certificate_authority_data,
                insecure_skip_tls_verify,
                client_certificate_data,
                client_key_data,
                token,
                username,
                password,
                namespace,
                additional_headers,
            } => f
                .debug_struct("KubernetesClientConfig::Manual")
                .field("server_url", server_url)
                .field("certificate_authority_data", certificate_authority_data)
                .field("insecure_skip_tls_verify", insecure_skip_tls_verify)
                .field("client_certificate_data", client_certificate_data)
                .field(
                    "client_key_data",
                    &client_key_data.as_ref().map(|_| "[REDACTED]"),
                )
                .field("token", &token.as_ref().map(|_| "[REDACTED]"))
                .field("username", username)
                .field("password", &password.as_ref().map(|_| "[REDACTED]"))
                .field("namespace", namespace)
                .field("additional_headers", additional_headers)
                .finish(),
        }
    }
}

/// Cloud-agnostic impersonation configuration
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "platform")]
pub enum ImpersonationConfig {
    Aws(AwsImpersonationConfig),
    Gcp(GcpImpersonationConfig),
    Azure(AzureImpersonationConfig),
    // Kubernetes doesn't support impersonation, so we don't include it here
}

/// Configuration for different cloud platform clients
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "platform")]
pub enum ClientConfig {
    Aws(Box<AwsClientConfig>),
    Gcp(Box<GcpClientConfig>),
    Azure(Box<AzureClientConfig>),
    Kubernetes(Box<KubernetesClientConfig>),
    KubernetesCloud {
        kubernetes: Box<KubernetesClientConfig>,
        #[cfg_attr(feature = "openapi", schema(value_type = Object))]
        cloud: Box<ClientConfig>,
    },
    Local {
        /// State directory for local resources and deployment state
        state_directory: String,
    },
    /// Machines platform - uses Horizon-backed controllers without cloud credentials.
    #[serde(skip)]
    Machines,
    /// Test platform - uses mock controllers without real cloud APIs
    #[serde(skip)]
    Test,
}

impl ClientConfig {
    /// Returns the platform enum for this configuration.
    pub fn platform(&self) -> Platform {
        match self {
            ClientConfig::Aws(_) => Platform::Aws,
            ClientConfig::Gcp(_) => Platform::Gcp,
            ClientConfig::Azure(_) => Platform::Azure,
            ClientConfig::Kubernetes(_) => Platform::Kubernetes,
            ClientConfig::KubernetesCloud { .. } => Platform::Kubernetes,
            ClientConfig::Local { .. } => Platform::Local,
            ClientConfig::Machines => Platform::Machines,
            ClientConfig::Test => Platform::Test,
        }
    }

    pub fn config_for_platform(&self, platform: Platform) -> Option<ClientConfig> {
        match self {
            ClientConfig::KubernetesCloud { cloud, .. } => {
                if platform == Platform::Kubernetes {
                    Some(self.clone())
                } else if cloud.platform() == platform {
                    Some((**cloud).clone())
                } else {
                    None
                }
            }
            config if config.platform() == platform => Some(config.clone()),
            _ => None,
        }
    }

    /// Returns the AWS configuration if this is an AWS client config.
    pub fn aws_config(&self) -> Option<&AwsClientConfig> {
        match self {
            ClientConfig::Aws(config) => Some(config),
            ClientConfig::KubernetesCloud { cloud, .. } => cloud.aws_config(),
            _ => None,
        }
    }

    /// Returns the GCP configuration if this is a GCP client config.
    pub fn gcp_config(&self) -> Option<&GcpClientConfig> {
        match self {
            ClientConfig::Gcp(config) => Some(config),
            ClientConfig::KubernetesCloud { cloud, .. } => cloud.gcp_config(),
            _ => None,
        }
    }

    /// Returns the Azure configuration if this is an Azure client config.
    pub fn azure_config(&self) -> Option<&AzureClientConfig> {
        match self {
            ClientConfig::Azure(config) => Some(config),
            ClientConfig::KubernetesCloud { cloud, .. } => cloud.azure_config(),
            _ => None,
        }
    }

    /// Returns the Kubernetes configuration if this is a Kubernetes client config.
    pub fn kubernetes_config(&self) -> Option<&KubernetesClientConfig> {
        match self {
            ClientConfig::Kubernetes(config) => Some(config),
            ClientConfig::KubernetesCloud { kubernetes, .. } => Some(kubernetes),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{
        AwsClientConfig, AwsCredentials, AzureClientConfig, AzureCredentials, ClientConfig,
        GcpClientConfig, GcpCredentials, KubernetesClientConfig,
    };

    #[test]
    fn kubernetes_cloud_exposes_nested_aws_config() {
        let config = ClientConfig::KubernetesCloud {
            kubernetes: Box::new(KubernetesClientConfig::InCluster {
                namespace: Some("test".to_string()),
                additional_headers: None,
            }),
            cloud: Box::new(ClientConfig::Aws(Box::new(AwsClientConfig {
                account_id: "123456789012".to_string(),
                region: "us-east-2".to_string(),
                credentials: AwsCredentials::AccessKeys {
                    access_key_id: "access".to_string(),
                    secret_access_key: "secret".to_string(),
                    session_token: None,
                },
                service_overrides: None,
            }))),
        };

        assert_eq!(config.platform(), crate::Platform::Kubernetes);
        assert!(config.kubernetes_config().is_some());
        assert_eq!(config.aws_config().unwrap().region, "us-east-2");
        assert!(config.gcp_config().is_none());
        assert!(config.azure_config().is_none());
    }

    #[test]
    fn kubernetes_cloud_preserves_cloud_config_for_kubernetes_controllers() {
        let config = ClientConfig::KubernetesCloud {
            kubernetes: Box::new(KubernetesClientConfig::InCluster {
                namespace: Some("test".to_string()),
                additional_headers: None,
            }),
            cloud: Box::new(ClientConfig::Aws(Box::new(AwsClientConfig {
                account_id: "123456789012".to_string(),
                region: "us-east-2".to_string(),
                credentials: AwsCredentials::AccessKeys {
                    access_key_id: "access".to_string(),
                    secret_access_key: "secret".to_string(),
                    session_token: None,
                },
                service_overrides: None,
            }))),
        };

        let kubernetes_config = config
            .config_for_platform(crate::Platform::Kubernetes)
            .unwrap();

        assert!(matches!(
            kubernetes_config,
            ClientConfig::KubernetesCloud { .. }
        ));
        assert!(kubernetes_config.kubernetes_config().is_some());
        assert_eq!(kubernetes_config.aws_config().unwrap().region, "us-east-2");
    }

    #[test]
    fn kubernetes_cloud_exposes_nested_gcp_config() {
        let config = ClientConfig::KubernetesCloud {
            kubernetes: Box::new(KubernetesClientConfig::InCluster {
                namespace: Some("test".to_string()),
                additional_headers: None,
            }),
            cloud: Box::new(ClientConfig::Gcp(Box::new(GcpClientConfig {
                project_id: "project".to_string(),
                region: "us-central1".to_string(),
                credentials: GcpCredentials::AccessToken {
                    token: "token".to_string(),
                },
                service_overrides: None,
                project_number: None,
            }))),
        };

        assert_eq!(config.gcp_config().unwrap().project_id, "project");
        assert!(config.aws_config().is_none());
        assert!(config.azure_config().is_none());
    }

    #[test]
    fn kubernetes_cloud_exposes_nested_azure_config() {
        let config = ClientConfig::KubernetesCloud {
            kubernetes: Box::new(KubernetesClientConfig::InCluster {
                namespace: Some("test".to_string()),
                additional_headers: None,
            }),
            cloud: Box::new(ClientConfig::Azure(Box::new(AzureClientConfig {
                subscription_id: "sub".to_string(),
                tenant_id: "tenant".to_string(),
                region: Some("eastus".to_string()),
                credentials: AzureCredentials::AccessToken {
                    token: "token".to_string(),
                },
                service_overrides: None,
            }))),
        };

        assert_eq!(config.azure_config().unwrap().subscription_id, "sub");
        assert!(config.aws_config().is_none());
        assert!(config.gcp_config().is_none());
    }
}
