//! Configuration definitions and structures for the server
//!
//! This module contains all the configuration struct definitions and their
//! implementations, except for the main Config struct which is in config.rs.

use std::path::PathBuf;

use serde::{Deserialize, Serialize};

/// Server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    pub protocol: Protocol,
    pub host: String,
    pub port: u16,
    pub environment: Environment,
    pub log_level: String,
    pub tls: Option<TlsConfig>,
    #[serde(default = "rustelo_core_lib::AssetMode::default_for_environment")]
    pub asset_mode: rustelo_core_lib::AssetMode,
}

fn default_create_if_missing() -> bool {
    true
}

/// Database configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
    pub url: String,
    pub max_connections: u32,
    pub min_connections: u32,
    pub connect_timeout: u64,
    pub idle_timeout: u64,
    pub max_lifetime: u64,
    /// Create the database file on first use (SQLite only; ignored for
    /// PostgreSQL).
    #[serde(default = "default_create_if_missing")]
    pub create_if_missing: bool,
}

/// Session configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionConfig {
    pub secret: String,
    pub cookie_name: String,
    pub cookie_secure: bool,
    pub cookie_http_only: bool,
    pub cookie_same_site: String,
    pub max_age: u64,
}

/// CORS configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorsConfig {
    pub allowed_origins: Vec<String>,
    pub allowed_methods: Vec<String>,
    pub allowed_headers: Vec<String>,
    pub allow_credentials: bool,
    pub max_age: u64,
}

/// Static files configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StaticConfig {
    pub assets_dir: String,
    pub site_root: String,
    pub site_pkg_dir: String,
}

/// Server directories configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerDirConfig {
    pub public_dir: String,
    pub uploads_dir: String,
    pub logs_dir: String,
    pub temp_dir: String,
    pub cache_dir: String,
    pub config_dir: String,
    pub data_dir: String,
    pub backup_dir: String,
    pub template_dir: Option<String>,
}

/// Security configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
    pub enable_csrf: bool,
    pub csrf_token_name: String,
    pub rate_limit_requests: u32,
    pub rate_limit_window: u64,
    pub bcrypt_cost: u32,
}

/// OAuth configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OAuthConfig {
    #[serde(default)]
    pub enabled: bool,
    pub google: Option<OAuthProvider>,
    pub github: Option<OAuthProvider>,
}

/// OAuth provider configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthProvider {
    pub client_id: String,
    pub client_secret: String,
    pub redirect_uri: String,
}

/// Email configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailConfig {
    pub enabled: bool,
    pub provider: String,
    pub smtp_host: String,
    pub smtp_port: u16,
    pub smtp_username: String,
    pub smtp_password: String,
    pub smtp_use_tls: bool,
    pub smtp_use_starttls: bool,
    pub sendgrid_api_key: String,
    pub sendgrid_endpoint: String,
    pub from_email: String,
    pub from_name: String,
    pub template_dir: Option<String>,
    pub email_enabled: Option<bool>,
}

/// Redis configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedisConfig {
    pub enabled: bool,
    pub url: String,
    pub pool_size: u32,
    pub connection_timeout: u64,
    pub command_timeout: u64,
}

/// Application configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
    pub name: String,
    pub version: String,
    pub debug: bool,
    pub enable_metrics: bool,
    pub enable_health_check: bool,
    pub enable_compression: bool,
    pub max_request_size: u64,
    pub admin_email: Option<String>,
}

/// Logging configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    pub format: String,
    pub level: String,
    pub file_path: String,
    pub max_file_size: u64,
    pub max_files: u32,
    pub enable_console: bool,
    pub enable_file: bool,
}

/// Content configuration (feature-gated)
#[cfg(feature = "content-db")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentConfig {
    pub enabled: bool,
    pub content_dir: String,
    pub cache_enabled: bool,
    pub cache_ttl: u64,
    pub max_file_size: u64,
}

/// Cache configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
    pub persistent_enabled: bool,
    pub memory_enabled: bool,
    pub memory_max_size: u64,
    pub memory_ttl: u64,
    pub persistent_ttl: u64,
    pub persistent_max_size: u64,
    pub persistent_cleanup_interval: u64,
}

/// Routing configuration for navigation tracking and caching
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutingConfig {
    pub enable_tracking: Option<bool>,
    pub tracking_log_path: Option<String>,
    pub max_events_in_memory: Option<usize>,
    pub slow_resolution_threshold_ms: Option<u64>,
    pub enable_console_debug: Option<bool>,
    pub cache_max_entries: Option<usize>,
    pub cache_ttl_seconds: Option<u64>,
}

impl Default for RoutingConfig {
    fn default() -> Self {
        Self {
            enable_tracking: Some(true),
            tracking_log_path: Some("logs/navigation.jsonl".to_string()),
            max_events_in_memory: Some(1000),
            slow_resolution_threshold_ms: Some(10),
            enable_console_debug: Some(cfg!(debug_assertions)),
            cache_max_entries: Some(1000),
            cache_ttl_seconds: Some(3600),
        }
    }
}

/// TLS configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
    pub cert_path: PathBuf,
    pub key_path: PathBuf,
}

/// Protocol enum
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Protocol {
    Http,
    Https,
}

/// Environment enum
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Environment {
    Development,
    Production,
}

/// Server directory type enum
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerDirType {
    Public,
    Uploads,
    Logs,
    Temp,
    Cache,
    Config,
    Data,
    Backup,
}

/// Configuration error types
#[derive(Debug)]
pub enum ConfigError {
    ReadError(String),
    ParseError(String),
    ValidationError(String),
    #[allow(dead_code)]
    InvalidPort(u16),
    MissingFile(String),
    #[allow(dead_code)]
    DirectoryCreationError(String),
    #[allow(dead_code)]
    MissingTlsCert(String),
    #[allow(dead_code)]
    EncryptionError(String),
}

impl std::fmt::Display for ConfigError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConfigError::ReadError(msg) => write!(f, "Read error: {msg}"),
            ConfigError::ParseError(msg) => write!(f, "Parse error: {msg}"),
            ConfigError::ValidationError(msg) => write!(f, "Validation error: {msg}"),
            ConfigError::InvalidPort(port) => write!(f, "Invalid port number: {port}"),
            ConfigError::MissingFile(file) => write!(f, "Missing file: {file}"),
            ConfigError::DirectoryCreationError(msg) => {
                write!(f, "Directory creation error: {msg}")
            }
            ConfigError::MissingTlsCert(msg) => write!(f, "Missing TLS certificate: {msg}"),
            ConfigError::EncryptionError(msg) => write!(f, "Encryption error: {msg}"),
        }
    }
}

impl std::error::Error for ConfigError {}

/// Server-side cache configuration that matches TOML structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerCacheConfig {
    /// Enable persistent disk-based caching
    pub persistent_enabled: bool,
    /// Memory cache settings
    pub memory_enabled: bool,
    pub memory_max_size: u64,
    pub memory_ttl: u64,
    /// Persistent cache settings
    pub persistent_ttl: u64,
    pub persistent_max_size: u64,
    pub persistent_cleanup_interval: u64,
}

impl Default for ServerCacheConfig {
    fn default() -> Self {
        Self {
            persistent_enabled: false,
            memory_enabled: true,
            memory_max_size: 104857600,        // 100MB
            memory_ttl: 3600,                  // 1 hour
            persistent_ttl: 86400,             // 24 hours
            persistent_max_size: 1073741824,   // 1GB
            persistent_cleanup_interval: 3600, // 1 hour
        }
    }
}

// Default implementations

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            protocol: Protocol::Http,
            host: "127.0.0.1".to_string(),
            port: 3030,
            environment: Environment::Development,
            log_level: "info".to_string(),
            tls: None,
            asset_mode: rustelo_core_lib::AssetMode::default_for_environment(),
        }
    }
}

impl Default for DatabaseConfig {
    fn default() -> Self {
        Self {
            url: "postgresql://postgres:password@localhost/rustelo".to_string(),
            max_connections: 10,
            min_connections: 2,
            connect_timeout: 30,
            idle_timeout: 600,
            max_lifetime: 1800,
            create_if_missing: true,
        }
    }
}

impl Default for SessionConfig {
    fn default() -> Self {
        Self {
            secret: "your-secret-key-change-me-in-production".to_string(),
            cookie_name: "rustelo_session".to_string(),
            cookie_secure: false,
            cookie_http_only: true,
            cookie_same_site: "lax".to_string(),
            max_age: 86400,
        }
    }
}

impl Default for CorsConfig {
    fn default() -> Self {
        Self {
            allowed_origins: vec!["*".to_string()],
            allowed_methods: vec![
                "GET".to_string(),
                "POST".to_string(),
                "PUT".to_string(),
                "DELETE".to_string(),
                "OPTIONS".to_string(),
            ],
            allowed_headers: vec!["*".to_string()],
            allow_credentials: true,
            max_age: 3600,
        }
    }
}

impl Default for StaticConfig {
    fn default() -> Self {
        Self {
            assets_dir: "public".to_string(),
            site_root: "target/site".to_string(),
            site_pkg_dir: "pkg".to_string(),
        }
    }
}

impl Default for ServerDirConfig {
    fn default() -> Self {
        Self {
            public_dir: "public".to_string(),
            uploads_dir: "uploads".to_string(),
            logs_dir: "logs".to_string(),
            temp_dir: "temp".to_string(),
            cache_dir: "cache".to_string(),
            config_dir: "config".to_string(),
            data_dir: "data".to_string(),
            backup_dir: "backups".to_string(),
            template_dir: Some("templates".to_string()),
        }
    }
}

impl Default for SecurityConfig {
    fn default() -> Self {
        Self {
            enable_csrf: true,
            csrf_token_name: "csrf_token".to_string(),
            rate_limit_requests: 100,
            rate_limit_window: 60,
            bcrypt_cost: 12,
        }
    }
}

impl Default for EmailConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            provider: "smtp".to_string(),
            smtp_host: "smtp.gmail.com".to_string(),
            smtp_port: 587,
            smtp_username: String::new(),
            smtp_password: String::new(),
            smtp_use_tls: false,
            smtp_use_starttls: true,
            sendgrid_api_key: String::new(),
            sendgrid_endpoint: "https://api.sendgrid.com/v3/mail/send".to_string(),
            from_email: "noreply@example.com".to_string(),
            from_name: "Rustelo".to_string(),
            template_dir: Some("templates".to_string()),
            email_enabled: Some(false),
        }
    }
}

impl Default for RedisConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            url: "redis://127.0.0.1:6379".to_string(),
            pool_size: 10,
            connection_timeout: 5,
            command_timeout: 5,
        }
    }
}

impl Default for AppConfig {
    fn default() -> Self {
        Self {
            name: "My Rust App".to_string(),
            version: "0.1.0".to_string(),
            debug: true,
            enable_metrics: false,
            enable_health_check: true,
            enable_compression: true,
            max_request_size: 10 * 1024 * 1024, // 10MB
            admin_email: None,
        }
    }
}

impl Default for LoggingConfig {
    fn default() -> Self {
        Self {
            format: "json".to_string(),
            level: "info".to_string(),
            file_path: "logs/app.log".to_string(),
            max_file_size: 10 * 1024 * 1024, // 10MB
            max_files: 10,
            enable_console: true,
            enable_file: false,
        }
    }
}

#[cfg(feature = "content-db")]
impl Default for ContentConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            content_dir: "content".to_string(),
            cache_enabled: true,
            cache_ttl: 3600,
            max_file_size: 5 * 1024 * 1024, // 5MB
        }
    }
}

impl Default for CacheConfig {
    fn default() -> Self {
        Self {
            persistent_enabled: false,
            memory_enabled: true,
            memory_max_size: 104857600,        // 100MB
            memory_ttl: 3600,                  // 1 hour
            persistent_ttl: 86400,             // 24 hours
            persistent_max_size: 1073741824,   // 1GB
            persistent_cleanup_interval: 3600, // 1 hour
        }
    }
}

/// File-based RBAC policy configuration.
#[cfg(feature = "rbac")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RbacNclConfig {
    /// Path to the `rbac.ncl` policy file.
    /// Supports `{SITE_CONTENT_PATH}` — resolved at load time via
    /// `default_rbac_ncl_path`.
    #[serde(default = "default_rbac_ncl_path")]
    pub ncl_path: String,
}

#[cfg(feature = "rbac")]
fn default_rbac_ncl_path() -> String {
    std::env::var("RBAC_NCL_PATH").unwrap_or_else(|_| "./site/rbac.ncl".to_string())
}

#[cfg(feature = "rbac")]
impl Default for RbacNclConfig {
    fn default() -> Self {
        Self {
            ncl_path: default_rbac_ncl_path(),
        }
    }
}

/// Pipeline event system configuration.
#[cfg(feature = "nats-admin")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelinesNclConfig {
    /// Path to the pipeline index NCL file.
    /// Supports `PIPELINES_NCL_PATH` env-var override.
    /// Default: `./site/pipelines/index.ncl`
    #[serde(default = "default_pipelines_ncl_path")]
    pub ncl_path: String,

    /// Root directory from which relative script paths in pipeline steps are
    /// resolved.  Supports `PIPELINE_SCRIPTS_ROOT` env-var override.
    /// Default: `./site/scripts`
    #[serde(default = "default_pipeline_scripts_root")]
    pub scripts_root: String,
}

#[cfg(feature = "nats-admin")]
fn default_pipelines_ncl_path() -> String {
    std::env::var("PIPELINES_NCL_PATH").unwrap_or_else(|_| "./site/pipelines/index.ncl".to_string())
}

#[cfg(feature = "nats-admin")]
fn default_pipeline_scripts_root() -> String {
    std::env::var("PIPELINE_SCRIPTS_ROOT").unwrap_or_else(|_| "./site/scripts".to_string())
}

#[cfg(feature = "nats-admin")]
impl Default for PipelinesNclConfig {
    fn default() -> Self {
        Self {
            ncl_path: default_pipelines_ncl_path(),
            scripts_root: default_pipeline_scripts_root(),
        }
    }
}

/// OTP / Passwordless auth configuration (loaded from site/config/auth.toml)
#[cfg(feature = "auth")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthOtpConfig {
    #[serde(default = "default_login_mode")]
    pub login_mode: LoginMode,
    #[serde(default = "default_otp_store_kind")]
    pub otp_store: OtpStoreKind,
    #[serde(default = "default_otp_expiry")]
    pub otp_expiry_minutes: u64,
    #[serde(default = "default_otp_max_attempts")]
    pub otp_max_attempts: u8,
    #[serde(default = "default_otp_code_length")]
    pub otp_code_length: u8,
    #[serde(default = "default_registration_role")]
    pub registration_role: String,
}

#[cfg(feature = "auth")]
fn default_login_mode() -> LoginMode {
    LoginMode::Modal
}
#[cfg(feature = "auth")]
fn default_otp_store_kind() -> OtpStoreKind {
    OtpStoreKind::Memory
}
#[cfg(feature = "auth")]
fn default_otp_expiry() -> u64 {
    10
}
#[cfg(feature = "auth")]
fn default_otp_max_attempts() -> u8 {
    3
}
#[cfg(feature = "auth")]
fn default_otp_code_length() -> u8 {
    6
}
#[cfg(feature = "auth")]
fn default_registration_role() -> String {
    "user".to_string()
}

#[cfg(feature = "auth")]
impl Default for AuthOtpConfig {
    fn default() -> Self {
        Self {
            login_mode: default_login_mode(),
            otp_store: default_otp_store_kind(),
            otp_expiry_minutes: default_otp_expiry(),
            otp_max_attempts: default_otp_max_attempts(),
            otp_code_length: default_otp_code_length(),
            registration_role: default_registration_role(),
        }
    }
}

#[cfg(feature = "auth")]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum LoginMode {
    Modal,
    Page,
}

#[cfg(feature = "auth")]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OtpStoreKind {
    Memory,
    Db,
}

/// Wrapper to parse [auth] section from auth.toml
#[cfg(feature = "auth")]
#[derive(Debug, Deserialize)]
struct AuthTomlWrapper {
    auth: AuthOtpConfig,
}

/// Load AuthOtpConfig from a TOML file path, falling back to defaults on any
/// error.
#[cfg(feature = "auth")]
pub fn load_auth_otp_config(path: &std::path::Path) -> AuthOtpConfig {
    std::fs::read_to_string(path)
        .ok()
        .and_then(|s| toml::from_str::<AuthTomlWrapper>(&s).ok())
        .map(|w| w.auth)
        .unwrap_or_default()
}

/// Navigation and application log paths loaded from `site/config/logs.ncl`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogsConfig {
    /// Base directory for per-page navigation JSONL logs.
    /// Relative paths are resolved against `root_path` at startup.
    /// Overridable at runtime via `RUSTELO_NAV_LOG_DIR` env var.
    pub nav_log_dir: String,
}

impl Default for LogsConfig {
    fn default() -> Self {
        Self {
            nav_log_dir: "logs/navigation".to_string(),
        }
    }
}

/// NATS admin namespace — prefix + env assembled into full subject paths at
/// runtime.
///
/// Full subject path: `{prefix}.{env}.{leaf_subject}`
/// e.g. `evol.website.prod.admin.logs`
///
/// `env` can be overridden per k8s Deployment via `NATS_ADMIN_NAMESPACE_ENV`
/// without changing the NCL file.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NatsAdminNamespaceConfig {
    pub prefix: String,
    pub env: String,
}

/// JetStream consumer definition — one pull consumer on the admin stream.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NatsAdminConsumerConfig {
    /// Durable consumer name (unique within the stream).
    pub name: String,
    /// Ack policy: `"Explicit"` | `"None"` | `"All"`. Default: `"Explicit"`.
    #[serde(default = "NatsAdminConsumerConfig::default_ack_policy")]
    pub ack_policy: String,
    /// Optional subject filters. Empty list means the consumer receives all
    /// subjects on the stream.
    #[serde(default)]
    pub filter_subjects: Vec<String>,
}

impl NatsAdminConsumerConfig {
    fn default_ack_policy() -> String {
        "Explicit".to_string()
    }
}

impl Default for NatsAdminConsumerConfig {
    fn default() -> Self {
        Self {
            name: "rustelo-pipelines".to_string(),
            ack_policy: Self::default_ack_policy(),
            filter_subjects: Vec::new(),
        }
    }
}

/// JetStream stream + consumer configuration for the admin event stream.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NatsAdminJetStreamConfig {
    pub enabled: bool,
    /// Stream name in JetStream.
    pub stream_name: String,
    /// Retention period in days (0 = unlimited).
    pub retention_days: u32,
    /// Retention policy: `"Limits"` | `"Interest"` | `"WorkQueue"`.
    #[serde(default = "NatsAdminJetStreamConfig::default_retention")]
    pub retention: String,
    /// Storage backend: `"File"` | `"Memory"`.
    #[serde(default = "NatsAdminJetStreamConfig::default_storage")]
    pub storage: String,
    /// Max messages in the stream (0 = unlimited).
    #[serde(default)]
    pub max_messages: i64,
    /// Max bytes in the stream (0 = unlimited).
    #[serde(default)]
    pub max_bytes: i64,
    /// Pull consumers to create on this stream.
    #[serde(default = "NatsAdminJetStreamConfig::default_consumers")]
    pub consumers: Vec<NatsAdminConsumerConfig>,
}

impl NatsAdminJetStreamConfig {
    fn default_retention() -> String {
        "Limits".to_string()
    }
    fn default_storage() -> String {
        "File".to_string()
    }
    fn default_consumers() -> Vec<NatsAdminConsumerConfig> {
        vec![NatsAdminConsumerConfig::default()]
    }

    /// Build the list of subjects for the JetStream stream.
    ///
    /// Uses a wildcard pattern covering the full namespace so all pipeline
    /// subjects (`content.*`, `deploy.*`, `user.*`, etc.) are captured by
    /// a single stream.
    ///
    /// Pattern: `{prefix}.{env}.>` (NATS `>` wildcard matches everything)
    pub fn pipeline_subjects(&self, namespace: &NatsAdminNamespaceConfig) -> Vec<String> {
        vec![format!("{}.{}.>", namespace.prefix, namespace.env)]
    }
}

impl Default for NatsAdminJetStreamConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            stream_name: "rustelo-admin".to_string(),
            retention_days: 30,
            retention: Self::default_retention(),
            storage: Self::default_storage(),
            max_messages: 0,
            max_bytes: 0,
            consumers: Self::default_consumers(),
        }
    }
}

/// Admin NATS connection — separate from the general app NATS.
///
/// Uses nkeys credentials file (mounted as k8s Secret) instead of a plain
/// token. Leaf-node topology: the web server connects outbound to the hub;
/// no inbound ports required in k8s.
///
/// Sensitive fields (`url`, `credentials_file`, `namespace.*`, `nkey_seed`)
/// support `${ENV_VAR}` substitution resolved at startup.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NatsAdminConnectionConfig {
    pub enabled: bool,
    pub url: String,
    pub credentials_file: String,
    pub connection_timeout: u64,
    pub max_reconnects: usize,
    pub reconnect_delay: u64,
    pub namespace: NatsAdminNamespaceConfig,
    /// Leaf subject names (without namespace prefix). Open map —
    /// implementations may extend. Assembled at runtime:
    /// `{namespace.prefix}.{namespace.env}.{leaf}`
    pub subjects: std::collections::HashMap<String, String>,
    pub jetstream: NatsAdminJetStreamConfig,
    /// NKey seed for signing published messages (ed25519).
    /// Use `${NATS_ADMIN_NKEY_SEED}` to read from env.
    /// When `None`, messages are published unsigned.
    #[serde(default)]
    pub nkey_seed: Option<String>,
    /// When `true`, incoming pipeline messages without a valid NKey signature
    /// are dropped and nack'd. Only meaningful when `trusted_nkeys` is
    /// non-empty.
    #[serde(default)]
    pub require_signed_messages: bool,
    /// NKey public keys trusted to sign pipeline messages.
    /// Empty list disables signature verification.
    #[serde(default)]
    pub trusted_nkeys: Vec<String>,
}

impl Default for NatsAdminConnectionConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            url: String::new(),
            credentials_file: String::new(),
            connection_timeout: 5,
            max_reconnects: 10,
            reconnect_delay: 2,
            namespace: NatsAdminNamespaceConfig::default(),
            subjects: std::collections::HashMap::new(),
            jetstream: NatsAdminJetStreamConfig::default(),
            nkey_seed: None,
            require_signed_messages: false,
            trusted_nkeys: Vec::new(),
        }
    }
}

/// Deserialized `external_services` block from the unified NCL config.
///
/// Only `nats_admin` is captured here; other services (`nats`, `ollama`, `s3`)
/// are silently ignored by serde unless their own structs are added.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExternalServicesConfig {
    #[serde(default)]
    pub nats_admin: Option<NatsAdminConnectionConfig>,
}

/// Database pool configuration helper
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct DatabasePoolConfig {
    pub url: String,
    pub max_connections: u32,
    pub min_connections: u32,
    pub connect_timeout: std::time::Duration,
    pub idle_timeout: std::time::Duration,
    pub max_lifetime: std::time::Duration,
}
