//!
//! Defines stack-level settings and management configurations for different cloud platforms.
//! These settings customize deployment behavior and cross-account/cross-tenant access patterns.

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

use crate::{KubernetesCloudReference, KubernetesClusterOwnership};

/// AWS management configuration extracted from stack settings
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct AwsManagementConfig {
    /// The managing AWS IAM role ARN that can assume cross-account roles
    pub managing_role_arn: String,
}

/// GCP management configuration extracted from stack settings
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct GcpManagementConfig {
    /// Service account email for management roles
    pub service_account_email: String,
}

/// Azure management configuration extracted from stack settings
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct AzureManagementConfig {
    /// The managing Azure Tenant ID for cross-tenant access
    pub managing_tenant_id: String,
    /// OIDC issuer URL trusted by the target-side managed identity.
    pub oidc_issuer: String,
    /// OIDC subject claim trusted by the target-side managed identity.
    pub oidc_subject: String,
}

/// Management configuration for different cloud platforms.
///
/// Platform-derived configuration for cross-account/cross-tenant access.
/// This is NOT user-specified - it's derived from the Manager's ServiceAccount.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "platform")]
pub enum ManagementConfig {
    /// AWS management configuration
    Aws(AwsManagementConfig),
    /// GCP management configuration  
    Gcp(GcpManagementConfig),
    /// Azure management configuration
    Azure(AzureManagementConfig),
    /// Kubernetes management configuration (minimal for now)
    Kubernetes,
}

/// Network configuration for the stack.
///
/// Controls how VPC/VNet networking is provisioned. Users configure this in
/// `StackSettings`; the Network resource itself is auto-generated by preflights.
///
/// ## Egress policy
///
/// Container cluster VMs are configured for egress based on the mode:
///
/// - `UseDefault` → VMs get ephemeral public IPs (no NAT is provisioned)
/// - `Create` → VMs use private IPs; Alien provisions a NAT gateway for outbound access
/// - `ByoVpc*` / `ByoVnet*` → no public IPs assigned; customer manages egress
///
/// For production workloads, use `Create`. For fast dev/test iteration, `UseDefault` is
/// sufficient. For environments with existing VPCs, use the appropriate `ByoVpc*` variant.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum NetworkSettings {
    /// Use the cloud provider's default VPC/network.
    ///
    /// Designed for fast dev/test provisioning. No isolated VPC is created, so there
    /// is nothing to wait for or clean up. VMs receive ephemeral public IPs for internet
    /// access — no NAT gateway is provisioned.
    ///
    /// - **AWS**: Discovers the account's default VPC. Subnets are public with auto-assigned IPs.
    /// - **GCP**: Discovers the project's `default` network and regional subnet. Instance
    ///   templates include an `AccessConfig` to assign an ephemeral external IP.
    /// - **Azure**: Azure has no default VNet, so one is created along with a NAT Gateway.
    ///   VMs stay private and use NAT for egress.
    ///
    /// Not recommended for production. Use `Create` instead.
    #[serde(rename = "use-default")]
    UseDefault,

    /// Create a new isolated VPC/VNet with a managed NAT gateway.
    ///
    /// All networking infrastructure is provisioned by Alien and cleaned up on delete.
    /// VMs use private IPs only; all outbound traffic routes through the NAT gateway.
    ///
    /// Recommended for production deployments.
    #[serde(rename = "create")]
    Create {
        /// VPC/VNet CIDR block. If not specified, auto-generated from stack ID
        /// to reduce conflicts (e.g., "10.{hash}.0.0/16").
        #[serde(skip_serializing_if = "Option::is_none")]
        cidr: Option<String>,

        /// Number of availability zones (default: 2).
        #[serde(default = "default_availability_zones")]
        availability_zones: u8,
    },

    /// Use an existing VPC (AWS).
    ///
    /// Alien validates the references but creates no networking infrastructure.
    /// The customer is responsible for routing and egress (NAT, proxy, VPN, etc.).
    #[serde(rename = "byo-vpc-aws")]
    ByoVpcAws {
        /// The ID of the existing VPC
        vpc_id: String,
        /// IDs of public subnets (required for public ingress)
        public_subnet_ids: Vec<String>,
        /// IDs of private subnets
        private_subnet_ids: Vec<String>,
        /// Optional security group IDs to use
        #[serde(default)]
        security_group_ids: Vec<String>,
    },

    /// Use an existing VPC (GCP).
    ///
    /// Alien validates the references but creates no networking infrastructure.
    /// The customer is responsible for routing and egress (Cloud NAT, proxy, VPN, etc.).
    #[serde(rename = "byo-vpc-gcp")]
    ByoVpcGcp {
        /// The name of the existing VPC network
        network_name: String,
        /// The name of the subnet to use
        subnet_name: String,
        /// The region of the subnet
        region: String,
    },

    /// Use an existing VNet (Azure).
    ///
    /// Alien validates the references but creates no networking infrastructure.
    /// The customer is responsible for routing and egress (NAT Gateway, proxy, VPN, etc.).
    #[serde(rename = "byo-vnet-azure")]
    ByoVnetAzure {
        /// The full resource ID of the existing VNet
        vnet_resource_id: String,
        /// Name of the public subnet within the VNet
        public_subnet_name: String,
        /// Name of the private subnet within the VNet
        private_subnet_name: String,
        /// Name of the dedicated classic Application Gateway subnet within the VNet.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        application_gateway_subnet_name: Option<String>,
    },
}

fn default_availability_zones() -> u8 {
    2
}

/// Deployment-time compute choices for Alien-managed compute pools.
///
/// Application source declares portable pool requirements. This settings
/// object stores the concrete choices made for one deployment, such as the
/// provider machine type and selected machine counts.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct ComputeSettings {
    /// Selected compute choices keyed by pool ID.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub pools: HashMap<String, ComputePoolSelection>,
}

/// User-selected deployment settings for one compute pool.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "mode")]
pub enum ComputePoolSelection {
    /// Fixed number of machines.
    Fixed {
        /// Number of machines to run.
        machines: u32,
        /// Provider machine type selected for this deployment.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        machine: Option<String>,
    },
    /// Autoscaling machine pool.
    Autoscale {
        /// Minimum machine count.
        min: u32,
        /// Maximum machine count.
        max: u32,
        /// Provider machine type selected for this deployment.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        machine: Option<String>,
    },
}

impl ComputePoolSelection {
    /// Selected provider machine type, when this platform needs one.
    pub fn machine(&self) -> Option<&str> {
        match self {
            Self::Fixed { machine, .. } | Self::Autoscale { machine, .. } => machine.as_deref(),
        }
    }

    /// Selected minimum machine count.
    pub fn min_size(&self) -> u32 {
        match self {
            Self::Fixed { machines, .. } => *machines,
            Self::Autoscale { min, .. } => *min,
        }
    }

    /// Selected maximum machine count.
    pub fn max_size(&self) -> u32 {
        match self {
            Self::Fixed { machines, .. } => *machines,
            Self::Autoscale { max, .. } => *max,
        }
    }

    /// Whether the selection has internally valid scale bounds.
    pub fn validate(&self) -> std::result::Result<(), String> {
        match self {
            Self::Fixed { machines, .. } => {
                if *machines == 0 {
                    Err("fixed compute pools must select at least one machine".to_string())
                } else {
                    Ok(())
                }
            }
            Self::Autoscale { min, max, .. } => {
                if min > max {
                    Err(format!(
                        "autoscaling compute pool minimum ({min}) cannot exceed maximum ({max})"
                    ))
                } else {
                    Ok(())
                }
            }
        }
    }
}

/// Deployment model: how updates are delivered to the remote environment.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub enum DeploymentModel {
    /// Manager pushes updates via cross-account access.
    /// Available for AWS, GCP, Azure only.
    #[default]
    Push,
    /// Agent in remote environment pulls updates.
    /// Available for all platforms (AWS, GCP, Azure, Kubernetes, Local).
    Pull,
}

/// How updates are delivered to the deployment.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "kebab-case")]
pub enum UpdatesMode {
    /// Updates deploy automatically (default).
    #[default]
    Auto,
    /// Updates require explicit approval before deployment.
    ApprovalRequired,
}

/// How telemetry (logs, metrics, traces) is handled.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "kebab-case")]
pub enum TelemetryMode {
    /// No telemetry permissions. Data will not be collected.
    Off,
    /// Telemetry flows automatically (default).
    #[default]
    Auto,
    /// Telemetry requires explicit approval before collection begins.
    ApprovalRequired,
}

impl TelemetryMode {
    /// Returns true if telemetry is enabled (Auto or ApprovalRequired).
    pub fn is_enabled(&self) -> bool {
        !matches!(self, TelemetryMode::Off)
    }
}

/// How heartbeat health checks are handled.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "kebab-case")]
pub enum HeartbeatsMode {
    /// No heartbeat permissions. Health checks disabled.
    Off,
    /// Heartbeat enabled (default).
    #[default]
    On,
}

impl HeartbeatsMode {
    /// Returns true if heartbeat is enabled.
    pub fn is_enabled(&self) -> bool {
        matches!(self, HeartbeatsMode::On)
    }
}

/// Domain configuration for the stack.
///
/// When `custom_domains` is set, the specified resources use customer-provided
/// domains and certificates. Otherwise, Alien auto-generates domains.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct DomainSettings {
    /// Custom domain configuration per resource ID.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub custom_domains: Option<HashMap<String, CustomDomainConfig>>,
}

/// Custom domain configuration for a single resource.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CustomDomainConfig {
    /// Fully qualified domain name to use.
    pub domain: String,
    /// Customer-provided certificate reference.
    pub certificate: CustomCertificateConfig,
}

/// Platform-specific certificate references for custom domains.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CustomCertificateConfig {
    /// AWS ACM certificate ARN
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub aws: Option<AwsCustomCertificateConfig>,
    /// GCP Certificate Manager certificate name
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub gcp: Option<GcpCustomCertificateConfig>,
    /// Azure Key Vault certificate ID
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub azure: Option<AzureCustomCertificateConfig>,
    /// Kubernetes TLS Secret reference for Secret-backed route profiles.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub kubernetes: Option<KubernetesCustomCertificateConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct AwsCustomCertificateConfig {
    pub certificate_arn: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct GcpCustomCertificateConfig {
    pub certificate_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct AzureCustomCertificateConfig {
    pub key_vault_certificate_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub key_vault_resource_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct KubernetesCustomCertificateConfig {
    /// Existing TLS Secret containing `tls.crt` and `tls.key`.
    pub tls_secret_ref: KubernetesTlsSecretRef,
}

/// Kubernetes runtime substrate configuration.
///
/// This controls how setup chooses the cluster backing `Platform::Kubernetes`
/// deployments. When omitted, cloud-backed Kubernetes deployments default to a
/// managed cluster and generic/on-prem Kubernetes defaults to an external
/// cluster.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct KubernetesSettings {
    /// Cluster selection or creation settings.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cluster: Option<KubernetesClusterSettings>,
    /// Public HTTPS exposure contract shared by setup, Helm, and runtime.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub exposure: Option<KubernetesExposureSettings>,
}

/// Kubernetes cluster setup settings.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct KubernetesClusterSettings {
    /// Whether Alien should create the cluster, use a setup-owned existing
    /// cluster, or bind to an external/on-prem cluster.
    pub ownership: KubernetesClusterOwnership,
    /// Namespace where the Alien chart and application resources run.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub namespace: Option<String>,
    /// Optional provider-specific cloud identity for existing clusters.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cloud: Option<KubernetesCloudReference>,
}

/// Kubernetes public HTTPS exposure mode.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "mode")]
pub enum KubernetesExposureSettings {
    /// Do not create Alien-managed external routing.
    Disabled,
    /// Use Alien-generated DNS and Platform-managed certificate material.
    Generated {
        /// Runtime route profile to materialize.
        route: KubernetesRouteProfile,
        /// How managed certificate material reaches the route profile.
        certificate: KubernetesCertificateMode,
    },
    /// Use a customer hostname and customer-owned certificate reference.
    Custom {
        /// Hostname routed by the Kubernetes public endpoint.
        domain: String,
        /// Runtime route profile to materialize.
        route: KubernetesRouteProfile,
        /// Customer-owned certificate reference consumed by the route profile.
        certificate: KubernetesCertificateMode,
    },
}

/// Kubernetes route API selected for public endpoints.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "routeApi")]
pub enum KubernetesRouteProfile {
    /// `networking.k8s.io/v1` Ingress route profile.
    Ingress(KubernetesIngressRouteProfile),
    /// Gateway API `Gateway` + `HTTPRoute` route profile.
    Gateway(KubernetesGatewayRouteProfile),
}

/// Shared Ingress route profile values.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct KubernetesIngressRouteProfile {
    /// Route controller identifier, for example `eks.amazonaws.com/alb`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub controller: Option<String>,
    /// `spec.ingressClassName` for generated Ingresses.
    pub ingress_class_name: String,
    /// Labels applied to route objects.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub labels: HashMap<String, String>,
    /// Annotations applied to route objects.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub annotations: HashMap<String, String>,
    /// Provider-specific route options that are required by the selected class.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provider: Option<KubernetesRouteProviderOptions>,
}

/// Shared Gateway API route profile values.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct KubernetesGatewayRouteProfile {
    /// Route controller identifier, for example a cloud Gateway controller.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub controller: Option<String>,
    /// GatewayClass selected for generated Gateways.
    pub gateway_class_name: String,
    /// Listener port, usually 443.
    pub listener_port: u16,
    /// Labels applied to route objects.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub labels: HashMap<String, String>,
    /// Annotations applied to route objects.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub annotations: HashMap<String, String>,
    /// Provider-specific route options that are required by the selected class.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provider: Option<KubernetesRouteProviderOptions>,
}

/// Provider-specific route options required by supported managed profiles.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "provider")]
pub enum KubernetesRouteProviderOptions {
    /// AWS ALB route options for EKS.
    #[serde(rename_all = "camelCase")]
    AwsAlb {
        /// Internet-facing or internal ALB scheme.
        scheme: String,
        /// ALB target type, usually `ip`.
        target_type: String,
        /// Optional ALB IP address type, such as `dualstack`.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        ip_address_type: Option<String>,
        /// Explicit subnet IDs when the profile cannot rely on controller discovery.
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        subnet_ids: Vec<String>,
    },
    /// GKE Gateway route options.
    #[serde(rename_all = "camelCase")]
    GkeGateway {
        /// Optional static address name for the Gateway frontend.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        static_address_name: Option<String>,
    },
    /// Azure Application Gateway for Containers route options.
    #[serde(rename_all = "camelCase")]
    AzureApplicationGatewayForContainers {
        /// Optional ALB namespace when using BYO Application Gateway resources.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        alb_namespace: Option<String>,
        /// Optional ALB name when using BYO Application Gateway resources.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        alb_name: Option<String>,
        /// Public or internal frontend exposure.
        frontend: String,
    },
}

/// Certificate publication or reference mode for Kubernetes public endpoints.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase", tag = "mode")]
pub enum KubernetesCertificateMode {
    /// Platform-managed cert imported into AWS ACM by the runtime.
    #[serde(rename_all = "camelCase")]
    ManagedAcmImport {
        /// ACM region. Defaults to the deployment region when omitted.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        region: Option<String>,
        /// Tags applied to runtime-imported ACM certificates.
        #[serde(default, skip_serializing_if = "HashMap::is_empty")]
        tags: HashMap<String, String>,
    },
    /// Customer-provided AWS ACM certificate ARN.
    #[serde(rename_all = "camelCase")]
    AwsAcmArn {
        /// Existing ACM certificate ARN.
        certificate_arn: String,
    },
    /// Platform-managed cert written to a Kubernetes TLS Secret.
    #[serde(rename_all = "camelCase")]
    ManagedTlsSecret {
        /// Secret name template. Runtime may substitute resource/deployment tokens.
        secret_name_template: String,
    },
    /// Customer-provided Kubernetes TLS Secret.
    TlsSecretRef(KubernetesTlsSecretRef),
    /// No TLS certificate should be configured by Alien.
    None,
}

/// Namespace-scoped Kubernetes TLS Secret reference.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct KubernetesTlsSecretRef {
    /// Secret name.
    pub secret_name: String,
    /// Secret namespace. Defaults to the release namespace when omitted.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub namespace: Option<String>,
}

/// User-customizable deployment settings specified at deploy time.
///
/// These settings are provided by the customer via CloudFormation parameters,
/// Terraform attributes, CLI flags, or Helm values. They customize how the
/// deployment runs and what capabilities are enabled.
///
/// **Key distinction**: StackSettings is user-customizable, while ManagementConfig
/// is platform-derived (from the Manager's ServiceAccount).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct StackSettings {
    /// Network configuration for the stack (VPC/VNet settings).
    /// If `None`, an isolated VPC with NAT is auto-created when the stack has resources
    /// that require networking (e.g., containers). Set explicitly to customize:
    /// `UseDefault` for the provider's default network (fast, dev/test only),
    /// `Create` for an isolated VPC with managed NAT (production), or `ByoVpc*`
    /// to reference an existing customer-managed VPC.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub network: Option<NetworkSettings>,

    /// Domain configuration (future).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub domains: Option<DomainSettings>,

    /// Kubernetes runtime substrate configuration.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub kubernetes: Option<KubernetesSettings>,

    /// Deployment-time compute selections for Alien-managed compute pools.
    ///
    /// This is where provider machine names such as EC2 instance types, GCE
    /// machine types, or Azure VM SKUs belong. Application source should
    /// declare portable requirements instead.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub compute: Option<ComputeSettings>,

    /// Deployment model: push (Manager) or pull (Agent).
    /// Default: Push.
    /// - Push: Manager drives updates. For cloud platforms, requires cross-account
    ///   credentials established during initial setup. For push-mode local
    ///   deployments (currently `alien dev`), the manager has direct access —
    ///   no bootstrap needed.
    /// - Pull: Agent in the target environment drives updates via polling.
    ///   Required for Kubernetes and remote local deployments.
    #[serde(default, skip_serializing_if = "is_default_deployment_model")]
    pub deployment_model: DeploymentModel,

    /// How updates are delivered.
    /// - auto: Updates deploy automatically (default)
    /// - approval-required: Updates wait for explicit approval
    #[serde(default, skip_serializing_if = "is_default_updates_mode")]
    pub updates: UpdatesMode,

    /// How telemetry (logs, metrics, traces) is handled.
    /// - off: No telemetry permissions
    /// - auto: Telemetry flows automatically (default)
    /// - approval-required: Telemetry waits for explicit approval
    #[serde(default, skip_serializing_if = "is_default_telemetry_mode")]
    pub telemetry: TelemetryMode,

    /// How heartbeat health checks are handled.
    /// - off: No heartbeat permissions
    /// - on: Heartbeat enabled (default)
    #[serde(default, skip_serializing_if = "is_default_heartbeats_mode")]
    pub heartbeats: HeartbeatsMode,

    /// External bindings for pre-existing infrastructure.
    /// Allows using existing resources (MinIO, Redis, shared Container Apps
    /// Environment, etc.) instead of having Alien provision them.
    /// Required for Kubernetes platform, optional for cloud platforms.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
    pub external_bindings: Option<crate::ExternalBindings>,
}

fn is_default_deployment_model(model: &DeploymentModel) -> bool {
    *model == DeploymentModel::default()
}

fn is_default_updates_mode(mode: &UpdatesMode) -> bool {
    *mode == UpdatesMode::default()
}

fn is_default_telemetry_mode(mode: &TelemetryMode) -> bool {
    *mode == TelemetryMode::default()
}

fn is_default_heartbeats_mode(mode: &HeartbeatsMode) -> bool {
    *mode == HeartbeatsMode::default()
}
