//! Configuration validation library for LMRC Stack projects
//!
//! This library provides types and validation for LMRC Stack project configurations.
//! It defines the schema for project configuration files and validates them.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use validator::Validate;

pub mod providers;

/// Main configuration structure for LMRC Stack projects
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct LmrcConfig {
    /// Project metadata
    #[validate(nested)]
    pub project: ProjectConfig,

    /// Infrastructure provider selection
    #[validate(nested)]
    pub providers: ProviderConfig,

    /// Application configuration
    #[validate(nested)]
    pub apps: AppsConfig,

    /// Infrastructure-specific configurations
    #[validate(nested)]
    pub infrastructure: InfrastructureConfig,
}

/// Project metadata
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ProjectConfig {
    /// Project name (alphanumeric with hyphens/underscores)
    #[validate(length(min = 1, max = 100))]
    pub name: String,

    /// Project description
    #[validate(length(min = 1, max = 500))]
    pub description: String,
}

/// Provider selection configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ProviderConfig {
    /// Server provider (currently only "hetzner")
    pub server: String,

    /// Kubernetes provider (currently "k3s" or "kubernetes")
    pub kubernetes: String,

    /// Database provider (currently only "postgres")
    pub database: String,

    /// Queue/Message broker provider (currently only "rabbitmq")
    #[serde(default = "default_queue_provider")]
    pub queue: String,

    /// DNS provider (currently only "cloudflare")
    pub dns: String,

    /// Git provider (currently only "gitlab")
    pub git: String,
}

fn default_queue_provider() -> String {
    "rabbitmq".to_string()
}

/// Applications configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct AppsConfig {
    /// List of application names in the workspace
    #[validate(length(min = 1))]
    pub applications: Vec<ApplicationEntry>,
}

/// Application type - determines which template to use
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum AppType {
    /// Gateway with authentication (session-based)
    Gateway,
    /// API service without authentication
    Api,
    /// Database migrator
    Migrator,
    /// Basic application (simple Rust binary)
    Basic,
}

impl AppType {
    /// Get all available app types
    pub fn all() -> Vec<Self> {
        vec![
            AppType::Gateway,
            AppType::Api,
            AppType::Migrator,
            AppType::Basic,
        ]
    }

    /// Get display name for the app type
    pub fn display_name(&self) -> &'static str {
        match self {
            AppType::Gateway => "Gateway (with authentication)",
            AppType::Api => "API Service (no auth)",
            AppType::Migrator => "Database Migrator",
            AppType::Basic => "Basic Application",
        }
    }

    /// Get description for the app type
    pub fn description(&self) -> &'static str {
        match self {
            AppType::Gateway => "HTTP API gateway with session-based authentication, uses SeaORM + PostgreSQL",
            AppType::Api => "HTTP API service without authentication (for internal services behind gateway)",
            AppType::Migrator => "SeaORM migration CLI for database schema management",
            AppType::Basic => "Simple Rust binary application with no HTTP framework",
        }
    }
}

/// Single application entry
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ApplicationEntry {
    /// Application name
    #[validate(length(min = 1, max = 50))]
    pub name: String,

    /// Application type - determines which template to use
    #[serde(default)]
    pub app_type: Option<AppType>,

    /// Docker configuration for this application
    #[serde(default)]
    #[validate(nested)]
    pub docker: Option<DockerConfig>,

    /// Kubernetes deployment configuration
    #[serde(default)]
    #[validate(nested)]
    pub deployment: Option<DeploymentConfig>,
}

/// Docker build configuration for an application
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct DockerConfig {
    /// Dockerfile path relative to app directory
    #[serde(default = "default_dockerfile")]
    pub dockerfile: String,

    /// Build context path relative to workspace root
    #[serde(default = "default_context")]
    pub context: String,

    /// Image tags (e.g., ["latest", "v1.0.0"])
    #[serde(default = "default_tags")]
    pub tags: Vec<String>,
}

fn default_dockerfile() -> String {
    "Dockerfile".to_string()
}

fn default_context() -> String {
    ".".to_string()
}

fn default_tags() -> Vec<String> {
    vec!["latest".to_string()]
}

impl Default for DockerConfig {
    fn default() -> Self {
        Self {
            dockerfile: default_dockerfile(),
            context: default_context(),
            tags: default_tags(),
        }
    }
}

/// Kubernetes deployment configuration for an application
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct DeploymentConfig {
    /// Number of replicas
    #[serde(default = "default_replicas")]
    #[validate(range(min = 1, max = 100))]
    pub replicas: u32,

    /// Container port
    #[serde(default = "default_port")]
    #[validate(range(min = 1, max = 65535))]
    pub port: u16,

    /// CPU request (e.g., "100m", "1")
    #[serde(default)]
    pub cpu_request: Option<String>,

    /// Memory request (e.g., "128Mi", "1Gi")
    #[serde(default)]
    pub memory_request: Option<String>,

    /// CPU limit (e.g., "1", "2")
    #[serde(default)]
    pub cpu_limit: Option<String>,

    /// Memory limit (e.g., "512Mi", "2Gi")
    #[serde(default)]
    pub memory_limit: Option<String>,

    /// Environment variables
    #[serde(default)]
    pub env: Vec<EnvVar>,
}

fn default_replicas() -> u32 {
    1
}

fn default_port() -> u16 {
    8080
}

impl Default for DeploymentConfig {
    fn default() -> Self {
        Self {
            replicas: default_replicas(),
            port: default_port(),
            cpu_request: None,
            memory_request: None,
            cpu_limit: None,
            memory_limit: None,
            env: Vec::new(),
        }
    }
}

/// Environment variable configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct EnvVar {
    /// Variable name
    #[validate(length(min = 1))]
    pub name: String,

    /// Variable value (for non-sensitive values)
    #[serde(default)]
    pub value: Option<String>,

    /// Reference to a secret (alternative to value)
    #[serde(default)]
    pub secret_ref: Option<SecretRef>,
}

/// Reference to a Kubernetes secret
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct SecretRef {
    /// Secret name
    #[validate(length(min = 1))]
    pub name: String,

    /// Key within the secret
    #[validate(length(min = 1))]
    pub key: String,
}

/// Infrastructure configuration for all providers
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct InfrastructureConfig {
    /// Infrastructure provider (e.g., "hetzner", "aws", "digitalocean")
    #[serde(default = "default_provider")]
    pub provider: String,

    /// Network configuration
    #[serde(default)]
    #[validate(nested)]
    pub network: Option<NetworkConfig>,

    /// Server groups defining infrastructure topology
    #[serde(default)]
    pub servers: Vec<ServerGroup>,

    /// K3s cluster configuration
    #[validate(nested)]
    pub k3s: Option<K3sConfig>,

    /// PostgreSQL configuration
    #[validate(nested)]
    pub postgres: Option<PostgresConfig>,

    /// RabbitMQ configuration
    #[validate(nested)]
    pub rabbitmq: Option<RabbitMqConfig>,

    /// Vault configuration
    #[validate(nested)]
    pub vault: Option<VaultConfig>,

    /// DNS configuration
    #[validate(nested)]
    pub dns: Option<DnsConfig>,

    /// GitLab-specific configuration
    #[validate(nested)]
    pub gitlab: Option<GitLabConfig>,

    /// Load balancer configuration
    #[serde(default)]
    #[validate(nested)]
    pub load_balancer: Option<LoadBalancerConfig>,
}

fn default_provider() -> String {
    "hetzner".to_string()
}

/// Network configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct NetworkConfig {
    /// Enable private networking between servers
    #[serde(default)]
    pub enable_private_network: bool,

    /// Private network CIDR (e.g., "10.0.0.0/16")
    pub private_network: Option<String>,

    /// Firewall rules
    #[serde(default)]
    pub firewall_rules: Vec<FirewallRule>,
}

/// Firewall rule configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct FirewallRule {
    /// Rule name
    #[validate(length(min = 1))]
    pub name: String,

    /// Direction: "inbound" or "outbound"
    pub direction: String,

    /// Protocol: "tcp", "udp", "icmp", or "any"
    pub protocol: String,

    /// Port or port range (e.g., "80", "8000-9000")
    pub port: Option<String>,

    /// Source (CIDR or server group name)
    pub source: String,

    /// Destination (server group name or "any")
    pub destination: String,

    /// Action: "allow" or "deny"
    pub action: String,
}

/// Server group defining a set of similar servers
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ServerGroup {
    /// Unique name for this server group
    #[validate(length(min = 1, max = 50))]
    pub name: String,

    /// Role/purpose of this server group
    pub role: ServerRole,

    /// Server type (e.g., "cx11", "cpx21" for Hetzner)
    pub server_type: String,

    /// Location/region (e.g., "nbg1", "fsn1" for Hetzner)
    pub location: String,

    /// Number of servers in this group
    #[validate(range(min = 1, max = 50))]
    pub count: u32,

    /// Labels for organization and selection
    #[serde(default)]
    pub labels: HashMap<String, String>,

    /// SSH key names or IDs
    #[serde(default)]
    pub ssh_keys: Vec<String>,

    /// Image/OS to use (optional, defaults to provider default)
    pub image: Option<String>,
}

/// Server role/purpose
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum ServerRole {
    /// K3s control plane node
    K3sControl,
    /// K3s worker node
    K3sWorker,
    /// PostgreSQL database server
    Postgres,
    /// MySQL/MariaDB database server
    MySQL,
    /// MongoDB database server
    MongoDB,
    /// Redis cache server
    Redis,
    /// Memcached cache server
    Memcached,
    /// RabbitMQ message broker
    RabbitMQ,
    /// Kafka message broker
    Kafka,
    /// Elasticsearch search engine
    Elasticsearch,
    /// Load balancer (HAProxy, Nginx, etc.)
    LoadBalancer,
    /// Monitoring server (Prometheus, Grafana, etc.)
    Monitoring,
    /// CI/CD runner (GitLab Runner, etc.)
    CIRunner,
    /// Bastion/Jump server for SSH access
    Bastion,
    /// Storage server (MinIO, NFS, etc.)
    Storage,
    /// Custom/generic server
    Custom,
}

impl ServerRole {
    /// Get all available server roles
    pub fn all() -> Vec<Self> {
        vec![
            ServerRole::K3sControl,
            ServerRole::K3sWorker,
            ServerRole::Postgres,
            ServerRole::MySQL,
            ServerRole::MongoDB,
            ServerRole::Redis,
            ServerRole::Memcached,
            ServerRole::RabbitMQ,
            ServerRole::Kafka,
            ServerRole::Elasticsearch,
            ServerRole::LoadBalancer,
            ServerRole::Monitoring,
            ServerRole::CIRunner,
            ServerRole::Bastion,
            ServerRole::Storage,
            ServerRole::Custom,
        ]
    }

    /// Get display name for the role
    pub fn display_name(&self) -> &'static str {
        match self {
            ServerRole::K3sControl => "K3s Control Plane",
            ServerRole::K3sWorker => "K3s Worker Node",
            ServerRole::Postgres => "PostgreSQL Database",
            ServerRole::MySQL => "MySQL/MariaDB Database",
            ServerRole::MongoDB => "MongoDB Database",
            ServerRole::Redis => "Redis Cache",
            ServerRole::Memcached => "Memcached Cache",
            ServerRole::RabbitMQ => "RabbitMQ Message Broker",
            ServerRole::Kafka => "Kafka Message Broker",
            ServerRole::Elasticsearch => "Elasticsearch Search Engine",
            ServerRole::LoadBalancer => "Load Balancer (HAProxy/Nginx)",
            ServerRole::Monitoring => "Monitoring (Prometheus/Grafana)",
            ServerRole::CIRunner => "CI/CD Runner (GitLab Runner)",
            ServerRole::Bastion => "Bastion/Jump Server",
            ServerRole::Storage => "Storage (MinIO/NFS)",
            ServerRole::Custom => "Custom/Generic Server",
        }
    }

    /// Get description for the role
    pub fn description(&self) -> &'static str {
        match self {
            ServerRole::K3sControl => "Control plane node for K3s cluster management",
            ServerRole::K3sWorker => "Worker node for running application workloads",
            ServerRole::Postgres => "PostgreSQL relational database server",
            ServerRole::MySQL => "MySQL or MariaDB relational database server",
            ServerRole::MongoDB => "MongoDB NoSQL document database",
            ServerRole::Redis => "In-memory data store and cache",
            ServerRole::Memcached => "Distributed memory caching system",
            ServerRole::RabbitMQ => "Message broker for async communication",
            ServerRole::Kafka => "Distributed streaming platform and message broker",
            ServerRole::Elasticsearch => "Distributed search and analytics engine",
            ServerRole::LoadBalancer => "Traffic distribution and load balancing",
            ServerRole::Monitoring => "Metrics collection and visualization",
            ServerRole::CIRunner => "Continuous integration and deployment runner",
            ServerRole::Bastion => "Secure SSH gateway for infrastructure access",
            ServerRole::Storage => "Object storage or network file system",
            ServerRole::Custom => "Custom server with user-defined purpose",
        }
    }
}

/// Setup mode for infrastructure configuration
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetupMode {
    /// Quick setup - single K3s server (development/testing)
    Quick,
    /// Standard setup - HA production with control plane, workers, and database
    Standard,
    /// Advanced setup - full control over server topology
    Advanced,
}

impl SetupMode {
    /// Get all available setup modes
    pub fn all() -> Vec<Self> {
        vec![SetupMode::Quick, SetupMode::Standard, SetupMode::Advanced]
    }

    /// Get display name for the mode
    pub fn display_name(&self) -> &'static str {
        match self {
            SetupMode::Quick => "Quick - Single Server (Development)",
            SetupMode::Standard => "Standard - Multi-Server HA (Production)",
            SetupMode::Advanced => "Advanced - Custom Topology",
        }
    }

    /// Get description for the mode
    pub fn description(&self) -> &'static str {
        match self {
            SetupMode::Quick => {
                "Single K3s server for development and testing. Fastest to set up, lowest cost."
            }
            SetupMode::Standard => {
                "Production-ready HA setup with control plane, workers, and dedicated database server."
            }
            SetupMode::Advanced => {
                "Full control over infrastructure topology. Configure multiple server groups with custom roles."
            }
        }
    }
}

/// K3s cluster configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct K3sConfig {
    /// K3s version (e.g., "v1.28.5+k3s1")
    pub version: String,

    /// Server groups that are part of the cluster
    #[validate(length(min = 1))]
    pub deploy_on: Vec<String>,

    /// Server groups that are control plane nodes
    #[validate(length(min = 1))]
    pub control_plane_servers: Vec<String>,

    /// Server groups that are worker nodes (can be empty for single-node clusters)
    #[serde(default)]
    pub worker_servers: Vec<String>,

    /// Enable Traefik ingress controller
    #[serde(default = "default_true")]
    pub enable_traefik: bool,

    /// Enable metrics server
    #[serde(default)]
    pub enable_metrics_server: bool,

    /// Additional flags for k3s server
    #[serde(default)]
    pub server_flags: Vec<String>,

    /// Additional flags for k3s agent
    #[serde(default)]
    pub agent_flags: Vec<String>,
}

fn default_true() -> bool {
    true
}

/// PostgreSQL configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct PostgresConfig {
    /// PostgreSQL version
    pub version: String,

    /// Database name
    #[validate(length(min = 1, max = 63))]
    pub database_name: String,

    /// Deployment mode
    pub deployment_mode: PostgresDeploymentMode,

    /// Configuration for standalone mode
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(nested)]
    pub standalone: Option<PostgresStandaloneConfig>,

    /// Configuration for in-cluster mode
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(nested)]
    pub in_cluster: Option<PostgresInClusterConfig>,
}

/// PostgreSQL deployment mode
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum PostgresDeploymentMode {
    /// Standalone server (recommended for production)
    Standalone,
    /// Deployed in Kubernetes cluster
    InCluster,
}

/// PostgreSQL standalone deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct PostgresStandaloneConfig {
    /// Server group to deploy on
    #[validate(length(min = 1))]
    pub deploy_on: String,

    /// Data directory (optional, uses default if not specified)
    pub data_dir: Option<String>,

    /// Max connections (optional, uses default if not specified)
    pub max_connections: Option<u32>,

    /// Shared buffers setting (e.g., "256MB")
    pub shared_buffers: Option<String>,
}

/// PostgreSQL in-cluster deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct PostgresInClusterConfig {
    /// Namespace to deploy in
    #[validate(length(min = 1))]
    pub namespace: String,

    /// Storage class for persistent volume
    pub storage_class: String,

    /// Storage size (e.g., "20Gi")
    pub storage_size: String,

    /// Use operator (e.g., Zalando postgres-operator)
    #[serde(default)]
    pub use_operator: bool,
}

/// RabbitMQ configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct RabbitMqConfig {
    /// RabbitMQ version
    pub version: String,

    /// Deployment mode
    pub deployment_mode: RabbitMqDeploymentMode,

    /// Configuration for standalone mode
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(nested)]
    pub standalone: Option<RabbitMqStandaloneConfig>,

    /// Configuration for in-cluster mode
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(nested)]
    pub in_cluster: Option<RabbitMqInClusterConfig>,
}

/// RabbitMQ deployment mode
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum RabbitMqDeploymentMode {
    /// Standalone server (recommended for production)
    Standalone,
    /// Deployed in Kubernetes cluster
    InCluster,
}

/// RabbitMQ standalone deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct RabbitMqStandaloneConfig {
    /// Server group to deploy on
    #[validate(length(min = 1))]
    pub deploy_on: String,

    /// Enable management plugin
    #[serde(default = "default_true")]
    pub enable_management: bool,

    /// Management plugin port (default 15672)
    #[serde(default = "default_management_port")]
    pub management_port: u16,

    /// AMQP port (default 5672)
    #[serde(default = "default_amqp_port")]
    pub amqp_port: u16,
}

fn default_management_port() -> u16 {
    15672
}

fn default_amqp_port() -> u16 {
    5672
}

/// RabbitMQ in-cluster deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct RabbitMqInClusterConfig {
    /// Namespace to deploy in
    #[validate(length(min = 1))]
    pub namespace: String,

    /// Storage class for persistent volume
    pub storage_class: String,

    /// Storage size (e.g., "10Gi")
    pub storage_size: String,

    /// Number of replicas (for HA)
    #[serde(default = "default_rabbitmq_replicas")]
    #[validate(range(min = 1, max = 10))]
    pub replicas: u32,

    /// Use RabbitMQ cluster operator
    #[serde(default)]
    pub use_operator: bool,
}

fn default_rabbitmq_replicas() -> u32 {
    1
}

/// Vault configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct VaultConfig {
    /// Vault version
    pub version: String,

    /// Deployment mode
    pub deployment_mode: VaultDeploymentMode,

    /// Configuration for standalone mode
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(nested)]
    pub standalone: Option<VaultStandaloneConfig>,

    /// Configuration for in-cluster mode
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(nested)]
    pub in_cluster: Option<VaultInClusterConfig>,
}

/// Vault deployment mode
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum VaultDeploymentMode {
    /// Standalone server (recommended for production)
    Standalone,
    /// Deployed in Kubernetes cluster
    InCluster,
}

/// Vault standalone deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct VaultStandaloneConfig {
    /// Server group to deploy on
    #[validate(length(min = 1))]
    pub deploy_on: String,

    /// Enable Vault UI
    #[serde(default = "default_true")]
    pub enable_ui: bool,

    /// API port (default 8200)
    #[serde(default = "default_vault_port")]
    pub api_port: u16,
}

fn default_vault_port() -> u16 {
    8200
}

/// Vault in-cluster deployment configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct VaultInClusterConfig {
    /// Namespace to deploy in
    #[validate(length(min = 1))]
    pub namespace: String,

    /// Number of replicas (for HA)
    #[serde(default = "default_vault_replicas")]
    #[validate(range(min = 1, max = 5))]
    pub replicas: u32,

    /// Storage size per replica (e.g., "10Gi")
    #[serde(default = "default_vault_storage")]
    pub storage_size: String,

    /// Storage class for persistent volume
    pub storage_class: Option<String>,

    /// Enable Vault UI
    #[serde(default = "default_true")]
    pub enable_ui: bool,

    /// Service type (ClusterIP, NodePort, LoadBalancer)
    #[serde(default = "default_service_type")]
    pub service_type: String,

    /// NodePort for Vault API (if service_type is NodePort)
    pub node_port: Option<u16>,

    /// Ingress hostname for external access
    pub ingress_host: Option<String>,

    /// Enable TLS for ingress
    #[serde(default = "default_true")]
    pub enable_tls: bool,
}

fn default_vault_replicas() -> u32 {
    1
}

fn default_vault_storage() -> String {
    "10Gi".to_string()
}

fn default_service_type() -> String {
    "ClusterIP".to_string()
}

/// DNS configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct DnsConfig {
    /// DNS provider (e.g., "cloudflare", "route53")
    pub provider: String,

    /// Primary domain
    #[validate(length(min = 1))]
    pub domain: String,

    /// DNS records
    #[serde(default)]
    pub records: Vec<DnsRecord>,
}

/// DNS record configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct DnsRecord {
    /// Record type (A, AAAA, CNAME, MX, TXT, etc.)
    #[serde(rename = "type")]
    pub record_type: String,

    /// Record name (e.g., "@", "www", "api")
    #[validate(length(min = 1))]
    pub name: String,

    /// Target - can be:
    /// - Server group name (will use public IP)
    /// - IP address
    /// - Domain name (for CNAME)
    pub target: String,

    /// TTL in seconds
    #[serde(default = "default_ttl")]
    pub ttl: u32,

    /// Enable Cloudflare proxy (Cloudflare-specific)
    #[serde(default)]
    pub proxied: bool,
}

fn default_ttl() -> u32 {
    300
}

/// GitLab configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct GitLabConfig {
    /// GitLab instance URL
    #[validate(url)]
    pub url: String,

    /// Project namespace/group
    #[validate(length(min = 1))]
    pub namespace: String,
}

/// Load balancer configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct LoadBalancerConfig {
    /// Load balancer name
    #[validate(length(min = 1, max = 50))]
    pub name: String,

    /// Load balancer type (e.g., "lb11", "lb21" for Hetzner)
    #[serde(rename = "type")]
    pub lb_type: String,

    /// Location/region (e.g., "nbg1", "fsn1" for Hetzner)
    pub location: String,

    /// Load balancing algorithm (e.g., "round_robin", "least_connections")
    pub algorithm: String,

    /// Target servers for load balancer
    #[validate(length(min = 1))]
    pub targets: Vec<LoadBalancerTarget>,

    /// Services/ports to expose
    #[validate(length(min = 1))]
    pub services: Vec<LoadBalancerService>,
}

/// Load balancer target configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct LoadBalancerTarget {
    /// Target type (e.g., "server", "label_selector", "ip")
    #[serde(rename = "type")]
    pub target_type: String,

    /// Use private IP for target
    #[serde(default)]
    pub use_private_ip: bool,

    /// Server group names to use as targets
    #[validate(length(min = 1))]
    pub servers: Vec<String>,
}

/// Load balancer service/port configuration
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct LoadBalancerService {
    /// Protocol (http, https, tcp)
    pub protocol: String,

    /// Listen port (port on load balancer)
    #[validate(range(min = 1, max = 65535))]
    pub listen_port: u16,

    /// Destination port (port on target servers)
    #[validate(range(min = 1, max = 65535))]
    pub destination_port: u16,

    /// Health check protocol (http, https, tcp)
    pub health_check_protocol: String,

    /// Health check port
    #[validate(range(min = 1, max = 65535))]
    pub health_check_port: u16,

    /// Health check path (for HTTP/HTTPS)
    #[serde(default = "default_health_check_path")]
    pub health_check_path: String,

    /// Health check interval in seconds
    #[serde(default = "default_health_check_interval")]
    #[validate(range(min = 1, max = 3600))]
    pub health_check_interval: u64,

    /// Health check timeout in seconds
    #[serde(default = "default_health_check_timeout")]
    #[validate(range(min = 1, max = 300))]
    pub health_check_timeout: u64,

    /// Health check retries
    #[serde(default = "default_health_check_retries")]
    #[validate(range(min = 1, max = 10))]
    pub health_check_retries: u64,

    /// Enable HTTP to HTTPS redirect (for HTTPS services)
    #[serde(default)]
    pub http_redirect_http_to_https: bool,
}

fn default_health_check_path() -> String {
    "/".to_string()
}

fn default_health_check_interval() -> u64 {
    15
}

fn default_health_check_timeout() -> u64 {
    10
}

fn default_health_check_retries() -> u64 {
    3
}

/// Configuration validation errors
#[derive(Debug, Error)]
pub enum ConfigError {
    /// Invalid TOML format
    #[error("Invalid TOML format: {0}")]
    InvalidToml(String),

    /// TOML deserialization error
    #[error("TOML deserialization error: {0}")]
    TomlDe(#[from] toml::de::Error),

    /// Validation failed
    #[error("Configuration validation failed: {0}")]
    ValidationFailed(#[from] validator::ValidationErrors),

    /// IO error
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    /// Missing required provider configuration
    #[error("Missing required configuration for provider: {0}")]
    MissingProviderConfig(String),

    /// Architecture principle violation
    #[error("Architecture violation: {0}")]
    ArchitectureViolation(String),
}

impl LmrcConfig {
    /// Load configuration from a TOML file
    pub fn from_file(path: &std::path::Path) -> Result<Self, ConfigError> {
        let content = std::fs::read_to_string(path)?;
        Self::from_toml_str(&content)
    }

    /// Parse configuration from a TOML string
    pub fn from_toml_str(content: &str) -> Result<Self, ConfigError> {
        let config: Self = toml::from_str(content)?;
        config.validate()?;
        config.validate_provider_configs()?;
        Ok(config)
    }

    /// Validate that provider-specific configs are present when providers are selected
    fn validate_provider_configs(&self) -> Result<(), ConfigError> {
        // Validate server provider config
        if self.infrastructure.servers.is_empty() {
            return Err(ConfigError::MissingProviderConfig(
                "servers (at least one server group required)".to_string(),
            ));
        }

        // Validate kubernetes provider config
        if self.providers.kubernetes == "k3s" && self.infrastructure.k3s.is_none() {
            return Err(ConfigError::MissingProviderConfig("k3s".to_string()));
        }

        // Validate database provider config
        if self.providers.database == "postgres" && self.infrastructure.postgres.is_none() {
            return Err(ConfigError::MissingProviderConfig("postgres".to_string()));
        }

        // Validate DNS provider config
        if self.infrastructure.dns.is_none() {
            return Err(ConfigError::MissingProviderConfig("dns".to_string()));
        }

        // Validate Git provider config
        if self.providers.git == "gitlab" && self.infrastructure.gitlab.is_none() {
            return Err(ConfigError::MissingProviderConfig("gitlab".to_string()));
        }

        // Validate PostgreSQL deployment mode consistency
        if let Some(postgres) = &self.infrastructure.postgres {
            match postgres.deployment_mode {
                PostgresDeploymentMode::Standalone => {
                    if postgres.standalone.is_none() {
                        return Err(ConfigError::InvalidToml(
                            "PostgreSQL standalone mode requires 'standalone' configuration"
                                .to_string(),
                        ));
                    }
                    // Validate that deploy_on references a valid server group
                    if let Some(standalone) = &postgres.standalone
                        && !self
                            .infrastructure
                            .servers
                            .iter()
                            .any(|s| s.name == standalone.deploy_on)
                    {
                        return Err(ConfigError::InvalidToml(format!(
                            "PostgreSQL standalone deploy_on '{}' does not reference a valid server group",
                            standalone.deploy_on
                        )));
                    }
                }
                PostgresDeploymentMode::InCluster => {
                    // ARCHITECTURE PRINCIPLE: Stateless Kubernetes - No In-Cluster Services
                    return Err(ConfigError::ArchitectureViolation(
                        "PostgreSQL in-cluster deployment is not allowed. \
                        LMRC Stack architecture principle: 'Stateless Kubernetes - No In-Cluster Services'. \
                        Stateful services (databases, message queues) must run on dedicated servers, \
                        not in Kubernetes. Please use deployment_mode = 'standalone' instead. \
                        See docs/architecture/PRINCIPLES.md for details.".to_string()
                    ));
                }
            }
        }

        // Validate RabbitMQ deployment mode consistency
        if let Some(rabbitmq) = &self.infrastructure.rabbitmq {
            match rabbitmq.deployment_mode {
                RabbitMqDeploymentMode::Standalone => {
                    if rabbitmq.standalone.is_none() {
                        return Err(ConfigError::InvalidToml(
                            "RabbitMQ standalone mode requires 'standalone' configuration"
                                .to_string(),
                        ));
                    }
                    // Validate that deploy_on references a valid server group
                    if let Some(standalone) = &rabbitmq.standalone
                        && !self
                            .infrastructure
                            .servers
                            .iter()
                            .any(|s| s.name == standalone.deploy_on)
                    {
                        return Err(ConfigError::InvalidToml(format!(
                            "RabbitMQ standalone deploy_on '{}' does not reference a valid server group",
                            standalone.deploy_on
                        )));
                    }
                }
                RabbitMqDeploymentMode::InCluster => {
                    // ARCHITECTURE PRINCIPLE: Stateless Kubernetes - No In-Cluster Services
                    return Err(ConfigError::ArchitectureViolation(
                        "RabbitMQ in-cluster deployment is not allowed. \
                        LMRC Stack architecture principle: 'Stateless Kubernetes - No In-Cluster Services'. \
                        Stateful services (databases, message queues) must run on dedicated servers, \
                        not in Kubernetes. Please use deployment_mode = 'standalone' instead. \
                        See docs/architecture/PRINCIPLES.md for details.".to_string()
                    ));
                }
            }
        }

        // Validate Vault deployment mode consistency
        if let Some(vault) = &self.infrastructure.vault {
            match vault.deployment_mode {
                VaultDeploymentMode::Standalone => {
                    if vault.standalone.is_none() {
                        return Err(ConfigError::InvalidToml(
                            "Vault standalone mode requires 'standalone' configuration"
                                .to_string(),
                        ));
                    }
                    // Validate that deploy_on references a valid server group
                    if let Some(standalone) = &vault.standalone
                        && !self
                            .infrastructure
                            .servers
                            .iter()
                            .any(|s| s.name == standalone.deploy_on)
                    {
                        return Err(ConfigError::InvalidToml(format!(
                            "Vault standalone deploy_on '{}' does not reference a valid server group",
                            standalone.deploy_on
                        )));
                    }
                }
                VaultDeploymentMode::InCluster => {
                    if vault.in_cluster.is_none() {
                        return Err(ConfigError::InvalidToml(
                            "Vault in-cluster mode requires 'in_cluster' configuration"
                                .to_string(),
                        ));
                    }
                }
            }
        }

        // Validate Load Balancer configuration
        if let Some(lb) = &self.infrastructure.load_balancer {
            // Validate that target servers reference valid server groups
            for target in &lb.targets {
                for server_name in &target.servers {
                    if !self
                        .infrastructure
                        .servers
                        .iter()
                        .any(|s| s.name == *server_name)
                    {
                        return Err(ConfigError::InvalidToml(format!(
                            "Load balancer target server '{}' does not reference a valid server group",
                            server_name
                        )));
                    }
                }
            }
        }

        // Validate K3s server group references
        if let Some(k3s) = &self.infrastructure.k3s {
            for server_group in &k3s.deploy_on {
                if !self
                    .infrastructure
                    .servers
                    .iter()
                    .any(|s| s.name == *server_group)
                {
                    return Err(ConfigError::InvalidToml(format!(
                        "K3s deploy_on '{}' does not reference a valid server group",
                        server_group
                    )));
                }
            }
            for server_group in &k3s.control_plane_servers {
                if !self
                    .infrastructure
                    .servers
                    .iter()
                    .any(|s| s.name == *server_group)
                {
                    return Err(ConfigError::InvalidToml(format!(
                        "K3s control_plane_servers '{}' does not reference a valid server group",
                        server_group
                    )));
                }
            }
            for server_group in &k3s.worker_servers {
                if !self
                    .infrastructure
                    .servers
                    .iter()
                    .any(|s| s.name == *server_group)
                {
                    return Err(ConfigError::InvalidToml(format!(
                        "K3s worker_servers '{}' does not reference a valid server group",
                        server_group
                    )));
                }
            }
        }

        Ok(())
    }

    /// Generate a default/template configuration
    pub fn template() -> Self {
        let mut labels = HashMap::new();
        labels.insert("environment".to_string(), "production".to_string());

        Self {
            project: ProjectConfig {
                name: "my-project".to_string(),
                description: "My LMRC Stack project".to_string(),
            },
            providers: ProviderConfig {
                server: "hetzner".to_string(),
                kubernetes: "k3s".to_string(),
                database: "postgres".to_string(),
                queue: "rabbitmq".to_string(),
                dns: "cloudflare".to_string(),
                git: "gitlab".to_string(),
            },
            apps: AppsConfig {
                applications: vec![ApplicationEntry {
                    name: "api".to_string(),
                    app_type: Some(AppType::Api),
                    docker: Some(DockerConfig {
                        dockerfile: "apps/api/Dockerfile".to_string(),
                        context: "apps/api".to_string(),
                        tags: vec!["latest".to_string()],
                    }),
                    deployment: Some(DeploymentConfig {
                        replicas: 2,
                        port: 8080,
                        cpu_request: Some("100m".to_string()),
                        memory_request: Some("128Mi".to_string()),
                        cpu_limit: Some("1".to_string()),
                        memory_limit: Some("512Mi".to_string()),
                        env: vec![],
                    }),
                }],
            },
            infrastructure: InfrastructureConfig {
                provider: "hetzner".to_string(),
                network: Some(NetworkConfig {
                    enable_private_network: true,
                    private_network: Some("10.0.0.0/16".to_string()),
                    firewall_rules: vec![],
                }),
                servers: vec![
                    ServerGroup {
                        name: "k3s-control".to_string(),
                        role: ServerRole::K3sControl,
                        server_type: "cpx11".to_string(),
                        location: "nbg1".to_string(),
                        count: 1,
                        labels: labels.clone(),
                        ssh_keys: vec![],
                        image: None,
                    },
                    ServerGroup {
                        name: "k3s-workers".to_string(),
                        role: ServerRole::K3sWorker,
                        server_type: "cx21".to_string(),
                        location: "nbg1".to_string(),
                        count: 2,
                        labels: labels.clone(),
                        ssh_keys: vec![],
                        image: None,
                    },
                    ServerGroup {
                        name: "postgres-server".to_string(),
                        role: ServerRole::Postgres,
                        server_type: "cx31".to_string(),
                        location: "nbg1".to_string(),
                        count: 1,
                        labels,
                        ssh_keys: vec![],
                        image: None,
                    },
                ],
                k3s: Some(K3sConfig {
                    version: "v1.28.5+k3s1".to_string(),
                    deploy_on: vec!["k3s-control".to_string(), "k3s-workers".to_string()],
                    control_plane_servers: vec!["k3s-control".to_string()],
                    worker_servers: vec!["k3s-workers".to_string()],
                    enable_traefik: true,
                    enable_metrics_server: false,
                    server_flags: vec![],
                    agent_flags: vec![],
                }),
                postgres: Some(PostgresConfig {
                    version: "16".to_string(),
                    database_name: "myapp".to_string(),
                    deployment_mode: PostgresDeploymentMode::Standalone,
                    standalone: Some(PostgresStandaloneConfig {
                        deploy_on: "postgres-server".to_string(),
                        data_dir: None,
                        max_connections: None,
                        shared_buffers: None,
                    }),
                    in_cluster: None,
                }),
                rabbitmq: None,
                vault: None,
                dns: Some(DnsConfig {
                    provider: "cloudflare".to_string(),
                    domain: "example.com".to_string(),
                    records: vec![
                        DnsRecord {
                            record_type: "A".to_string(),
                            name: "@".to_string(),
                            target: "k3s-control".to_string(),
                            ttl: 300,
                            proxied: true,
                        },
                        DnsRecord {
                            record_type: "A".to_string(),
                            name: "api".to_string(),
                            target: "k3s-control".to_string(),
                            ttl: 300,
                            proxied: true,
                        },
                    ],
                }),
                gitlab: Some(GitLabConfig {
                    url: "https://gitlab.com".to_string(),
                    namespace: "mygroup".to_string(),
                }),
                load_balancer: None,
            },
        }
    }

    /// Convert config to TOML string
    pub fn to_toml_string(&self) -> Result<String, ConfigError> {
        toml::to_string_pretty(self).map_err(|e| ConfigError::InvalidToml(e.to_string()))
    }
}

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

    #[test]
    fn test_template_config_is_valid() {
        let config = LmrcConfig::template();
        assert!(config.validate().is_ok());
        assert!(config.validate_provider_configs().is_ok());
    }

    #[test]
    fn test_config_to_toml_and_back() {
        let config = LmrcConfig::template();
        let toml_str = config.to_toml_string().unwrap();
        let parsed = LmrcConfig::from_toml_str(&toml_str).unwrap();
        assert_eq!(config.project.name, parsed.project.name);
    }

    #[test]
    fn test_missing_provider_config() {
        let mut config = LmrcConfig::template();
        config.infrastructure.servers = vec![];
        assert!(config.validate_provider_configs().is_err());
    }

    #[test]
    fn test_invalid_server_group_reference() {
        let mut config = LmrcConfig::template();
        if let Some(ref mut postgres) = config.infrastructure.postgres {
            if let Some(ref mut standalone) = postgres.standalone {
                standalone.deploy_on = "nonexistent-server".to_string();
            }
        }
        assert!(config.validate_provider_configs().is_err());
    }
}
