use std::collections::HashMap;

use pd_proto_grpc::admin::v1::ConfigViolation;

pub(super) fn validate_i64_range(
    proposed: &HashMap<String, String>,
    key: &str,
    min: i64,
    max: i64,
    violations: &mut Vec<ConfigViolation>,
) {
    if let Some(raw) = proposed.get(key) {
        match raw.parse::<i64>() {
            Ok(value) if value >= min && value <= max => {}
            Ok(value) => violations.push(ConfigViolation {
                code: format!("{key}_out_of_range"),
                message: format!("{key} must be within [{min}, {max}], got {value}"),
            }),
            Err(err) => violations.push(ConfigViolation {
                code: format!("{key}_invalid"),
                message: format!("{key} is not a valid integer: {err}"),
            }),
        }
    }
}

pub(super) fn validate_i64_min(
    proposed: &HashMap<String, String>,
    key: &str,
    min: i64,
    violations: &mut Vec<ConfigViolation>,
) {
    if let Some(raw) = proposed.get(key) {
        match raw.parse::<i64>() {
            Ok(value) if value >= min => {}
            Ok(value) => violations.push(ConfigViolation {
                code: format!("{key}_too_small"),
                message: format!("{key} must be >= {min}, got {value}"),
            }),
            Err(err) => violations.push(ConfigViolation {
                code: format!("{key}_invalid"),
                message: format!("{key} is not a valid integer: {err}"),
            }),
        }
    }
}

pub(super) fn validate_i64_non_negative(
    proposed: &HashMap<String, String>,
    key: &str,
    violations: &mut Vec<ConfigViolation>,
) {
    validate_i64_min(proposed, key, 0, violations);
}

pub(super) fn validate_i64(
    proposed: &HashMap<String, String>,
    key: &str,
    violations: &mut Vec<ConfigViolation>,
) {
    if let Some(raw) = proposed.get(key)
        && let Err(err) = raw.parse::<i64>()
    {
        violations.push(ConfigViolation {
            code: format!("{key}_invalid"),
            message: format!("{key} is not a valid integer: {err}"),
        });
    }
}

pub(super) fn validate_i64_special_range(
    proposed: &HashMap<String, String>,
    key: &str,
    disabled: i64,
    min: i64,
    max: i64,
    violations: &mut Vec<ConfigViolation>,
) {
    if let Some(raw) = proposed.get(key) {
        match raw.parse::<i64>() {
            Ok(value) if value == disabled || (value >= min && value <= max) => {}
            Ok(value) => violations.push(ConfigViolation {
                code: format!("{key}_invalid"),
                message: format!("{key} must be {disabled} or within [{min}, {max}], got {value}"),
            }),
            Err(err) => violations.push(ConfigViolation {
                code: format!("{key}_invalid"),
                message: format!("{key} is not a valid integer: {err}"),
            }),
        }
    }
}

pub(super) fn validate_bool(
    proposed: &HashMap<String, String>,
    key: &str,
    violations: &mut Vec<ConfigViolation>,
) {
    if let Some(raw) = proposed.get(key) {
        match raw.trim().to_ascii_lowercase().as_str() {
            "1" | "0" | "true" | "false" | "yes" | "no" | "on" | "off" => {}
            _ => violations.push(ConfigViolation {
                code: format!("{key}_invalid"),
                message: format!("{key} must be a boolean value, got '{raw}'"),
            }),
        }
    }
}

pub(super) fn validate_enum(
    proposed: &HashMap<String, String>,
    key: &str,
    allowed: &[&str],
    violations: &mut Vec<ConfigViolation>,
) {
    if let Some(raw) = proposed.get(key)
        && !allowed.iter().any(|value| value == raw)
    {
        violations.push(ConfigViolation {
            code: format!("{key}_invalid"),
            message: format!("{key} must be one of [{}], got '{raw}'", allowed.join(", ")),
        });
    }
}

pub(super) fn strict_profile_guardrails(
    normalized: &HashMap<String, String>,
    violations: &mut Vec<ConfigViolation>,
) {
    let checks = [
        ("enable-async-tso-window-prefetch", "false"),
        ("enable-tso-overload-hardening", "false"),
        ("max-tso-count-per-request", "0"),
        ("get-min-ts-rate-limit-qps-per-caller", "0"),
        ("get-min-ts-rate-limit-qps-global", "0"),
        ("get-min-ts-authz-allow-sender-id", "0"),
    ];
    for (key, expected) in checks {
        if let Some(actual) = normalized.get(key)
            && actual != expected
        {
            violations.push(ConfigViolation {
                code: "strict_profile_guardrail_violation".to_string(),
                message: format!("strict profile requires {key}={expected}, got {actual}"),
            });
        }
    }
}

pub(super) fn validate_tso_interval_order(
    normalized: &HashMap<String, String>,
    violations: &mut Vec<ConfigViolation>,
) {
    let save = normalized
        .get("tso-save-interval-ms")
        .and_then(|raw| raw.parse::<i64>().ok());
    let update = normalized
        .get("tso-update-physical-interval-ms")
        .and_then(|raw| raw.parse::<i64>().ok());
    if let (Some(save_interval_ms), Some(update_interval_ms)) = (save, update)
        && save_interval_ms < update_interval_ms
    {
        violations.push(ConfigViolation {
            code: "tso_interval_order_invalid".to_string(),
            message: format!(
                "tso-save-interval-ms must be >= tso-update-physical-interval-ms, got {save_interval_ms} < {update_interval_ms}"
            ),
        });
    }
}

pub(super) fn predicted_apply_mode(
    proposed: &HashMap<String, String>,
    active: &HashMap<String, String>,
) -> &'static str {
    for (key, proposed_value) in proposed {
        if is_restart_required_key(key)
            && active
                .get(key)
                .is_none_or(|active_value| active_value != proposed_value)
        {
            return "restart-required";
        }
    }
    "hot-reload"
}

fn is_restart_required_key(key: &str) -> bool {
    const RESTART_REQUIRED: [&str; 11] = [
        "compatibility-profile",
        "lease",
        "lease-ms",
        "listen-addr",
        "advertise-listen-addr",
        "backend-endpoints",
        "tls-enabled",
        "tls-ca-path",
        "tls-cert-path",
        "tls-key-path",
        "security-options",
    ];
    RESTART_REQUIRED.contains(&key)
}
