use tracing::warn;

use crate::error::{ProxyError, Result};

const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
    "general",
    "logging",
    "network",
    "server",
    "web",
    "timeouts",
    "censorship",
    "access",
    "upstreams",
    "show_link",
    "dc_overrides",
    "default_dc",
    "beobachten",
    "beobachten_minutes",
    "beobachten_flush_secs",
    "beobachten_file",
    "include",
];

const GENERAL_CONFIG_KEYS: &[&str] = &[
    "data_path",
    "quota_state_path",
    "config_strict",
    "modes",
    "prefer_ipv6",
    "fast_mode",
    "use_middle_proxy",
    "proxy_secret_path",
    "proxy_secret_url",
    "proxy_config_v4_cache_path",
    "proxy_config_v4_url",
    "proxy_config_v6_cache_path",
    "proxy_config_v6_url",
    "ad_tag",
    "middle_proxy_nat_ip",
    "middle_proxy_nat_probe",
    "middle_proxy_nat_stun",
    "middle_proxy_nat_stun_servers",
    "stun_nat_probe_concurrency",
    "middle_proxy_pool_size",
    "middle_proxy_warm_standby",
    "me_init_retry_attempts",
    "me2dc_fallback",
    "me2dc_fast",
    "me_keepalive_enabled",
    "me_keepalive_interval_secs",
    "me_keepalive_jitter_secs",
    "me_keepalive_payload_random",
    "rpc_proxy_req_every",
    "me_writer_cmd_channel_capacity",
    "me_writer_byte_budget_bytes",
    "me_route_channel_capacity",
    "me_c2me_channel_capacity",
    "me_c2me_send_timeout_ms",
    "me_reader_route_data_wait_ms",
    "me_d2c_flush_batch_max_frames",
    "me_d2c_flush_batch_max_bytes",
    "me_d2c_flush_batch_max_delay_us",
    "me_d2c_ack_flush_immediate",
    "me_quota_soft_overshoot_bytes",
    "me_d2c_frame_buf_shrink_threshold_bytes",
    "direct_relay_copy_buf_c2s_bytes",
    "direct_relay_copy_buf_s2c_bytes",
    "direct_relay_buffer_budget_max_bytes",
    "crypto_pending_buffer",
    "max_client_frame",
    "desync_all_full",
    "beobachten",
    "beobachten_minutes",
    "beobachten_flush_secs",
    "beobachten_file",
    "hardswap",
    "me_warmup_stagger_enabled",
    "me_warmup_step_delay_ms",
    "me_warmup_step_jitter_ms",
    "me_reconnect_max_concurrent_per_dc",
    "me_reconnect_backoff_base_ms",
    "me_reconnect_backoff_cap_ms",
    "me_reconnect_fast_retry_count",
    "me_single_endpoint_shadow_writers",
    "me_single_endpoint_outage_mode_enabled",
    "me_single_endpoint_outage_disable_quarantine",
    "me_single_endpoint_outage_backoff_min_ms",
    "me_single_endpoint_outage_backoff_max_ms",
    "me_single_endpoint_shadow_rotate_every_secs",
    "me_floor_mode",
    "me_adaptive_floor_idle_secs",
    "me_adaptive_floor_min_writers_single_endpoint",
    "me_adaptive_floor_min_writers_multi_endpoint",
    "me_adaptive_floor_recover_grace_secs",
    "me_adaptive_floor_writers_per_core_total",
    "me_adaptive_floor_cpu_cores_override",
    "me_adaptive_floor_max_extra_writers_single_per_core",
    "me_adaptive_floor_max_extra_writers_multi_per_core",
    "me_adaptive_floor_max_active_writers_per_core",
    "me_adaptive_floor_max_warm_writers_per_core",
    "me_adaptive_floor_max_active_writers_global",
    "me_adaptive_floor_max_warm_writers_global",
    "upstream_connect_retry_attempts",
    "upstream_connect_retry_backoff_ms",
    "upstream_connect_budget_ms",
    "tg_connect",
    "upstream_unhealthy_fail_threshold",
    "upstream_connect_failfast_hard_errors",
    "stun_iface_mismatch_ignore",
    "unknown_dc_log_path",
    "unknown_dc_file_log_enabled",
    "log_level",
    "disable_colors",
    "telemetry",
    "me_socks_kdf_policy",
    "me_route_backpressure_enabled",
    "me_route_fairshare_enabled",
    "me_route_backpressure_base_timeout_ms",
    "me_route_backpressure_high_timeout_ms",
    "me_route_backpressure_high_watermark_pct",
    "me_health_interval_ms_unhealthy",
    "me_health_interval_ms_healthy",
    "me_admission_poll_ms",
    "me_warn_rate_limit_ms",
    "me_route_no_writer_mode",
    "me_route_no_writer_wait_ms",
    "me_route_hybrid_max_wait_ms",
    "me_route_blocking_send_timeout_ms",
    "me_route_inline_recovery_attempts",
    "me_route_inline_recovery_wait_ms",
    "links",
    "fast_mode_min_tls_record",
    "update_every",
    "me_reinit_every_secs",
    "me_hardswap_warmup_delay_min_ms",
    "me_hardswap_warmup_delay_max_ms",
    "me_hardswap_warmup_extra_passes",
    "me_hardswap_warmup_pass_backoff_base_ms",
    "me_config_stable_snapshots",
    "me_config_apply_cooldown_secs",
    "me_snapshot_require_http_2xx",
    "me_snapshot_reject_empty_map",
    "me_snapshot_min_proxy_for_lines",
    "proxy_secret_stable_snapshots",
    "proxy_secret_rotate_runtime",
    "me_secret_atomic_snapshot",
    "proxy_secret_len_max",
    "me_pool_drain_ttl_secs",
    "me_instadrain",
    "me_pool_drain_threshold",
    "me_pool_drain_soft_evict_enabled",
    "me_pool_drain_soft_evict_grace_secs",
    "me_pool_drain_soft_evict_per_writer",
    "me_pool_drain_soft_evict_budget_per_core",
    "me_pool_drain_soft_evict_cooldown_ms",
    "me_bind_stale_mode",
    "me_bind_stale_ttl_secs",
    "me_pool_min_fresh_ratio",
    "me_reinit_drain_timeout_secs",
    "proxy_secret_auto_reload_secs",
    "proxy_config_auto_reload_secs",
    "me_reinit_singleflight",
    "me_reinit_max_concurrency",
    "me_reinit_trigger_channel",
    "me_reinit_coalesce_window_ms",
    "me_deterministic_writer_sort",
    "me_writer_pick_mode",
    "me_writer_pick_sample_size",
    "ntp_check",
    "ntp_servers",
    "auto_degradation_enabled",
    "degradation_min_unavailable_dc_groups",
    "rst_on_close",
];

const NETWORK_CONFIG_KEYS: &[&str] = &[
    "ipv4",
    "ipv6",
    "prefer",
    "multipath",
    "stun_use",
    "stun_servers",
    "stun_tcp_fallback",
    "http_ip_detect_urls",
    "cache_public_ip_path",
    "dns_overrides",
];

const SERVER_CONFIG_KEYS: &[&str] = &[
    "port",
    "listen_addr_ipv4",
    "listen_addr_ipv6",
    "listen_unix_sock",
    "listen_unix_sock_perm",
    "listen_tcp",
    "client_mss",
    "client_mss_bulk",
    "proxy_protocol",
    "proxy_protocol_header_timeout_ms",
    "proxy_protocol_trusted_cidrs",
    "metrics_port",
    "metrics_listen",
    "metrics_whitelist",
    "api",
    "admin_api",
    "listeners",
    "listen_backlog",
    "max_connections",
    "accept_permit_timeout_ms",
    "conntrack_control",
];

const API_CONFIG_KEYS: &[&str] = &[
    "enabled",
    "listen",
    "whitelist",
    "gray_action",
    "auth_header",
    "request_body_limit_bytes",
    "minimal_runtime_enabled",
    "minimal_runtime_cache_ttl_ms",
    "runtime_edge_enabled",
    "runtime_edge_cache_ttl_ms",
    "runtime_edge_top_n",
    "runtime_edge_events_capacity",
    "read_only",
];

const CONNTRACK_CONTROL_CONFIG_KEYS: &[&str] = &[
    "inline_conntrack_control",
    "mode",
    "backend",
    "profile",
    "hybrid_listener_ips",
    "pressure_high_watermark_pct",
    "pressure_low_watermark_pct",
    "delete_budget_per_sec",
];

const LISTENER_CONFIG_KEYS: &[&str] = &[
    "ip",
    "transport",
    "port",
    "client_mss",
    "synlimit",
    "synlimit_seconds",
    "synlimit_hitcount",
    "synlimit_burst",
    "synlimit_ios_seconds",
    "synlimit_ios_hitcount",
    "synlimit_ios_burst",
    "synlimit_hashlimit_expire_ms",
    "synlimit_hashlimit_size",
    "announce",
    "announce_ip",
    "proxy_protocol",
    "reuse_allow",
    "web_client_ip_source",
    "web_trusted_proxy_cidrs",
];

const WEB_CONFIG_KEYS: &[&str] = &[
    "enabled",
    "carrier",
    "carriers",
    "carrier_learning",
    "carrier_negotiation_aggressiveness",
    "decoy_fasttrack_mode",
    "http_connection_capacity_action",
    "debug",
    "limits",
    "timeouts",
    "vhosts",
];

const WEB_LIMITS_CONFIG_KEYS: &[&str] = &[
    "max_header_bytes",
    "max_body_bytes",
    "max_frame_payload_bytes",
    "carrier_batch_bytes",
    "max_frames_per_body",
    "max_http_connections",
    "max_http_overload_connections",
    "max_http_handlers",
    "max_lane_open_waits_per_session",
    "pending_bytes_per_lane",
    "pending_items_per_lane",
    "websocket_bytes_global",
    "websocket_admission_watermark_pct",
    "websocket_eviction_watermark_pct",
    "websocket_http_connection_reserve",
    "max_websocket_evictions_in_flight",
    "max_carrier_learning_entries",
    "max_body_readers",
    "max_body_bytes_global",
    "max_sessions_global",
    "max_sessions_per_ip",
    "max_streams_per_session",
    "max_streams_global",
    "max_stream_handshakes",
    "max_tombstones_per_session",
    "pending_bytes_per_session",
    "pending_bytes_global",
    "pending_items_per_session",
    "pending_items_global",
    "control_bytes_per_session",
    "control_bytes_global",
    "max_bootstraps_global",
    "max_bootstraps_per_ip",
    "max_vhosts",
    "max_profiles",
    "max_static_files",
    "max_static_file_bytes",
    "max_static_bytes",
    "debug_records_capacity",
    "debug_bytes_global",
    "memory_envelope_bytes",
    "new_bootstraps_per_minute",
    "new_bootstraps_burst",
    "new_sessions_per_minute",
    "new_sessions_burst",
    "new_streams_per_minute",
    "new_streams_burst",
];

const WEB_DEBUG_CONFIG_KEYS: &[&str] = &[
    "enabled",
    "capture_lifecycle",
    "capture_headers",
    "capture_timings",
    "capture_frames",
    "body_capture",
    "body_prefix_bytes",
    "decoy_body_prefix_bytes",
    "default_window_secs",
    "max_window_secs",
];

const WEB_TIMEOUTS_CONFIG_KEYS: &[&str] = &[
    "header_secs",
    "body_secs",
    "stream_handshake_secs",
    "stream_first_byte_secs",
    "long_poll_secs",
    "bridge_request_secs",
    "bridge_retry_secs",
    "bridge_recovery_secs",
    "carrier_probe_coalesce_ms",
    "lane_open_wait_secs",
    "carrier_health_secs",
    "websocket_upgrade_secs",
    "websocket_open_secs",
    "websocket_write_secs",
    "websocket_backpressure_secs",
    "websocket_eviction_secs",
    "carrier_negotiation_deadlines_secs",
    "carrier_learning_secs",
    "bootstrap_lifetime_secs",
    "reconnect_grace_secs",
    "http_idle_secs",
    "http_overload_timeout_ms",
    "shutdown_secs",
    "decoy_header_secs",
];

const WEB_VHOST_CONFIG_KEYS: &[&str] = &["host", "public_addr", "decoy", "profiles"];
const WEB_DECOY_CONFIG_KEYS: &[&str] = &["mode", "upstream", "directory", "index"];
const WEB_PROFILE_CONFIG_KEYS: &[&str] = &[
    "user",
    "secret_mode",
    "max_sessions",
    "max_streams",
    "max_streams_per_session",
];

const TIMEOUTS_CONFIG_KEYS: &[&str] = &[
    "client_first_byte_idle_secs",
    "client_handshake",
    "relay_idle_policy_v2_enabled",
    "relay_client_idle_soft_secs",
    "relay_client_idle_hard_secs",
    "relay_idle_grace_after_downstream_activity_secs",
    "client_keepalive",
    "client_ack",
    "me_one_retry",
    "me_one_timeout_ms",
];

const CENSORSHIP_CONFIG_KEYS: &[&str] = &[
    "tls_domain",
    "tls_domains",
    "unknown_sni_action",
    "tls_fetch_scope",
    "tls_fetch",
    "mask",
    "mask_dynamic",
    "mask_host",
    "mask_port",
    "exclusive_mask",
    "mask_unix_sock",
    "fake_cert_len",
    "tls_emulation",
    "tls_front_dir",
    "server_hello_delay_min_ms",
    "server_hello_delay_max_ms",
    "tls_new_session_tickets",
    "serverhello_compact",
    "tls_full_cert_ttl_secs",
    "alpn_enforce",
    "mask_proxy_protocol",
    "mask_shape_hardening",
    "mask_shape_hardening_aggressive_mode",
    "mask_shape_bucket_floor_bytes",
    "mask_shape_bucket_cap_bytes",
    "mask_shape_above_cap_blur",
    "mask_shape_above_cap_blur_max_bytes",
    "mask_relay_max_bytes",
    "mask_relay_timeout_ms",
    "mask_relay_idle_timeout_ms",
    "mask_classifier_prefetch_timeout_ms",
    "mask_timing_normalization_enabled",
    "mask_timing_normalization_floor_ms",
    "mask_timing_normalization_ceiling_ms",
];

const TLS_FETCH_CONFIG_KEYS: &[&str] = &[
    "profiles",
    "strict_route",
    "attempt_timeout_ms",
    "total_budget_ms",
    "grease_enabled",
    "deterministic",
    "profile_cache_ttl_secs",
];

const ACCESS_CONFIG_KEYS: &[&str] = &[
    "users",
    "user_enabled",
    "user_ad_tags",
    "user_max_tcp_conns",
    "user_max_tcp_conns_global_each",
    "user_expirations",
    "user_data_quota",
    "user_rate_limits",
    "cidr_rate_limits",
    "user_max_unique_ips",
    "user_max_unique_ips_global_each",
    "user_max_unique_ips_mode",
    "user_max_unique_ips_window_secs",
    "replay_check_len",
    "replay_window_secs",
    "ignore_time_skew",
];

const RATE_LIMIT_BPS_CONFIG_KEYS: &[&str] = &["up_bps", "down_bps"];

const UPSTREAM_CONFIG_KEYS: &[&str] = &[
    "type",
    "interface",
    "bind_addresses",
    "bindtodevice",
    "force_bind",
    "address",
    "user_id",
    "username",
    "password",
    "url",
    "weight",
    "enabled",
    "scopes",
    "ipv4",
    "ipv6",
];

const PROXY_MODES_CONFIG_KEYS: &[&str] = &["classic", "secure", "tls"];
const TELEMETRY_CONFIG_KEYS: &[&str] = &["core_enabled", "user_enabled", "me_level"];
const LINKS_CONFIG_KEYS: &[&str] = &["show", "public_host", "public_port"];
const LOGGING_CONFIG_KEYS: &[&str] = &[
    "destination",
    "path",
    "rotation",
    "max_size_bytes",
    "max_files",
    "max_age_secs",
];

// Recursive table traversal and key suggestion logic.
mod check;

/// Rejects or reports unknown configuration keys according to strict mode.
pub(super) fn handle_unknown_config_keys(parsed_toml: &toml::Value) -> Result<()> {
    let unknown = check::collect_unknown_config_keys(parsed_toml);
    if unknown.is_empty() {
        return Ok(());
    }

    for item in &unknown {
        if let Some(suggestion) = item.suggestion.as_deref() {
            warn!(
                key = %item.path,
                suggestion = %suggestion,
                "Unknown config key ignored; did you mean the suggested key?"
            );
        } else {
            warn!(key = %item.path, "Unknown config key ignored");
        }
    }

    if check::is_strict_config(parsed_toml) {
        let mut paths = Vec::with_capacity(unknown.len());
        for item in unknown {
            if let Some(suggestion) = item.suggestion {
                paths.push(format!("{} (did you mean `{}`?)", item.path, suggestion));
            } else {
                paths.push(item.path);
            }
        }
        return Err(ProxyError::Config(format!(
            "unknown config keys are not allowed when general.config_strict=true: {}",
            paths.join(", ")
        )));
    }

    Ok(())
}
