use super::*;

pub(super) fn validate(config: &mut ProxyConfig) -> Result<()> {
    if !(1..=MAX_API_REQUEST_BODY_LIMIT_BYTES).contains(&config.server.api.request_body_limit_bytes)
    {
        return Err(ProxyError::Config(
            "server.api.request_body_limit_bytes must be within [1, 1048576]".to_string(),
        ));
    }

    if config.server.api.minimal_runtime_cache_ttl_ms > 60_000 {
        return Err(ProxyError::Config(
            "server.api.minimal_runtime_cache_ttl_ms must be within [0, 60000]".to_string(),
        ));
    }

    if config.server.api.runtime_edge_cache_ttl_ms > 60_000 {
        return Err(ProxyError::Config(
            "server.api.runtime_edge_cache_ttl_ms must be within [0, 60000]".to_string(),
        ));
    }

    if !(1..=1000).contains(&config.server.api.runtime_edge_top_n) {
        return Err(ProxyError::Config(
            "server.api.runtime_edge_top_n must be within [1, 1000]".to_string(),
        ));
    }

    if !(16..=4096).contains(&config.server.api.runtime_edge_events_capacity) {
        return Err(ProxyError::Config(
            "server.api.runtime_edge_events_capacity must be within [16, 4096]".to_string(),
        ));
    }

    if config.server.api.listen.parse::<SocketAddr>().is_err() {
        return Err(ProxyError::Config(
            "server.api.listen must be in IP:PORT format".to_string(),
        ));
    }

    if config.server.proxy_protocol_header_timeout_ms == 0 {
        return Err(ProxyError::Config(
            "server.proxy_protocol_header_timeout_ms must be > 0".to_string(),
        ));
    }

    if config.server.listen_backlog == 0 || config.server.listen_backlog > i32::MAX as u32 {
        return Err(ProxyError::Config(format!(
            "server.listen_backlog must be within [1, {}]",
            i32::MAX
        )));
    }

    config
        .server
        .client_mss_value()
        .map_err(|error| ProxyError::Config(format!("server.client_mss {error}")))?;
    config
        .server
        .client_mss_bulk_value()
        .map_err(|error| ProxyError::Config(format!("server.client_mss_bulk {error}")))?;
    for (idx, listener) in config.server.listeners.iter().enumerate() {
        if listener.client_mss.is_some() {
            listener
                .effective_client_mss(&config.server)
                .map_err(|error| {
                    ProxyError::Config(format!("server.listeners[{idx}].client_mss {error}"))
                })?;
        }
        if listener.synlimit_seconds == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_seconds must be > 0"
            )));
        }
        if listener.synlimit_hitcount == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_hitcount must be > 0"
            )));
        }
        if listener.synlimit_burst == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_burst must be > 0"
            )));
        }
        if listener.synlimit_ios_seconds == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_ios_seconds must be > 0"
            )));
        }
        if listener.synlimit_ios_hitcount == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_ios_hitcount must be > 0"
            )));
        }
        if listener.synlimit_ios_burst == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_ios_burst must be > 0"
            )));
        }
        if listener.synlimit_hashlimit_expire_ms == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_hashlimit_expire_ms must be > 0"
            )));
        }
        if listener.synlimit_hashlimit_size == 0 {
            return Err(ProxyError::Config(format!(
                "server.listeners[{idx}].synlimit_hashlimit_size must be > 0"
            )));
        }
    }

    if config.server.accept_permit_timeout_ms > 60_000 {
        return Err(ProxyError::Config(
            "server.accept_permit_timeout_ms must be within [0, 60000]".to_string(),
        ));
    }

    if config.server.conntrack_control.pressure_high_watermark_pct == 0
        || config.server.conntrack_control.pressure_high_watermark_pct > 100
    {
        return Err(ProxyError::Config(
            "server.conntrack_control.pressure_high_watermark_pct must be within [1, 100]"
                .to_string(),
        ));
    }

    if config.server.conntrack_control.pressure_low_watermark_pct
        >= config.server.conntrack_control.pressure_high_watermark_pct
    {
        return Err(ProxyError::Config(
            "server.conntrack_control.pressure_low_watermark_pct must be < pressure_high_watermark_pct"
                .to_string(),
        ));
    }

    if config.server.conntrack_control.delete_budget_per_sec == 0 {
        return Err(ProxyError::Config(
            "server.conntrack_control.delete_budget_per_sec must be > 0".to_string(),
        ));
    }

    if matches!(config.server.conntrack_control.mode, ConntrackMode::Hybrid)
        && config
            .server
            .conntrack_control
            .hybrid_listener_ips
            .is_empty()
    {
        return Err(ProxyError::Config(
            "server.conntrack_control.hybrid_listener_ips must be non-empty in mode=hybrid"
                .to_string(),
        ));
    }

    if config.general.effective_me_pool_force_close_secs() > 0
        && config.general.effective_me_pool_force_close_secs()
            < config.general.me_pool_drain_ttl_secs
    {
        warn!(
            me_pool_drain_ttl_secs = config.general.me_pool_drain_ttl_secs,
            me_reinit_drain_timeout_secs = config.general.effective_me_pool_force_close_secs(),
            "force-close timeout is lower than drain TTL; bumping force-close timeout to TTL"
        );
        config.general.me_reinit_drain_timeout_secs = config.general.me_pool_drain_ttl_secs;
    }

    // Validate secrets.
    for (user, secret) in &config.access.users {
        if !secret.chars().all(|c| c.is_ascii_hexdigit()) || secret.len() != 32 {
            return Err(ProxyError::InvalidSecret {
                user: user.clone(),
                reason: "Must be 32 hex characters".to_string(),
            });
        }
    }

    config.censorship.tls_domain =
        normalize_domain_to_ascii(&config.censorship.tls_domain, "censorship.tls_domain")?;

    // Validate mask_unix_sock.
    if let Some(ref sock_path) = config.censorship.mask_unix_sock {
        if sock_path.is_empty() {
            return Err(ProxyError::Config(
                "mask_unix_sock cannot be empty".to_string(),
            ));
        }
        #[cfg(unix)]
        if sock_path.len() > 107 {
            return Err(ProxyError::Config(format!(
                "mask_unix_sock path too long: {} bytes (max 107)",
                sock_path.len()
            )));
        }
        #[cfg(not(unix))]
        return Err(ProxyError::Config(
            "mask_unix_sock is only supported on Unix platforms".to_string(),
        ));

        if config.censorship.mask_host.is_some() {
            return Err(ProxyError::Config(
                "mask_unix_sock and mask_host are mutually exclusive".to_string(),
            ));
        }
    }

    if let Some(mask_host) = config.censorship.mask_host.as_mut() {
        *mask_host = normalize_mask_host_to_ascii(mask_host, "censorship.mask_host")?;
    }

    for (domain, target) in &config.censorship.exclusive_mask {
        if !is_valid_tls_domain_name(domain) {
            return Err(ProxyError::Config(format!(
                "Invalid censorship.exclusive_mask domain: '{}'. Must be a valid domain name",
                domain
            )));
        }
        if parse_exclusive_mask_target(target).is_none() {
            return Err(ProxyError::Config(format!(
                "Invalid censorship.exclusive_mask target for '{}': '{}'. Expected host:port with port > 0",
                domain, target
            )));
        }
    }
    Ok(())
}
