use chrono::{DateTime, Utc};
use ipnetwork::IpNetwork;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;

use super::defaults::*;

// ============= Log Level =============

/// Logging verbosity level.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
    /// All messages including trace (trace + debug + info + warn + error).
    Debug,
    /// Detailed operational logs (debug + info + warn + error).
    Verbose,
    /// Standard operational logs (info + warn + error).
    #[default]
    Normal,
    /// Minimal output: only warnings and errors (warn + error).
    /// Startup messages (config, DC connectivity, proxy links) are always shown
    /// via info! before the filter is applied.
    Silent,
}

impl LogLevel {
    /// Convert to tracing EnvFilter directive string.
    pub fn to_filter_str(&self) -> &'static str {
        match self {
            LogLevel::Debug => "trace",
            LogLevel::Verbose => "debug",
            LogLevel::Normal => "info",
            LogLevel::Silent => "warn",
        }
    }

    /// Parse from a loose string (CLI argument).
    pub fn from_str_loose(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "debug" | "trace" => LogLevel::Debug,
            "verbose" => LogLevel::Verbose,
            "normal" | "info" => LogLevel::Normal,
            "silent" | "quiet" | "error" | "warn" => LogLevel::Silent,
            _ => LogLevel::Normal,
        }
    }
}

impl std::fmt::Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LogLevel::Debug => write!(f, "debug"),
            LogLevel::Verbose => write!(f, "verbose"),
            LogLevel::Normal => write!(f, "normal"),
            LogLevel::Silent => write!(f, "silent"),
        }
    }
}

/// Middle-End telemetry verbosity level.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum MeTelemetryLevel {
    #[default]
    Normal,
    Silent,
    Debug,
}

impl MeTelemetryLevel {
    pub fn as_u8(self) -> u8 {
        match self {
            MeTelemetryLevel::Silent => 0,
            MeTelemetryLevel::Normal => 1,
            MeTelemetryLevel::Debug => 2,
        }
    }

    pub fn from_u8(raw: u8) -> Self {
        match raw {
            0 => MeTelemetryLevel::Silent,
            2 => MeTelemetryLevel::Debug,
            _ => MeTelemetryLevel::Normal,
        }
    }

    pub fn allows_normal(self) -> bool {
        !matches!(self, MeTelemetryLevel::Silent)
    }

    pub fn allows_debug(self) -> bool {
        matches!(self, MeTelemetryLevel::Debug)
    }
}

impl std::fmt::Display for MeTelemetryLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MeTelemetryLevel::Silent => write!(f, "silent"),
            MeTelemetryLevel::Normal => write!(f, "normal"),
            MeTelemetryLevel::Debug => write!(f, "debug"),
        }
    }
}

/// Middle-End SOCKS KDF fallback policy.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum MeSocksKdfPolicy {
    #[default]
    Strict,
    Compat,
}

impl MeSocksKdfPolicy {
    pub fn as_u8(self) -> u8 {
        match self {
            MeSocksKdfPolicy::Strict => 0,
            MeSocksKdfPolicy::Compat => 1,
        }
    }

    pub fn from_u8(raw: u8) -> Self {
        match raw {
            1 => MeSocksKdfPolicy::Compat,
            _ => MeSocksKdfPolicy::Strict,
        }
    }
}

/// Stale ME writer bind policy during drain window.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum MeBindStaleMode {
    #[default]
    Never,
    Ttl,
    Always,
}

impl MeBindStaleMode {
    pub fn as_u8(self) -> u8 {
        match self {
            MeBindStaleMode::Never => 0,
            MeBindStaleMode::Ttl => 1,
            MeBindStaleMode::Always => 2,
        }
    }

    pub fn from_u8(raw: u8) -> Self {
        match raw {
            0 => MeBindStaleMode::Never,
            2 => MeBindStaleMode::Always,
            _ => MeBindStaleMode::Ttl,
        }
    }
}

/// Middle-End writer floor policy mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum MeFloorMode {
    Static,
    #[default]
    Adaptive,
}

impl MeFloorMode {
    pub fn as_u8(self) -> u8 {
        match self {
            MeFloorMode::Static => 0,
            MeFloorMode::Adaptive => 1,
        }
    }

    pub fn from_u8(raw: u8) -> Self {
        match raw {
            1 => MeFloorMode::Adaptive,
            _ => MeFloorMode::Static,
        }
    }
}

/// Middle-End route behavior when no writer is immediately available.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MeRouteNoWriterMode {
    AsyncRecoveryFailfast,
    InlineRecoveryLegacy,
    #[default]
    HybridAsyncPersistent,
}

impl MeRouteNoWriterMode {
    pub fn as_u8(self) -> u8 {
        match self {
            MeRouteNoWriterMode::AsyncRecoveryFailfast => 0,
            MeRouteNoWriterMode::InlineRecoveryLegacy => 1,
            MeRouteNoWriterMode::HybridAsyncPersistent => 2,
        }
    }

    pub fn from_u8(raw: u8) -> Self {
        match raw {
            0 => MeRouteNoWriterMode::AsyncRecoveryFailfast,
            1 => MeRouteNoWriterMode::InlineRecoveryLegacy,
            2 => MeRouteNoWriterMode::HybridAsyncPersistent,
            _ => MeRouteNoWriterMode::HybridAsyncPersistent,
        }
    }
}

/// Middle-End writer selection mode for new client bindings.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MeWriterPickMode {
    SortedRr,
    #[default]
    P2c,
}

impl MeWriterPickMode {
    pub fn as_u8(self) -> u8 {
        match self {
            MeWriterPickMode::SortedRr => 0,
            MeWriterPickMode::P2c => 1,
        }
    }

    pub fn from_u8(raw: u8) -> Self {
        match raw {
            0 => MeWriterPickMode::SortedRr,
            1 => MeWriterPickMode::P2c,
            _ => MeWriterPickMode::P2c,
        }
    }
}

/// Per-user unique source IP limit mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum UserMaxUniqueIpsMode {
    /// Count only currently active source IPs.
    #[default]
    ActiveWindow,
    /// Count source IPs seen within the recent time window.
    TimeWindow,
    /// Enforce both active and recent-window limits at the same time.
    Combined,
}

/// Telemetry controls for hot-path counters and ME diagnostics.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TelemetryConfig {
    #[serde(default = "default_true")]
    pub core_enabled: bool,
    #[serde(default = "default_true")]
    pub user_enabled: bool,
    #[serde(default)]
    pub me_level: MeTelemetryLevel,
}

impl Default for TelemetryConfig {
    fn default() -> Self {
        Self {
            core_enabled: default_true(),
            user_enabled: default_true(),
            me_level: MeTelemetryLevel::Normal,
        }
    }
}

// ============= Sub-Configs =============

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyModes {
    #[serde(default)]
    pub classic: bool,
    #[serde(default)]
    pub secure: bool,
    #[serde(default = "default_true")]
    pub tls: bool,
}

impl Default for ProxyModes {
    fn default() -> Self {
        Self {
            classic: false,
            secure: false,
            tls: default_true(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
    #[serde(default = "default_true")]
    pub ipv4: bool,

    /// None = auto-detect IPv6 availability.
    #[serde(default = "default_network_ipv6")]
    pub ipv6: Option<bool>,

    /// 4 or 6.
    #[serde(default = "default_prefer_4")]
    pub prefer: u8,

    #[serde(default)]
    pub multipath: bool,

    /// Global switch for STUN probing.
    /// When false, STUN is fully disabled and only non-STUN detection remains.
    #[serde(default = "default_true")]
    pub stun_use: bool,

    /// STUN servers list for public IP discovery.
    #[serde(default = "default_stun_servers")]
    pub stun_servers: Vec<String>,

    /// Enable TCP STUN fallback when UDP is blocked.
    #[serde(default = "default_stun_tcp_fallback")]
    pub stun_tcp_fallback: bool,

    /// HTTP-based public IP detection endpoints (fallback after STUN).
    #[serde(default = "default_http_ip_detect_urls")]
    pub http_ip_detect_urls: Vec<String>,

    /// Cache file path for detected public IP.
    #[serde(default = "default_cache_public_ip_path")]
    pub cache_public_ip_path: String,

    /// Runtime DNS overrides in `host:port:ip` format.
    /// IPv6 IP values must be bracketed: `[2001:db8::1]`.
    #[serde(default)]
    pub dns_overrides: Vec<String>,
}

impl Default for NetworkConfig {
    fn default() -> Self {
        Self {
            ipv4: default_true(),
            ipv6: default_network_ipv6(),
            prefer: default_prefer_4(),
            multipath: false,
            stun_use: default_true(),
            stun_servers: default_stun_servers(),
            stun_tcp_fallback: default_stun_tcp_fallback(),
            http_ip_detect_urls: default_http_ip_detect_urls(),
            cache_public_ip_path: default_cache_public_ip_path(),
            dns_overrides: Vec::new(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneralConfig {
    #[serde(default)]
    pub data_path: Option<PathBuf>,

    #[serde(default)]
    pub modes: ProxyModes,

    #[serde(default)]
    pub prefer_ipv6: bool,

    #[serde(default = "default_true")]
    pub fast_mode: bool,

    #[serde(default = "default_true")]
    pub use_middle_proxy: bool,

    /// Path to proxy-secret binary file (auto-downloaded if absent).
    /// Infrastructure secret from https://core.telegram.org/getProxySecret.
    #[serde(default = "default_proxy_secret_path")]
    pub proxy_secret_path: Option<String>,

    /// Optional path to cache raw getProxyConfig (IPv4) snapshot for startup fallback.
    #[serde(default = "default_proxy_config_v4_cache_path")]
    pub proxy_config_v4_cache_path: Option<String>,

    /// Optional path to cache raw getProxyConfigV6 snapshot for startup fallback.
    #[serde(default = "default_proxy_config_v6_cache_path")]
    pub proxy_config_v6_cache_path: Option<String>,

    /// Global ad_tag (32 hex chars from @MTProxybot). Fallback when user has no per-user tag in access.user_ad_tags.
    #[serde(default)]
    pub ad_tag: Option<String>,

    /// Public IP override for middle-proxy NAT environments.
    /// When set, this IP is used in ME key derivation and RPC_PROXY_REQ "our_addr".
    #[serde(default)]
    pub middle_proxy_nat_ip: Option<IpAddr>,

    /// Enable STUN-based NAT probing to discover public IP:port for ME KDF.
    #[serde(default = "default_true")]
    pub middle_proxy_nat_probe: bool,

    /// Deprecated legacy single STUN server for NAT probing.
    /// Use `network.stun_servers` instead.
    #[serde(default = "default_middle_proxy_nat_stun")]
    pub middle_proxy_nat_stun: Option<String>,

    /// Deprecated legacy STUN list for NAT probing fallback.
    /// Use `network.stun_servers` instead.
    #[serde(default = "default_middle_proxy_nat_stun_servers")]
    pub middle_proxy_nat_stun_servers: Vec<String>,

    /// Maximum number of concurrent STUN probes during NAT detection.
    #[serde(default = "default_stun_nat_probe_concurrency")]
    pub stun_nat_probe_concurrency: usize,

    /// Desired size of active Middle-Proxy writer pool.
    #[serde(default = "default_pool_size")]
    pub middle_proxy_pool_size: usize,

    /// Number of warm standby ME connections kept pre-initialized.
    #[serde(default = "default_middle_proxy_warm_standby")]
    pub middle_proxy_warm_standby: usize,

    /// Startup retries for Middle-End pool initialization before ME→Direct fallback.
    /// 0 means unlimited retries.
    #[serde(default = "default_me_init_retry_attempts")]
    pub me_init_retry_attempts: u32,

    /// Allow fallback from Middle-End mode to direct DC when ME startup cannot be initialized.
    #[serde(default = "default_me2dc_fallback")]
    pub me2dc_fallback: bool,

    /// Enable ME keepalive padding frames.
    #[serde(default = "default_true")]
    pub me_keepalive_enabled: bool,

    /// Keepalive interval in seconds.
    #[serde(default = "default_keepalive_interval")]
    pub me_keepalive_interval_secs: u64,

    /// Keepalive jitter in seconds.
    #[serde(default = "default_keepalive_jitter")]
    pub me_keepalive_jitter_secs: u64,

    /// Keepalive payload randomized (4 bytes); otherwise zeros.
    #[serde(default = "default_true")]
    pub me_keepalive_payload_random: bool,

    /// Interval in seconds for service RPC_PROXY_REQ activity signals to ME.
    /// 0 disables service activity signals.
    #[serde(default = "default_rpc_proxy_req_every")]
    pub rpc_proxy_req_every: u64,

    /// Capacity of per-ME writer command channel.
    #[serde(default = "default_me_writer_cmd_channel_capacity")]
    pub me_writer_cmd_channel_capacity: usize,

    /// Capacity of per-connection ME response route channel.
    #[serde(default = "default_me_route_channel_capacity")]
    pub me_route_channel_capacity: usize,

    /// Capacity of per-client command queue from client reader to ME sender task.
    #[serde(default = "default_me_c2me_channel_capacity")]
    pub me_c2me_channel_capacity: usize,

    /// Maximum wait in milliseconds for enqueueing C2ME commands when the queue is full.
    /// `0` keeps legacy unbounded wait behavior.
    #[serde(default = "default_me_c2me_send_timeout_ms")]
    pub me_c2me_send_timeout_ms: u64,

    /// Bounded wait in milliseconds for routing ME DATA to per-connection queue.
    /// `0` keeps non-blocking routing; values >0 enable bounded wait for compatibility.
    #[serde(default = "default_me_reader_route_data_wait_ms")]
    pub me_reader_route_data_wait_ms: u64,

    /// Maximum number of ME->Client responses coalesced before flush.
    #[serde(default = "default_me_d2c_flush_batch_max_frames")]
    pub me_d2c_flush_batch_max_frames: usize,

    /// Maximum total payload bytes coalesced before flush.
    #[serde(default = "default_me_d2c_flush_batch_max_bytes")]
    pub me_d2c_flush_batch_max_bytes: usize,

    /// Maximum wait in microseconds to coalesce additional ME->Client responses.
    /// `0` disables timed coalescing.
    #[serde(default = "default_me_d2c_flush_batch_max_delay_us")]
    pub me_d2c_flush_batch_max_delay_us: u64,

    /// Flush client writer immediately after quick-ack write.
    #[serde(default = "default_me_d2c_ack_flush_immediate")]
    pub me_d2c_ack_flush_immediate: bool,

    /// Additional bytes above strict per-user quota allowed in hot-path soft mode.
    #[serde(default = "default_me_quota_soft_overshoot_bytes")]
    pub me_quota_soft_overshoot_bytes: u64,

    /// Shrink threshold for reusable ME->Client frame assembly buffer.
    #[serde(default = "default_me_d2c_frame_buf_shrink_threshold_bytes")]
    pub me_d2c_frame_buf_shrink_threshold_bytes: usize,

    /// Copy buffer size for client->DC direction in direct relay.
    #[serde(default = "default_direct_relay_copy_buf_c2s_bytes")]
    pub direct_relay_copy_buf_c2s_bytes: usize,

    /// Copy buffer size for DC->client direction in direct relay.
    #[serde(default = "default_direct_relay_copy_buf_s2c_bytes")]
    pub direct_relay_copy_buf_s2c_bytes: usize,

    /// Max pending ciphertext buffer per client writer (bytes).
    /// Controls FakeTLS backpressure vs throughput.
    #[serde(default = "default_crypto_pending_buffer")]
    pub crypto_pending_buffer: usize,

    /// Maximum allowed client MTProto frame size (bytes).
    #[serde(default = "default_max_client_frame")]
    pub max_client_frame: usize,

    /// Emit full crypto-desync forensic logs for every event.
    /// When false, full forensic details are emitted once per key window.
    #[serde(default = "default_desync_all_full")]
    pub desync_all_full: bool,

    /// Enable per-IP forensic observation buckets for scanners and handshake failures.
    #[serde(default = "default_true")]
    pub beobachten: bool,

    /// Observation retention window in minutes for per-IP forensic buckets.
    #[serde(default = "default_beobachten_minutes")]
    pub beobachten_minutes: u64,

    /// Snapshot flush interval in seconds for beob output file.
    #[serde(default = "default_beobachten_flush_secs")]
    pub beobachten_flush_secs: u64,

    /// Snapshot file path for beob output.
    #[serde(default = "default_beobachten_file")]
    pub beobachten_file: String,

    /// Enable C-like hard-swap for ME pool generations.
    /// When true, Telemt prewarms a new generation and switches once full coverage is reached.
    #[serde(default = "default_hardswap")]
    pub hardswap: bool,

    /// Enable staggered warmup of extra ME writers.
    #[serde(default = "default_true")]
    pub me_warmup_stagger_enabled: bool,

    /// Base delay between warmup connections in ms.
    #[serde(default = "default_warmup_step_delay_ms")]
    pub me_warmup_step_delay_ms: u64,

    /// Jitter for warmup delay in ms.
    #[serde(default = "default_warmup_step_jitter_ms")]
    pub me_warmup_step_jitter_ms: u64,

    /// Max concurrent reconnect attempts per DC.
    #[serde(default = "default_me_reconnect_max_concurrent_per_dc")]
    pub me_reconnect_max_concurrent_per_dc: u32,

    /// Base backoff in ms for reconnect.
    #[serde(default = "default_reconnect_backoff_base_ms")]
    pub me_reconnect_backoff_base_ms: u64,

    /// Cap backoff in ms for reconnect.
    #[serde(default = "default_reconnect_backoff_cap_ms")]
    pub me_reconnect_backoff_cap_ms: u64,

    /// Fast retry attempts before backoff.
    #[serde(default = "default_me_reconnect_fast_retry_count")]
    pub me_reconnect_fast_retry_count: u32,

    /// Number of additional reserve writers for DC groups with exactly one endpoint.
    #[serde(default = "default_me_single_endpoint_shadow_writers")]
    pub me_single_endpoint_shadow_writers: u8,

    /// Enable aggressive outage recovery mode for single-endpoint DC groups.
    #[serde(default = "default_me_single_endpoint_outage_mode_enabled")]
    pub me_single_endpoint_outage_mode_enabled: bool,

    /// Ignore endpoint quarantine while in single-endpoint outage mode.
    #[serde(default = "default_me_single_endpoint_outage_disable_quarantine")]
    pub me_single_endpoint_outage_disable_quarantine: bool,

    /// Minimum reconnect backoff in ms for single-endpoint outage mode.
    #[serde(default = "default_me_single_endpoint_outage_backoff_min_ms")]
    pub me_single_endpoint_outage_backoff_min_ms: u64,

    /// Maximum reconnect backoff in ms for single-endpoint outage mode.
    #[serde(default = "default_me_single_endpoint_outage_backoff_max_ms")]
    pub me_single_endpoint_outage_backoff_max_ms: u64,

    /// Periodic shadow writer rotation interval in seconds for single-endpoint DC groups.
    /// Set to 0 to disable periodic shadow rotation.
    #[serde(default = "default_me_single_endpoint_shadow_rotate_every_secs")]
    pub me_single_endpoint_shadow_rotate_every_secs: u64,

    /// Floor policy mode for ME writer targets.
    #[serde(default)]
    pub me_floor_mode: MeFloorMode,

    /// Idle time in seconds before adaptive floor can reduce single-endpoint writer target.
    #[serde(default = "default_me_adaptive_floor_idle_secs")]
    pub me_adaptive_floor_idle_secs: u64,

    /// Minimum writer target for single-endpoint DC groups in adaptive floor mode.
    #[serde(default = "default_me_adaptive_floor_min_writers_single_endpoint")]
    pub me_adaptive_floor_min_writers_single_endpoint: u8,

    /// Minimum writer target for multi-endpoint DC groups in adaptive floor mode.
    #[serde(default = "default_me_adaptive_floor_min_writers_multi_endpoint")]
    pub me_adaptive_floor_min_writers_multi_endpoint: u8,

    /// Grace period in seconds to hold static floor after activity in adaptive mode.
    #[serde(default = "default_me_adaptive_floor_recover_grace_secs")]
    pub me_adaptive_floor_recover_grace_secs: u64,

    /// Global ME writer budget per logical CPU core in adaptive mode.
    #[serde(default = "default_me_adaptive_floor_writers_per_core_total")]
    pub me_adaptive_floor_writers_per_core_total: u16,

    /// Override logical CPU core count for adaptive floor calculations.
    /// Set to 0 to use runtime auto-detection.
    #[serde(default = "default_me_adaptive_floor_cpu_cores_override")]
    pub me_adaptive_floor_cpu_cores_override: u16,

    /// Per-core max extra writers above base required floor for single-endpoint DC groups.
    #[serde(default = "default_me_adaptive_floor_max_extra_writers_single_per_core")]
    pub me_adaptive_floor_max_extra_writers_single_per_core: u16,

    /// Per-core max extra writers above base required floor for multi-endpoint DC groups.
    #[serde(default = "default_me_adaptive_floor_max_extra_writers_multi_per_core")]
    pub me_adaptive_floor_max_extra_writers_multi_per_core: u16,

    /// Hard cap for active ME writers per logical CPU core.
    #[serde(default = "default_me_adaptive_floor_max_active_writers_per_core")]
    pub me_adaptive_floor_max_active_writers_per_core: u16,

    /// Hard cap for warm ME writers per logical CPU core.
    #[serde(default = "default_me_adaptive_floor_max_warm_writers_per_core")]
    pub me_adaptive_floor_max_warm_writers_per_core: u16,

    /// Hard global cap for active ME writers.
    #[serde(default = "default_me_adaptive_floor_max_active_writers_global")]
    pub me_adaptive_floor_max_active_writers_global: u32,

    /// Hard global cap for warm ME writers.
    #[serde(default = "default_me_adaptive_floor_max_warm_writers_global")]
    pub me_adaptive_floor_max_warm_writers_global: u32,

    /// Connect attempts for the selected upstream before returning error/fallback.
    #[serde(default = "default_upstream_connect_retry_attempts")]
    pub upstream_connect_retry_attempts: u32,

    /// Delay in milliseconds between upstream connect attempts.
    #[serde(default = "default_upstream_connect_retry_backoff_ms")]
    pub upstream_connect_retry_backoff_ms: u64,

    /// Total wall-clock budget in milliseconds for one upstream connect request across retries.
    #[serde(default = "default_upstream_connect_budget_ms")]
    pub upstream_connect_budget_ms: u64,

    /// Consecutive failed requests before upstream is marked unhealthy.
    #[serde(default = "default_upstream_unhealthy_fail_threshold")]
    pub upstream_unhealthy_fail_threshold: u32,

    /// Skip additional retries for hard non-transient upstream connect errors.
    #[serde(default = "default_upstream_connect_failfast_hard_errors")]
    pub upstream_connect_failfast_hard_errors: bool,

    /// Ignore STUN/interface IP mismatch (keep using Middle Proxy even if NAT detected).
    #[serde(default)]
    pub stun_iface_mismatch_ignore: bool,

    /// Log unknown (non-standard) DC requests to a file (default: unknown-dc.txt). Set to null to disable.
    #[serde(default = "default_unknown_dc_log_path")]
    pub unknown_dc_log_path: Option<String>,

    /// Enable unknown-DC file logging.
    #[serde(default = "default_unknown_dc_file_log_enabled")]
    pub unknown_dc_file_log_enabled: bool,

    #[serde(default)]
    pub log_level: LogLevel,

    /// Disable colored output in logs (useful for files/systemd).
    #[serde(default)]
    pub disable_colors: bool,

    /// Runtime telemetry controls for counters/metrics in hot paths.
    #[serde(default)]
    pub telemetry: TelemetryConfig,

    /// SOCKS-bound KDF policy for Middle-End handshake.
    #[serde(default)]
    pub me_socks_kdf_policy: MeSocksKdfPolicy,

    /// Base backpressure timeout in milliseconds for ME route channel send.
    #[serde(default = "default_me_route_backpressure_base_timeout_ms")]
    pub me_route_backpressure_base_timeout_ms: u64,

    /// High backpressure timeout in milliseconds when queue occupancy is above watermark.
    #[serde(default = "default_me_route_backpressure_high_timeout_ms")]
    pub me_route_backpressure_high_timeout_ms: u64,

    /// Queue occupancy percent threshold for high backpressure timeout.
    #[serde(default = "default_me_route_backpressure_high_watermark_pct")]
    pub me_route_backpressure_high_watermark_pct: u8,

    /// Health monitor interval in milliseconds while writer coverage is degraded.
    #[serde(default = "default_me_health_interval_ms_unhealthy")]
    pub me_health_interval_ms_unhealthy: u64,

    /// Health monitor interval in milliseconds while writer coverage is stable.
    #[serde(default = "default_me_health_interval_ms_healthy")]
    pub me_health_interval_ms_healthy: u64,

    /// Poll interval in milliseconds for conditional-admission state checks.
    #[serde(default = "default_me_admission_poll_ms")]
    pub me_admission_poll_ms: u64,

    /// Cooldown for repetitive ME warning logs in milliseconds.
    #[serde(default = "default_me_warn_rate_limit_ms")]
    pub me_warn_rate_limit_ms: u64,

    /// ME route behavior when no writer is immediately available.
    #[serde(default)]
    pub me_route_no_writer_mode: MeRouteNoWriterMode,

    /// Maximum wait time in milliseconds for async-recovery failfast mode.
    #[serde(default = "default_me_route_no_writer_wait_ms")]
    pub me_route_no_writer_wait_ms: u64,

    /// Maximum cumulative wait in milliseconds for hybrid no-writer mode before failfast.
    #[serde(default = "default_me_route_hybrid_max_wait_ms")]
    pub me_route_hybrid_max_wait_ms: u64,

    /// Maximum wait in milliseconds for blocking ME writer channel send fallback.
    /// `0` keeps legacy unbounded wait behavior.
    #[serde(default = "default_me_route_blocking_send_timeout_ms")]
    pub me_route_blocking_send_timeout_ms: u64,

    /// Number of inline recovery attempts in legacy mode.
    #[serde(default = "default_me_route_inline_recovery_attempts")]
    pub me_route_inline_recovery_attempts: u32,

    /// Maximum wait time in milliseconds for inline recovery in legacy mode.
    #[serde(default = "default_me_route_inline_recovery_wait_ms")]
    pub me_route_inline_recovery_wait_ms: u64,

    /// [general.links] — proxy link generation overrides.
    #[serde(default)]
    pub links: LinksConfig,

    /// Minimum TLS record size when fast_mode coalescing is enabled (0 = disabled).
    #[serde(default = "default_fast_mode_min_tls_record")]
    pub fast_mode_min_tls_record: usize,

    /// Unified ME updater interval in seconds for getProxyConfig/getProxyConfigV6/getProxySecret.
    /// When omitted, effective value falls back to legacy proxy_*_auto_reload_secs fields.
    #[serde(default = "default_update_every")]
    pub update_every: Option<u64>,

    /// Periodic ME pool reinitialization interval in seconds.
    #[serde(default = "default_me_reinit_every_secs")]
    pub me_reinit_every_secs: u64,

    /// Minimum delay in ms between hardswap warmup connect attempts.
    #[serde(default = "default_me_hardswap_warmup_delay_min_ms")]
    pub me_hardswap_warmup_delay_min_ms: u64,

    /// Maximum delay in ms between hardswap warmup connect attempts.
    #[serde(default = "default_me_hardswap_warmup_delay_max_ms")]
    pub me_hardswap_warmup_delay_max_ms: u64,

    /// Additional warmup passes in the same hardswap cycle after the base pass.
    #[serde(default = "default_me_hardswap_warmup_extra_passes")]
    pub me_hardswap_warmup_extra_passes: u8,

    /// Base backoff in ms between hardswap warmup passes when floor is still incomplete.
    #[serde(default = "default_me_hardswap_warmup_pass_backoff_base_ms")]
    pub me_hardswap_warmup_pass_backoff_base_ms: u64,

    /// Number of identical getProxyConfig snapshots required before applying ME map updates.
    #[serde(default = "default_me_config_stable_snapshots")]
    pub me_config_stable_snapshots: u8,

    /// Cooldown in seconds between applied ME map updates.
    #[serde(default = "default_me_config_apply_cooldown_secs")]
    pub me_config_apply_cooldown_secs: u64,

    /// Ensure getProxyConfig snapshots are applied only for 2xx HTTP responses.
    #[serde(default = "default_me_snapshot_require_http_2xx")]
    pub me_snapshot_require_http_2xx: bool,

    /// Reject empty getProxyConfig snapshots instead of marking them applied.
    #[serde(default = "default_me_snapshot_reject_empty_map")]
    pub me_snapshot_reject_empty_map: bool,

    /// Minimum parsed `proxy_for` rows required to accept a snapshot.
    #[serde(default = "default_me_snapshot_min_proxy_for_lines")]
    pub me_snapshot_min_proxy_for_lines: u32,

    /// Number of identical getProxySecret snapshots required before runtime secret rotation.
    #[serde(default = "default_proxy_secret_stable_snapshots")]
    pub proxy_secret_stable_snapshots: u8,

    /// Enable runtime proxy-secret rotation from getProxySecret.
    #[serde(default = "default_proxy_secret_rotate_runtime")]
    pub proxy_secret_rotate_runtime: bool,

    /// Keep key-selector and secret bytes from one snapshot during ME handshake.
    #[serde(default = "default_me_secret_atomic_snapshot")]
    pub me_secret_atomic_snapshot: bool,

    /// Maximum allowed proxy-secret length in bytes for startup and runtime refresh.
    #[serde(default = "default_proxy_secret_len_max")]
    pub proxy_secret_len_max: usize,

    /// Drain-TTL in seconds for stale ME writers after endpoint map changes.
    /// During TTL, stale writers may be used only as fallback for new bindings.
    #[serde(default = "default_me_pool_drain_ttl_secs")]
    pub me_pool_drain_ttl_secs: u64,

    /// Force-remove any draining writer on the next cleanup tick, regardless of age/deadline.
    #[serde(default = "default_me_instadrain")]
    pub me_instadrain: bool,

    /// Maximum allowed number of draining ME writers before oldest ones are force-closed in batches.
    /// Set to 0 to disable threshold-based draining cleanup and keep timeout-only behavior.
    #[serde(default = "default_me_pool_drain_threshold")]
    pub me_pool_drain_threshold: u64,

    /// Enable staged client eviction for draining ME writers that remain non-empty past TTL.
    #[serde(default = "default_me_pool_drain_soft_evict_enabled")]
    pub me_pool_drain_soft_evict_enabled: bool,

    /// Extra grace in seconds after drain TTL before soft-eviction stage starts.
    #[serde(default = "default_me_pool_drain_soft_evict_grace_secs")]
    pub me_pool_drain_soft_evict_grace_secs: u64,

    /// Maximum number of client sessions to evict from one draining writer per health tick.
    #[serde(default = "default_me_pool_drain_soft_evict_per_writer")]
    pub me_pool_drain_soft_evict_per_writer: u8,

    /// Soft-eviction budget per CPU core for one health tick.
    #[serde(default = "default_me_pool_drain_soft_evict_budget_per_core")]
    pub me_pool_drain_soft_evict_budget_per_core: u16,

    /// Cooldown for repetitive soft-eviction on the same writer in milliseconds.
    #[serde(default = "default_me_pool_drain_soft_evict_cooldown_ms")]
    pub me_pool_drain_soft_evict_cooldown_ms: u64,

    /// Policy for new binds on stale draining writers.
    #[serde(default)]
    pub me_bind_stale_mode: MeBindStaleMode,

    /// TTL for stale bind allowance when `me_bind_stale_mode = \"ttl\"`.
    #[serde(default = "default_me_bind_stale_ttl_secs")]
    pub me_bind_stale_ttl_secs: u64,

    /// Minimum desired-DC coverage ratio required before draining stale writers.
    /// Range: 0.0..=1.0.
    #[serde(default = "default_me_pool_min_fresh_ratio")]
    pub me_pool_min_fresh_ratio: f32,

    /// Drain timeout in seconds for stale ME writers after endpoint map changes.
    /// Set to 0 to use the runtime safety fallback timeout.
    #[serde(default = "default_me_reinit_drain_timeout_secs")]
    pub me_reinit_drain_timeout_secs: u64,

    /// Deprecated legacy setting; kept for backward compatibility fallback.
    /// Use `update_every` instead.
    #[serde(default = "default_proxy_secret_reload_secs")]
    pub proxy_secret_auto_reload_secs: u64,

    /// Deprecated legacy setting; kept for backward compatibility fallback.
    /// Use `update_every` instead.
    #[serde(default = "default_proxy_config_reload_secs")]
    pub proxy_config_auto_reload_secs: u64,

    /// Serialize ME reinit cycles across all trigger sources.
    #[serde(default = "default_me_reinit_singleflight")]
    pub me_reinit_singleflight: bool,

    /// Trigger queue capacity for reinit scheduler.
    #[serde(default = "default_me_reinit_trigger_channel")]
    pub me_reinit_trigger_channel: usize,

    /// Trigger coalescing window before starting a reinit cycle.
    #[serde(default = "default_me_reinit_coalesce_window_ms")]
    pub me_reinit_coalesce_window_ms: u64,

    /// Deterministic candidate sort for ME writer binding path.
    #[serde(default = "default_me_deterministic_writer_sort")]
    pub me_deterministic_writer_sort: bool,

    /// Writer selection mode for ME route bind path.
    #[serde(default)]
    pub me_writer_pick_mode: MeWriterPickMode,

    /// Number of candidates sampled by writer picker in `p2c` mode.
    #[serde(default = "default_me_writer_pick_sample_size")]
    pub me_writer_pick_sample_size: u8,

    /// Enable NTP drift check at startup.
    #[serde(default = "default_ntp_check")]
    pub ntp_check: bool,

    /// NTP servers for drift check.
    #[serde(default = "default_ntp_servers")]
    pub ntp_servers: Vec<String>,

    /// Enable auto-degradation from ME to Direct-DC.
    #[serde(default = "default_true")]
    pub auto_degradation_enabled: bool,

    /// Minimum unavailable ME DC groups before degrading.
    #[serde(default = "default_degradation_min_unavailable_dc_groups")]
    pub degradation_min_unavailable_dc_groups: u8,
}

impl Default for GeneralConfig {
    fn default() -> Self {
        Self {
            data_path: None,
            modes: ProxyModes::default(),
            prefer_ipv6: false,
            fast_mode: default_true(),
            use_middle_proxy: default_true(),
            ad_tag: None,
            proxy_secret_path: default_proxy_secret_path(),
            proxy_config_v4_cache_path: default_proxy_config_v4_cache_path(),
            proxy_config_v6_cache_path: default_proxy_config_v6_cache_path(),
            middle_proxy_nat_ip: None,
            middle_proxy_nat_probe: default_true(),
            middle_proxy_nat_stun: default_middle_proxy_nat_stun(),
            middle_proxy_nat_stun_servers: default_middle_proxy_nat_stun_servers(),
            stun_nat_probe_concurrency: default_stun_nat_probe_concurrency(),
            middle_proxy_pool_size: default_pool_size(),
            middle_proxy_warm_standby: default_middle_proxy_warm_standby(),
            me_init_retry_attempts: default_me_init_retry_attempts(),
            me2dc_fallback: default_me2dc_fallback(),
            me_keepalive_enabled: default_true(),
            me_keepalive_interval_secs: default_keepalive_interval(),
            me_keepalive_jitter_secs: default_keepalive_jitter(),
            me_keepalive_payload_random: default_true(),
            rpc_proxy_req_every: default_rpc_proxy_req_every(),
            me_writer_cmd_channel_capacity: default_me_writer_cmd_channel_capacity(),
            me_route_channel_capacity: default_me_route_channel_capacity(),
            me_c2me_channel_capacity: default_me_c2me_channel_capacity(),
            me_c2me_send_timeout_ms: default_me_c2me_send_timeout_ms(),
            me_reader_route_data_wait_ms: default_me_reader_route_data_wait_ms(),
            me_d2c_flush_batch_max_frames: default_me_d2c_flush_batch_max_frames(),
            me_d2c_flush_batch_max_bytes: default_me_d2c_flush_batch_max_bytes(),
            me_d2c_flush_batch_max_delay_us: default_me_d2c_flush_batch_max_delay_us(),
            me_d2c_ack_flush_immediate: default_me_d2c_ack_flush_immediate(),
            me_quota_soft_overshoot_bytes: default_me_quota_soft_overshoot_bytes(),
            me_d2c_frame_buf_shrink_threshold_bytes: default_me_d2c_frame_buf_shrink_threshold_bytes(),
            direct_relay_copy_buf_c2s_bytes: default_direct_relay_copy_buf_c2s_bytes(),
            direct_relay_copy_buf_s2c_bytes: default_direct_relay_copy_buf_s2c_bytes(),
            me_warmup_stagger_enabled: default_true(),
            me_warmup_step_delay_ms: default_warmup_step_delay_ms(),
            me_warmup_step_jitter_ms: default_warmup_step_jitter_ms(),
            me_reconnect_max_concurrent_per_dc: default_me_reconnect_max_concurrent_per_dc(),
            me_reconnect_backoff_base_ms: default_reconnect_backoff_base_ms(),
            me_reconnect_backoff_cap_ms: default_reconnect_backoff_cap_ms(),
            me_reconnect_fast_retry_count: default_me_reconnect_fast_retry_count(),
            me_single_endpoint_shadow_writers: default_me_single_endpoint_shadow_writers(),
            me_single_endpoint_outage_mode_enabled: default_me_single_endpoint_outage_mode_enabled(
            ),
            me_single_endpoint_outage_disable_quarantine:
                default_me_single_endpoint_outage_disable_quarantine(),
            me_single_endpoint_outage_backoff_min_ms:
                default_me_single_endpoint_outage_backoff_min_ms(),
            me_single_endpoint_outage_backoff_max_ms:
                default_me_single_endpoint_outage_backoff_max_ms(),
            me_single_endpoint_shadow_rotate_every_secs:
                default_me_single_endpoint_shadow_rotate_every_secs(),
            me_floor_mode: MeFloorMode::default(),
            me_adaptive_floor_idle_secs: default_me_adaptive_floor_idle_secs(),
            me_adaptive_floor_min_writers_single_endpoint:
                default_me_adaptive_floor_min_writers_single_endpoint(),
            me_adaptive_floor_min_writers_multi_endpoint:
                default_me_adaptive_floor_min_writers_multi_endpoint(),
            me_adaptive_floor_recover_grace_secs: default_me_adaptive_floor_recover_grace_secs(),
            me_adaptive_floor_writers_per_core_total:
                default_me_adaptive_floor_writers_per_core_total(),
            me_adaptive_floor_cpu_cores_override: default_me_adaptive_floor_cpu_cores_override(),
            me_adaptive_floor_max_extra_writers_single_per_core:
                default_me_adaptive_floor_max_extra_writers_single_per_core(),
            me_adaptive_floor_max_extra_writers_multi_per_core:
                default_me_adaptive_floor_max_extra_writers_multi_per_core(),
            me_adaptive_floor_max_active_writers_per_core:
                default_me_adaptive_floor_max_active_writers_per_core(),
            me_adaptive_floor_max_warm_writers_per_core:
                default_me_adaptive_floor_max_warm_writers_per_core(),
            me_adaptive_floor_max_active_writers_global:
                default_me_adaptive_floor_max_active_writers_global(),
            me_adaptive_floor_max_warm_writers_global:
                default_me_adaptive_floor_max_warm_writers_global(),
            upstream_connect_retry_attempts: default_upstream_connect_retry_attempts(),
            upstream_connect_retry_backoff_ms: default_upstream_connect_retry_backoff_ms(),
            upstream_connect_budget_ms: default_upstream_connect_budget_ms(),
            upstream_unhealthy_fail_threshold: default_upstream_unhealthy_fail_threshold(),
            upstream_connect_failfast_hard_errors: default_upstream_connect_failfast_hard_errors(),
            stun_iface_mismatch_ignore: false,
            unknown_dc_log_path: default_unknown_dc_log_path(),
            unknown_dc_file_log_enabled: default_unknown_dc_file_log_enabled(),
            log_level: LogLevel::Normal,
            disable_colors: false,
            telemetry: TelemetryConfig::default(),
            me_socks_kdf_policy: MeSocksKdfPolicy::Strict,
            me_route_backpressure_base_timeout_ms: default_me_route_backpressure_base_timeout_ms(),
            me_route_backpressure_high_timeout_ms: default_me_route_backpressure_high_timeout_ms(),
            me_route_backpressure_high_watermark_pct:
                default_me_route_backpressure_high_watermark_pct(),
            me_health_interval_ms_unhealthy: default_me_health_interval_ms_unhealthy(),
            me_health_interval_ms_healthy: default_me_health_interval_ms_healthy(),
            me_admission_poll_ms: default_me_admission_poll_ms(),
            me_warn_rate_limit_ms: default_me_warn_rate_limit_ms(),
            me_route_no_writer_mode: MeRouteNoWriterMode::default(),
            me_route_no_writer_wait_ms: default_me_route_no_writer_wait_ms(),
            me_route_hybrid_max_wait_ms: default_me_route_hybrid_max_wait_ms(),
            me_route_blocking_send_timeout_ms: default_me_route_blocking_send_timeout_ms(),
            me_route_inline_recovery_attempts: default_me_route_inline_recovery_attempts(),
            me_route_inline_recovery_wait_ms: default_me_route_inline_recovery_wait_ms(),
            links: LinksConfig::default(),
            crypto_pending_buffer: default_crypto_pending_buffer(),
            max_client_frame: default_max_client_frame(),
            desync_all_full: default_desync_all_full(),
            beobachten: default_true(),
            beobachten_minutes: default_beobachten_minutes(),
            beobachten_flush_secs: default_beobachten_flush_secs(),
            beobachten_file: default_beobachten_file(),
            hardswap: default_hardswap(),
            fast_mode_min_tls_record: default_fast_mode_min_tls_record(),
            update_every: default_update_every(),
            me_reinit_every_secs: default_me_reinit_every_secs(),
            me_hardswap_warmup_delay_min_ms: default_me_hardswap_warmup_delay_min_ms(),
            me_hardswap_warmup_delay_max_ms: default_me_hardswap_warmup_delay_max_ms(),
            me_hardswap_warmup_extra_passes: default_me_hardswap_warmup_extra_passes(),
            me_hardswap_warmup_pass_backoff_base_ms:
                default_me_hardswap_warmup_pass_backoff_base_ms(),
            me_config_stable_snapshots: default_me_config_stable_snapshots(),
            me_config_apply_cooldown_secs: default_me_config_apply_cooldown_secs(),
            me_snapshot_require_http_2xx: default_me_snapshot_require_http_2xx(),
            me_snapshot_reject_empty_map: default_me_snapshot_reject_empty_map(),
            me_snapshot_min_proxy_for_lines: default_me_snapshot_min_proxy_for_lines(),
            proxy_secret_stable_snapshots: default_proxy_secret_stable_snapshots(),
            proxy_secret_rotate_runtime: default_proxy_secret_rotate_runtime(),
            me_secret_atomic_snapshot: default_me_secret_atomic_snapshot(),
            proxy_secret_len_max: default_proxy_secret_len_max(),
            me_pool_drain_ttl_secs: default_me_pool_drain_ttl_secs(),
            me_instadrain: default_me_instadrain(),
            me_pool_drain_threshold: default_me_pool_drain_threshold(),
            me_pool_drain_soft_evict_enabled: default_me_pool_drain_soft_evict_enabled(),
            me_pool_drain_soft_evict_grace_secs: default_me_pool_drain_soft_evict_grace_secs(),
            me_pool_drain_soft_evict_per_writer: default_me_pool_drain_soft_evict_per_writer(),
            me_pool_drain_soft_evict_budget_per_core:
                default_me_pool_drain_soft_evict_budget_per_core(),
            me_pool_drain_soft_evict_cooldown_ms: default_me_pool_drain_soft_evict_cooldown_ms(),
            me_bind_stale_mode: MeBindStaleMode::default(),
            me_bind_stale_ttl_secs: default_me_bind_stale_ttl_secs(),
            me_pool_min_fresh_ratio: default_me_pool_min_fresh_ratio(),
            me_reinit_drain_timeout_secs: default_me_reinit_drain_timeout_secs(),
            proxy_secret_auto_reload_secs: default_proxy_secret_reload_secs(),
            proxy_config_auto_reload_secs: default_proxy_config_reload_secs(),
            me_reinit_singleflight: default_me_reinit_singleflight(),
            me_reinit_trigger_channel: default_me_reinit_trigger_channel(),
            me_reinit_coalesce_window_ms: default_me_reinit_coalesce_window_ms(),
            me_deterministic_writer_sort: default_me_deterministic_writer_sort(),
            me_writer_pick_mode: MeWriterPickMode::default(),
            me_writer_pick_sample_size: default_me_writer_pick_sample_size(),
            ntp_check: default_ntp_check(),
            ntp_servers: default_ntp_servers(),
            auto_degradation_enabled: default_true(),
            degradation_min_unavailable_dc_groups: default_degradation_min_unavailable_dc_groups(),
        }
    }
}

impl GeneralConfig {
    /// Resolve the active updater interval for ME infrastructure refresh tasks.
    /// `update_every` has priority, otherwise legacy proxy_*_auto_reload_secs are used.
    pub fn effective_update_every_secs(&self) -> u64 {
        self.update_every.unwrap_or_else(|| {
            self.proxy_secret_auto_reload_secs
                .min(self.proxy_config_auto_reload_secs)
        })
    }

    /// Resolve periodic zero-downtime reinit interval for ME writers.
    pub fn effective_me_reinit_every_secs(&self) -> u64 {
        self.me_reinit_every_secs
    }

    /// Resolve force-close timeout for stale writers.
    /// `me_reinit_drain_timeout_secs` remains backward-compatible alias.
    /// A configured `0` uses the runtime safety fallback (300s).
    pub fn effective_me_pool_force_close_secs(&self) -> u64 {
        if self.me_reinit_drain_timeout_secs == 0 {
            300
        } else {
            self.me_reinit_drain_timeout_secs
        }
    }
}

/// `[general.links]` — proxy link generation settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinksConfig {
    /// List of usernames whose tg:// links to display at startup.
    /// `"*"` = all users, `["alice", "bob"]` = specific users.
    #[serde(default = "default_links_show")]
    pub show: ShowLink,

    /// Public hostname/IP for tg:// link generation (overrides detected IP).
    #[serde(default)]
    pub public_host: Option<String>,

    /// Public port for tg:// link generation (overrides server.port).
    #[serde(default)]
    pub public_port: Option<u16>,
}

impl Default for LinksConfig {
    fn default() -> Self {
        Self {
            show: default_links_show(),
            public_host: None,
            public_port: None,
        }
    }
}

/// API settings for control-plane endpoints.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ApiConfig {
    /// Enable or disable REST API.
    #[serde(default = "default_true")]
    pub enabled: bool,

    /// Listen address for API in `IP:PORT` format.
    #[serde(default = "default_api_listen")]
    pub listen: String,

    /// CIDR whitelist allowed to access API.
    #[serde(default = "default_api_whitelist")]
    pub whitelist: Vec<IpNetwork>,

    /// Optional static value for `Authorization` header validation.
    /// Empty string disables header auth.
    #[serde(default)]
    pub auth_header: String,

    /// Maximum accepted HTTP request body size in bytes.
    #[serde(default = "default_api_request_body_limit_bytes")]
    pub request_body_limit_bytes: usize,

    /// Enable runtime snapshots that require read-lock aggregation on API request path.
    #[serde(default = "default_api_minimal_runtime_enabled")]
    pub minimal_runtime_enabled: bool,

    /// Cache TTL for minimal runtime snapshots in milliseconds (0 disables caching).
    #[serde(default = "default_api_minimal_runtime_cache_ttl_ms")]
    pub minimal_runtime_cache_ttl_ms: u64,

    /// Enables runtime edge endpoints with optional cached aggregation.
    #[serde(default = "default_api_runtime_edge_enabled")]
    pub runtime_edge_enabled: bool,

    /// Cache TTL for runtime edge aggregation payloads in milliseconds.
    #[serde(default = "default_api_runtime_edge_cache_ttl_ms")]
    pub runtime_edge_cache_ttl_ms: u64,

    /// Top-N limit for edge connection leaderboard payloads.
    #[serde(default = "default_api_runtime_edge_top_n")]
    pub runtime_edge_top_n: usize,

    /// Ring-buffer capacity for runtime edge control-plane events.
    #[serde(default = "default_api_runtime_edge_events_capacity")]
    pub runtime_edge_events_capacity: usize,

    /// Read-only mode: mutating endpoints are rejected.
    #[serde(default)]
    pub read_only: bool,
}

impl Default for ApiConfig {
    fn default() -> Self {
        Self {
            enabled: default_true(),
            listen: default_api_listen(),
            whitelist: default_api_whitelist(),
            auth_header: String::new(),
            request_body_limit_bytes: default_api_request_body_limit_bytes(),
            minimal_runtime_enabled: default_api_minimal_runtime_enabled(),
            minimal_runtime_cache_ttl_ms: default_api_minimal_runtime_cache_ttl_ms(),
            runtime_edge_enabled: default_api_runtime_edge_enabled(),
            runtime_edge_cache_ttl_ms: default_api_runtime_edge_cache_ttl_ms(),
            runtime_edge_top_n: default_api_runtime_edge_top_n(),
            runtime_edge_events_capacity: default_api_runtime_edge_events_capacity(),
            read_only: false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    #[serde(default = "default_port")]
    pub port: u16,

    #[serde(default = "default_listen_addr_ipv4")]
    pub listen_addr_ipv4: Option<String>,

    #[serde(default = "default_listen_addr_ipv6_opt")]
    pub listen_addr_ipv6: Option<String>,

    #[serde(default)]
    pub listen_unix_sock: Option<String>,

    /// Unix socket file permissions (octal, e.g. "0666" or "0777").
    /// Applied via chmod after bind. Default: no change (inherits umask).
    #[serde(default)]
    pub listen_unix_sock_perm: Option<String>,

    /// Enable TCP listening. Default: true when no unix socket, false when
    /// listen_unix_sock is set. Set explicitly to override auto-detection.
    #[serde(default)]
    pub listen_tcp: Option<bool>,

    /// Accept HAProxy PROXY protocol headers on incoming connections.
    /// When enabled, real client IPs are extracted from PROXY v1/v2 headers.
    #[serde(default)]
    pub proxy_protocol: bool,

    /// Timeout in milliseconds for reading and parsing PROXY protocol headers.
    #[serde(default = "default_proxy_protocol_header_timeout_ms")]
    pub proxy_protocol_header_timeout_ms: u64,

    /// Trusted source CIDRs allowed to send incoming PROXY protocol headers.
    ///
    /// When non-empty, connections from addresses outside this allowlist are
    /// rejected before `src_addr` is applied.
    #[serde(default)]
    pub proxy_protocol_trusted_cidrs: Vec<IpNetwork>,

    /// Port for the Prometheus-compatible metrics endpoint.
    /// Enables metrics when set; binds on all interfaces (dual-stack) by default.
    #[serde(default)]
    pub metrics_port: Option<u16>,

    /// Listen address for metrics in `IP:PORT` format (e.g. `"127.0.0.1:9090"`).
    /// When set, takes precedence over `metrics_port` and binds on the specified address only.
    #[serde(default)]
    pub metrics_listen: Option<String>,

    /// CIDR whitelist for the metrics endpoint.
    #[serde(default = "default_metrics_whitelist")]
    pub metrics_whitelist: Vec<IpNetwork>,

    #[serde(default, alias = "admin_api")]
    pub api: ApiConfig,

    #[serde(default)]
    pub listeners: Vec<ListenerConfig>,

    /// Maximum number of concurrent client connections.
    /// 0 means unlimited.
    #[serde(default = "default_server_max_connections")]
    pub max_connections: u32,

    /// Maximum wait in milliseconds while acquiring a connection slot permit.
    /// `0` keeps legacy unbounded wait behavior.
    #[serde(default = "default_accept_permit_timeout_ms")]
    pub accept_permit_timeout_ms: u64,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            port: default_port(),
            listen_addr_ipv4: default_listen_addr_ipv4(),
            listen_addr_ipv6: default_listen_addr_ipv6_opt(),
            listen_unix_sock: None,
            listen_unix_sock_perm: None,
            listen_tcp: None,
            proxy_protocol: false,
            proxy_protocol_header_timeout_ms: default_proxy_protocol_header_timeout_ms(),
            proxy_protocol_trusted_cidrs: Vec::new(),
            metrics_port: None,
            metrics_listen: None,
            metrics_whitelist: default_metrics_whitelist(),
            api: ApiConfig::default(),
            listeners: Vec::new(),
            max_connections: default_server_max_connections(),
            accept_permit_timeout_ms: default_accept_permit_timeout_ms(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeoutsConfig {
    #[serde(default = "default_handshake_timeout")]
    pub client_handshake: u64,

    /// Enables soft/hard relay client idle policy for middle-relay sessions.
    #[serde(default = "default_relay_idle_policy_v2_enabled")]
    pub relay_idle_policy_v2_enabled: bool,

    /// Soft idle threshold for middle-relay client uplink activity in seconds.
    /// Hitting this threshold marks the session as idle-candidate, but does not close it.
    #[serde(default = "default_relay_client_idle_soft_secs")]
    pub relay_client_idle_soft_secs: u64,

    /// Hard idle threshold for middle-relay client uplink activity in seconds.
    /// Hitting this threshold closes the session.
    #[serde(default = "default_relay_client_idle_hard_secs")]
    pub relay_client_idle_hard_secs: u64,

    /// Additional grace in seconds added to hard idle window after recent downstream activity.
    #[serde(default = "default_relay_idle_grace_after_downstream_activity_secs")]
    pub relay_idle_grace_after_downstream_activity_secs: u64,

    #[serde(default = "default_connect_timeout")]
    pub tg_connect: u64,

    #[serde(default = "default_keepalive")]
    pub client_keepalive: u64,

    #[serde(default = "default_ack_timeout")]
    pub client_ack: u64,

    /// Number of quick ME reconnect attempts for single-address DC.
    #[serde(default = "default_me_one_retry")]
    pub me_one_retry: u8,

    /// Timeout per quick attempt in milliseconds for single-address DC.
    #[serde(default = "default_me_one_timeout")]
    pub me_one_timeout_ms: u64,
}

impl Default for TimeoutsConfig {
    fn default() -> Self {
        Self {
            client_handshake: default_handshake_timeout(),
            relay_idle_policy_v2_enabled: default_relay_idle_policy_v2_enabled(),
            relay_client_idle_soft_secs: default_relay_client_idle_soft_secs(),
            relay_client_idle_hard_secs: default_relay_client_idle_hard_secs(),
            relay_idle_grace_after_downstream_activity_secs:
                default_relay_idle_grace_after_downstream_activity_secs(),
            tg_connect: default_connect_timeout(),
            client_keepalive: default_keepalive(),
            client_ack: default_ack_timeout(),
            me_one_retry: default_me_one_retry(),
            me_one_timeout_ms: default_me_one_timeout(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AntiCensorshipConfig {
    #[serde(default = "default_tls_domain")]
    pub tls_domain: String,

    /// Additional TLS domains for generating multiple proxy links.
    #[serde(default)]
    pub tls_domains: Vec<String>,

    /// Upstream scope used for TLS front metadata fetches.
    /// Empty value keeps default upstream routing behavior.
    #[serde(default = "default_tls_fetch_scope")]
    pub tls_fetch_scope: String,

    #[serde(default = "default_true")]
    pub mask: bool,

    #[serde(default)]
    pub mask_host: Option<String>,

    #[serde(default = "default_mask_port")]
    pub mask_port: u16,

    #[serde(default)]
    pub mask_unix_sock: Option<String>,

    #[serde(default = "default_fake_cert_len")]
    pub fake_cert_len: usize,

    /// Enable TLS certificate emulation using cached real certificates.
    #[serde(default = "default_true")]
    pub tls_emulation: bool,

    /// Directory to store TLS front cache (on disk).
    #[serde(default = "default_tls_front_dir")]
    pub tls_front_dir: String,

    /// Minimum server_hello delay in milliseconds (anti-fingerprint).
    #[serde(default = "default_server_hello_delay_min_ms")]
    pub server_hello_delay_min_ms: u64,

    /// Maximum server_hello delay in milliseconds.
    #[serde(default = "default_server_hello_delay_max_ms")]
    pub server_hello_delay_max_ms: u64,

    /// Number of NewSessionTicket messages to emit post-handshake.
    #[serde(default = "default_tls_new_session_tickets")]
    pub tls_new_session_tickets: u8,

    /// TTL in seconds for sending full certificate payload per client IP.
    /// First client connection per (SNI domain, client IP) gets full cert payload.
    /// Subsequent handshakes within TTL use compact cert metadata payload.
    #[serde(default = "default_tls_full_cert_ttl_secs")]
    pub tls_full_cert_ttl_secs: u64,

    /// Enforce ALPN echo of client preference.
    #[serde(default = "default_alpn_enforce")]
    pub alpn_enforce: bool,

    /// Send PROXY protocol header when connecting to mask_host.
    /// 0 = disabled, 1 = v1 (text), 2 = v2 (binary).
    /// Allows the backend to see the real client IP.
    #[serde(default)]
    pub mask_proxy_protocol: u8,

    /// Enable shape-channel hardening on mask backend path by padding
    /// client->mask stream tail to configured buckets on stream end.
    #[serde(default = "default_mask_shape_hardening")]
    pub mask_shape_hardening: bool,

    /// Opt-in aggressive shape hardening mode.
    /// When enabled, masking may shape some backend-silent timeout paths and
    /// enforces strictly positive above-cap blur when blur is enabled.
    #[serde(default = "default_mask_shape_hardening_aggressive_mode")]
    pub mask_shape_hardening_aggressive_mode: bool,

    /// Minimum bucket size for mask shape hardening padding.
    #[serde(default = "default_mask_shape_bucket_floor_bytes")]
    pub mask_shape_bucket_floor_bytes: usize,

    /// Maximum bucket size for mask shape hardening padding.
    #[serde(default = "default_mask_shape_bucket_cap_bytes")]
    pub mask_shape_bucket_cap_bytes: usize,

    /// Add bounded random tail bytes even when total bytes already exceed
    /// mask_shape_bucket_cap_bytes.
    #[serde(default = "default_mask_shape_above_cap_blur")]
    pub mask_shape_above_cap_blur: bool,

    /// Maximum random bytes appended above cap when above-cap blur is enabled.
    #[serde(default = "default_mask_shape_above_cap_blur_max_bytes")]
    pub mask_shape_above_cap_blur_max_bytes: usize,

    /// Enable outcome-time normalization envelope for masking fallback.
    #[serde(default = "default_mask_timing_normalization_enabled")]
    pub mask_timing_normalization_enabled: bool,

    /// Lower bound (ms) for masking outcome timing envelope.
    #[serde(default = "default_mask_timing_normalization_floor_ms")]
    pub mask_timing_normalization_floor_ms: u64,

    /// Upper bound (ms) for masking outcome timing envelope.
    #[serde(default = "default_mask_timing_normalization_ceiling_ms")]
    pub mask_timing_normalization_ceiling_ms: u64,
}

impl Default for AntiCensorshipConfig {
    fn default() -> Self {
        Self {
            tls_domain: default_tls_domain(),
            tls_domains: Vec::new(),
            tls_fetch_scope: default_tls_fetch_scope(),
            mask: default_true(),
            mask_host: None,
            mask_port: default_mask_port(),
            mask_unix_sock: None,
            fake_cert_len: default_fake_cert_len(),
            tls_emulation: true,
            tls_front_dir: default_tls_front_dir(),
            server_hello_delay_min_ms: default_server_hello_delay_min_ms(),
            server_hello_delay_max_ms: default_server_hello_delay_max_ms(),
            tls_new_session_tickets: default_tls_new_session_tickets(),
            tls_full_cert_ttl_secs: default_tls_full_cert_ttl_secs(),
            alpn_enforce: default_alpn_enforce(),
            mask_proxy_protocol: 0,
            mask_shape_hardening: default_mask_shape_hardening(),
            mask_shape_hardening_aggressive_mode: default_mask_shape_hardening_aggressive_mode(),
            mask_shape_bucket_floor_bytes: default_mask_shape_bucket_floor_bytes(),
            mask_shape_bucket_cap_bytes: default_mask_shape_bucket_cap_bytes(),
            mask_shape_above_cap_blur: default_mask_shape_above_cap_blur(),
            mask_shape_above_cap_blur_max_bytes: default_mask_shape_above_cap_blur_max_bytes(),
            mask_timing_normalization_enabled: default_mask_timing_normalization_enabled(),
            mask_timing_normalization_floor_ms: default_mask_timing_normalization_floor_ms(),
            mask_timing_normalization_ceiling_ms: default_mask_timing_normalization_ceiling_ms(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AccessConfig {
    #[serde(default = "default_access_users")]
    pub users: HashMap<String, String>,

    /// Per-user ad_tag (32 hex chars from @MTProxybot).
    #[serde(default)]
    pub user_ad_tags: HashMap<String, String>,

    #[serde(default)]
    pub user_max_tcp_conns: HashMap<String, usize>,

    #[serde(default)]
    pub user_expirations: HashMap<String, DateTime<Utc>>,

    #[serde(default)]
    pub user_data_quota: HashMap<String, u64>,

    #[serde(default)]
    pub user_max_unique_ips: HashMap<String, usize>,

    /// Global per-user unique IP limit applied when a user has no individual override.
    /// `0` disables the inherited limit.
    #[serde(default = "default_user_max_unique_ips_global_each")]
    pub user_max_unique_ips_global_each: usize,

    #[serde(default)]
    pub user_max_unique_ips_mode: UserMaxUniqueIpsMode,

    #[serde(default = "default_user_max_unique_ips_window_secs")]
    pub user_max_unique_ips_window_secs: u64,

    #[serde(default = "default_replay_check_len")]
    pub replay_check_len: usize,

    #[serde(default = "default_replay_window_secs")]
    pub replay_window_secs: u64,

    #[serde(default)]
    pub ignore_time_skew: bool,
}

impl Default for AccessConfig {
    fn default() -> Self {
        Self {
            users: default_access_users(),
            user_ad_tags: HashMap::new(),
            user_max_tcp_conns: HashMap::new(),
            user_expirations: HashMap::new(),
            user_data_quota: HashMap::new(),
            user_max_unique_ips: HashMap::new(),
            user_max_unique_ips_global_each: default_user_max_unique_ips_global_each(),
            user_max_unique_ips_mode: UserMaxUniqueIpsMode::default(),
            user_max_unique_ips_window_secs: default_user_max_unique_ips_window_secs(),
            replay_check_len: default_replay_check_len(),
            replay_window_secs: default_replay_window_secs(),
            ignore_time_skew: false,
        }
    }
}

// ============= Aux Structures =============

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum UpstreamType {
    Direct {
        #[serde(default)]
        interface: Option<String>,
        #[serde(default)]
        bind_addresses: Option<Vec<String>>,
    },
    Socks4 {
        address: String,
        #[serde(default)]
        interface: Option<String>,
        #[serde(default)]
        user_id: Option<String>,
    },
    Socks5 {
        address: String,
        #[serde(default)]
        interface: Option<String>,
        #[serde(default)]
        username: Option<String>,
        #[serde(default)]
        password: Option<String>,
    },
    Shadowsocks {
        url: String,
        #[serde(default)]
        interface: Option<String>,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpstreamConfig {
    #[serde(flatten)]
    pub upstream_type: UpstreamType,
    #[serde(default = "default_weight")]
    pub weight: u16,
    #[serde(default = "default_true")]
    pub enabled: bool,
    #[serde(default)]
    pub scopes: String,
    #[serde(skip)]
    pub selected_scope: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListenerConfig {
    pub ip: IpAddr,
    /// IP address or hostname to announce in proxy links.
    /// Takes precedence over `announce_ip` if both are set.
    #[serde(default)]
    pub announce: Option<String>,
    /// Deprecated: Use `announce` instead. IP address to announce in proxy links.
    /// Migrated to `announce` automatically if `announce` is not set.
    #[serde(default)]
    pub announce_ip: Option<IpAddr>,
    /// Per-listener PROXY protocol override. When set, overrides global server.proxy_protocol.
    #[serde(default)]
    pub proxy_protocol: Option<bool>,
    /// Allow multiple telemt instances to listen on the same IP:port (SO_REUSEPORT).
    /// Default is false for safety.
    #[serde(default)]
    pub reuse_allow: bool,
}

// ============= ShowLink =============

/// Controls which users' proxy links are displayed at startup.
///
/// In TOML, this can be:
/// - `show_link = "*"`          — show links for all users
/// - `show_link = ["a", "b"]`   — show links for specific users
/// - omitted                    — default depends on the owning config field
#[derive(Debug, Clone, Default)]
pub enum ShowLink {
    /// Don't show any links (default when omitted).
    #[default]
    None,
    /// Show links for all configured users.
    All,
    /// Show links for specific users.
    Specific(Vec<String>),
}

fn default_links_show() -> ShowLink {
    ShowLink::All
}

impl ShowLink {
    /// Returns true if no links should be shown.
    pub fn is_empty(&self) -> bool {
        matches!(self, ShowLink::None) || matches!(self, ShowLink::Specific(v) if v.is_empty())
    }

    /// Resolve the list of user names to display, given all configured users.
    pub fn resolve_users<'a>(&'a self, all_users: &'a HashMap<String, String>) -> Vec<&'a String> {
        match self {
            ShowLink::None => vec![],
            ShowLink::All => {
                let mut names: Vec<&String> = all_users.keys().collect();
                names.sort();
                names
            }
            ShowLink::Specific(names) => names.iter().collect(),
        }
    }
}

impl Serialize for ShowLink {
    fn serialize<S: serde::Serializer>(
        &self,
        serializer: S,
    ) -> std::result::Result<S::Ok, S::Error> {
        match self {
            ShowLink::None => Vec::<String>::new().serialize(serializer),
            ShowLink::All => serializer.serialize_str("*"),
            ShowLink::Specific(v) => v.serialize(serializer),
        }
    }
}

impl<'de> Deserialize<'de> for ShowLink {
    fn deserialize<D: serde::Deserializer<'de>>(
        deserializer: D,
    ) -> std::result::Result<Self, D::Error> {
        use serde::de;

        struct ShowLinkVisitor;

        impl<'de> de::Visitor<'de> for ShowLinkVisitor {
            type Value = ShowLink;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str(r#""*" or an array of user names"#)
            }

            fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<ShowLink, E> {
                if v == "*" {
                    Ok(ShowLink::All)
                } else {
                    Err(de::Error::invalid_value(de::Unexpected::Str(v), &r#""*""#))
                }
            }

            fn visit_seq<A: de::SeqAccess<'de>>(
                self,
                mut seq: A,
            ) -> std::result::Result<ShowLink, A::Error> {
                let mut names = Vec::new();
                while let Some(name) = seq.next_element::<String>()? {
                    names.push(name);
                }
                if names.is_empty() {
                    Ok(ShowLink::None)
                } else {
                    Ok(ShowLink::Specific(names))
                }
            }
        }

        deserializer.deserialize_any(ShowLinkVisitor)
    }
}
