use std::{
    collections::HashMap,
    fmt::{self},
    path::PathBuf,
};

use clap::{CommandFactory, Parser};
use cosmian_kms_server_database::reexport::cosmian_kmip::kmip_2_1::extra::tagging::VENDOR_ID_COSMIAN;
use serde::{Deserialize, Serialize};

use super::{
    GoogleCseConfig, HsmConfig, HttpConfig, IdpAuthConfig, KmipPolicyConfig, MainDBConfig,
    WorkspaceConfig, logging::LoggingConfig, ui_config::UiConfig,
};
use crate::{
    config::{AzureEkmConfig, ProxyConfig, SocketServerConfig, TlsConfig},
    error::KmsError,
    result::KResult,
    routes::aws_xks::AwsXksConfig,
};

#[cfg(not(target_os = "windows"))]
pub const DEFAULT_COSMIAN_KMS_CONF: &str = "/etc/cosmian/kms.toml";

// On Windows, we need to resolve %LOCALAPPDATA% at runtime
#[cfg(target_os = "windows")]
#[must_use]
pub fn get_default_config_path() -> String {
    std::env::var("LOCALAPPDATA").map_or_else(
        |_| String::from("C:\\ProgramData\\cosmian\\kms.toml"),
        |localappdata| format!("{localappdata}\\Cosmian KMS Server\\kms.toml"),
    )
}

#[cfg(not(target_os = "windows"))]
#[must_use]
pub fn get_default_config_path() -> String {
    DEFAULT_COSMIAN_KMS_CONF.to_owned()
}

const DEFAULT_USERNAME: &str = "admin";

impl Default for ClapConfig {
    fn default() -> Self {
        Self {
            config_path: None,
            db: MainDBConfig::default(),
            socket_server: SocketServerConfig::default(),
            tls: TlsConfig::default(),
            http: HttpConfig::default(),
            proxy: ProxyConfig::default(),
            kms_public_url: None,
            idp_auth: IdpAuthConfig::default(),
            ui_config: UiConfig::default(),
            google_cse_config: GoogleCseConfig::default(),
            workspace: WorkspaceConfig::default(),
            vendor_identification: VENDOR_ID_COSMIAN.to_owned(),
            default_username: DEFAULT_USERNAME.to_owned(),
            force_default_username: false,
            ms_dke_service_url: None,
            logging: LoggingConfig::default(),
            info: false,
            print_default_config: false,
            hsm: HsmConfig::default(),
            key_encryption_key: None,
            default_unwrap_type: None,
            non_revocable_key_id: None,
            privileged_users: None,
            aws_xks_config: AwsXksConfig::default(),
            kmip_policy: KmipPolicyConfig::default(),
            azure_ekm_config: AzureEkmConfig::default(),
        }
    }
}

#[derive(Parser, Serialize, Deserialize)]
#[clap(version, about, long_about = None)]
#[serde(default)]
pub struct ClapConfig {
    /// Explicit configuration file path provided via -c / --config.
    /// When set, this file takes precedence over the `COSMIAN_KMS_CONF` environment variable
    /// and the default system path. All other command line arguments (except `--help` / `--version`)
    /// and environment variables are ignored once the configuration file is loaded.
    #[clap(short = 'c', long = "config", value_name = "COSMIAN_KMS_CONF")]
    pub config_path: Option<PathBuf>,

    /// The vendor identification string reported in KMIP `QueryServerInformation` responses
    #[clap(long, env = "KMS_VENDOR_IDENTIFICATION", default_value = VENDOR_ID_COSMIAN)]
    pub vendor_identification: String,

    /// The default username to use when no authentication method is provided
    #[clap(long, env = "KMS_DEFAULT_USERNAME", default_value = DEFAULT_USERNAME)]
    pub default_username: String,

    /// When an authentication method is provided, perform the authentication
    /// but always use the default username instead of the one provided by the authentication method
    #[clap(long, env = "KMS_FORCE_DEFAULT_USERNAME", verbatim_doc_comment)]
    pub force_default_username: bool,

    /// This setting enables the Microsoft Double Key Encryption service feature of this server.
    ///
    /// It should contain the external URL of this server as configured in Azure App Registrations
    /// as the DKE Service (<https://learn.microsoft.com/en-us/purview/double-key-encryption-setup#register-your-key-store>)
    ///
    /// The URL should be something like <https://cse.my_domain.com/ms_dke>
    #[clap(verbatim_doc_comment, long, env = "KMS_MS_DKE_SERVICE_URL")]
    pub ms_dke_service_url: Option<String>,

    /// Print the server configuration information and exit
    #[clap(long, default_value = "false")]
    pub info: bool,

    /// Serialize the default server configuration as TOML to stdout and exit.
    /// This is used to keep the documentation in sync with the Rust struct.
    #[clap(long, default_value = "false")]
    #[serde(skip)]
    pub print_default_config: bool,

    #[clap(flatten)]
    #[serde(flatten)]
    pub hsm: HsmConfig,

    /// Force all keys imported or created in the KMS, which are not protected by a key encryption key,
    /// to be wrapped by the specified key encryption key (KEK)
    pub key_encryption_key: Option<String>,

    /// Specifies which KMIP object types should be automatically unwrapped when retrieved.
    /// Repeat this option to specify multiple object types
    /// e.g.
    /// ```sh
    ///   --default-unwrap-type SecretData \
    ///   --default-unwrap-type SymmetricKey
    /// ```
    #[clap(verbatim_doc_comment,
        long,
        value_parser(["All", "Certificate", "CertificateRequest", "OpaqueObject", "PGPKey", "PrivateKey", "PublicKey", "SecretData", "SplitKey", "SymmetricKey"])
    )]
    pub default_unwrap_type: Option<Vec<String>>,

    /// The exposed URL of the KMS - this is required if Google CSE configuration is activated.
    /// If this server is running on the domain `cse.my_domain.com` with this public URL,
    /// The configured URL from Google admin  should be something like <https://cse.my_domain.com/google_cse>
    /// The URL is also used during the authentication flow initiated from the KMS UI.
    #[clap(verbatim_doc_comment, long, env = "KMS_PUBLIC_URL")]
    pub kms_public_url: Option<String>,

    #[clap(flatten)]
    pub db: MainDBConfig,

    #[clap(flatten)]
    pub socket_server: SocketServerConfig,

    #[clap(flatten)]
    pub tls: TlsConfig,

    #[clap(flatten)]
    pub http: HttpConfig,

    #[clap(flatten)]
    pub proxy: ProxyConfig,

    #[clap(flatten)]
    pub idp_auth: IdpAuthConfig,

    #[clap(flatten)]
    pub ui_config: UiConfig,

    #[clap(flatten)]
    pub google_cse_config: GoogleCseConfig,

    #[clap(flatten)]
    pub azure_ekm_config: AzureEkmConfig,

    #[clap(flatten)]
    pub workspace: WorkspaceConfig,

    #[clap(flatten)]
    pub logging: LoggingConfig,

    /// The non-revocable key ID used for demo purposes
    #[clap(long, hide = true)]
    pub non_revocable_key_id: Option<Vec<String>>,

    /// List of users who have the right to create and import Objects
    /// and grant access rights for Create Kmip Operation.
    #[clap(long, verbatim_doc_comment)]
    pub privileged_users: Option<Vec<String>>,

    #[clap(flatten)]
    pub aws_xks_config: AwsXksConfig,

    /// KMIP algorithm policy.
    ///
    /// This policy is configured via parameter-specific allowlists under `[kmip.allowlists]`.
    ///
    /// Policy selection is controlled by `kmip.policy_id` (accepted values: `DEFAULT`, `CUSTOM`).
    ///
    /// If `kmip.policy_id` is unset, the KMIP policy layer is disabled.
    ///
    /// The `DEFAULT` policy enforces built-in conservative allowlists (aligned with ANSSI/NIST/FIPS
    /// recommendations).
    #[clap(flatten)]
    #[serde(rename = "kmip")]
    pub kmip_policy: KmipPolicyConfig,
}

impl ClapConfig {
    /// Serialize the default configuration as TOML with comments extracted from
    /// the clap help strings. Each field is preceded by its `///` doc comment as
    /// TOML `# …` lines, making the output directly useful as a reference config file.
    ///
    /// # Errors
    /// Returns an error if TOML serialization of the default config fails.
    pub fn default_config_with_comments() -> KResult<String> {
        use std::fmt::Write as _;

        let cmd = Self::command();

        // Build a map: toml_key (underscore form) → help text.
        // Prefer long_help (full /// doc comment) over short help (first line only).
        let help_map: HashMap<String, String> = cmd
            .get_arguments()
            .filter_map(|arg| {
                let long = arg.get_long()?;
                let help = arg
                    .get_long_help()
                    .or_else(|| arg.get_help())
                    .map(ToString::to_string)?;
                // Clap uses --long-flag-name; TOML serialization uses field_name.
                Some((long.replace('-', "_"), help))
            })
            .collect();

        // Use kms_template.toml as the structural template (lives at the crate root so
        // it is included in the published tarball).
        // It documents ALL known fields — including optional ones shown as "# key = value".
        // For each field, the clap help string replaces the manual comment when available.
        let template = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/kms_template.toml"));

        // Collect the section headers present in the template so Phase 2 can detect gaps.
        let template_sections: std::collections::HashSet<&str> =
            template.lines().filter(|l| l.starts_with('[')).collect();

        let mut output = String::new();
        // Accumulate consecutive comment lines until a field or section header is reached.
        let mut pending_comments: Vec<&str> = Vec::new();
        // Skip the very first line (template file header: "## Cosmian KMS configuration file").
        let mut first_line = true;

        // ── Phase 1: process the template ──────────────────────────────────────────
        for line in template.lines() {
            if first_line {
                first_line = false;
                continue;
            }

            // Detect field lines:
            //   "# key = …" where key is a valid identifier → optional field (commented-out)
            //   "key = …"    → active field (not starting with '#', '[', or blank)
            //
            // Distinguishing "# key = val" (optional field) from "# plain comment" is done by
            // checking that the token before '=' consists only of lowercase ASCII, digits and '_'.
            let key_opt: Option<&str> = line.strip_prefix("# ").map_or_else(
                || {
                    if !line.starts_with('#') && !line.starts_with('[') && !line.trim().is_empty() {
                        line.split('=')
                            .next()
                            .map(str::trim)
                            .filter(|s| !s.is_empty())
                    } else {
                        None
                    }
                },
                |rest| {
                    let before_eq = rest.split('=').next().map_or("", str::trim);
                    if !before_eq.is_empty()
                        && rest.contains('=')
                        && before_eq
                            .chars()
                            .all(|c| c.is_ascii_lowercase() || c == '_' || c.is_ascii_digit())
                    {
                        Some(before_eq)
                    } else {
                        None
                    }
                },
            );

            if let Some(key) = key_opt {
                // Emit comments: prefer clap help when available, else use manual comments.
                if let Some(help) = help_map.get(key) {
                    for help_line in help.lines() {
                        if help_line.is_empty() {
                            let _ = writeln!(output, "#");
                        } else {
                            let _ = writeln!(output, "# {help_line}");
                        }
                    }
                } else {
                    for comment in &pending_comments {
                        let _ = writeln!(output, "{comment}");
                    }
                }
                pending_comments.clear();
                // Emit the field line as-is: already "# key = val" or "key = val".
                let _ = writeln!(output, "{line}");
            } else if line.starts_with('[') {
                // Section header: flush accumulated comments before it.
                for comment in &pending_comments {
                    let _ = writeln!(output, "{comment}");
                }
                pending_comments.clear();
                let _ = writeln!(output, "{line}");
            } else if line.starts_with('#') {
                // Comment line: accumulate until the associated field is found.
                pending_comments.push(line);
            } else {
                // Empty line or other content: flush pending comments then emit.
                for comment in &pending_comments {
                    let _ = writeln!(output, "{comment}");
                }
                pending_comments.clear();
                let _ = writeln!(output, "{line}");
            }
        }
        // Flush any remaining accumulated comments.
        for comment in &pending_comments {
            let _ = writeln!(output, "{comment}");
        }

        // ── Phase 2: append sections missing from the template ─────────────────────
        // `toml::to_string_pretty` covers every section in the struct (e.g. [aws_xks_config],
        // [azure_ekm_config], [kmip], …) but omits Option<None> fields.
        // We emit only sections not already covered by the template, using clap help comments.
        let plain = toml::to_string_pretty(&Self::default())
            .map_err(|e| KmsError::ServerError(e.to_string()))?;

        let mut in_missing_section = false;
        for line in plain.lines() {
            if line.starts_with('[') {
                in_missing_section = !template_sections.contains(line);
                if in_missing_section {
                    let _ = writeln!(output, "\n{line}");
                }
            } else if in_missing_section {
                if !line.starts_with('#') && !line.trim().is_empty() {
                    // Key = value: prepend clap help if available.
                    if let Some(key) = line.split('=').next().map(str::trim) {
                        if let Some(help) = help_map.get(key) {
                            for help_line in help.lines() {
                                if help_line.is_empty() {
                                    let _ = writeln!(output, "#");
                                } else {
                                    let _ = writeln!(output, "# {help_line}");
                                }
                            }
                        }
                    }
                }
                let _ = writeln!(output, "{line}");
            }
        }

        Ok(output)
    }

    /// Load the configuration from the default configuration file
    ///
    /// # Errors
    /// Fails if the configuration file is not found,
    /// or if the configuration file is not valid,
    /// or if the configuration file cannot be read,
    /// or if the configuration file cannot be parsed,
    /// or if the configuration file is not a valid TOML file.
    pub fn load_configuration() -> KResult<Self> {
        Self::load_from_args(std::env::args())
    }

    /// Load configuration using a custom iterator of arguments (testable entry point).
    #[allow(clippy::print_stdout)] // Logging is not being initialized yet, just use standard prints
    pub fn load_from_args<I, T>(args: I) -> KResult<Self>
    where
        I: IntoIterator<Item = T>,
        T: Into<std::ffi::OsString> + Clone,
    {
        // Collect args so we can re-use for parse + messages
        let args_vec: Vec<T> = args.into_iter().collect();
        // Remember whether any CLI arguments beyond the binary name were supplied,
        // before args_vec is consumed by parse_from.
        let has_extra_args = args_vec.len() > 1;
        // Parse preliminarily to capture the optional config path (this also handles --help / --version)
        let preliminary = Self::parse_from(args_vec);

        // --print-default-config is a meta flag: it must bypass any config file so that
        // it always outputs the built-in defaults regardless of what is on disk.
        if preliminary.print_default_config {
            return Ok(preliminary);
        }

        // Determine configuration file path precedence:
        // 1. Command line -c/--config
        // 2. COSMIAN_KMS_CONF environment variable (if exists and path exists)
        // 3. Default system path (if exists)
        // 4. Fall back to command line arguments & env vars (no file)

        let explicit = preliminary.config_path.clone();
        let env_path = std::env::var("COSMIAN_KMS_CONF").ok().map(PathBuf::from);
        let default_path = PathBuf::from(get_default_config_path());

        // Helper to load a TOML file into ClapConfig
        let load_file = |p: &PathBuf| -> KResult<Self> {
            let conf_content = std::fs::read_to_string(p).map_err(|e| {
                KmsError::ServerError(format!(
                    "Cannot read KMS server config at: {} - {e:?}",
                    p.display()
                ))
            })?;
            toml::from_str(&conf_content).map_err(|e| {
                KmsError::ServerError(format!(
                    "Cannot parse kms server config at: {} - {e:?}",
                    p.display()
                ))
            })
        };

        if let Some(path) = explicit {
            if path.is_file() {
                println!(
                    "Configuration file {} found (via -c/--config). Command line arguments and \
                     env variables are ignored.",
                    path.display()
                );
                return load_file(&path);
            }
            if path.exists() {
                return Err(KmsError::ServerError(format!(
                    "Configuration file specified with -c/--config is not a regular file: {}",
                    path.display()
                )));
            }
            return Err(KmsError::ServerError(format!(
                "Configuration file specified with -c/--config not found: {}",
                path.display()
            )));
        }

        if let Some(env_path) = env_path {
            if env_path.is_file() {
                println!(
                    "Configuration file {} found (via COSMIAN_KMS_CONF). Command line arguments \
                     and env variables are ignored.",
                    env_path.display()
                );
                return load_file(&env_path);
            }
            if env_path.exists() {
                println!(
                    "WARNING: COSMIAN_KMS_CONF path {} exists but is not a regular file (e.g. a \
                     directory). Ignoring and falling back.",
                    env_path.display()
                );
            } else {
                println!(
                    "WARNING: Configuration file {} (COSMIAN_KMS_CONF) not found. Falling back.",
                    env_path.display()
                );
            }
        }

        if default_path.is_file() {
            // --info is an informational meta-flag: allow it even when the default
            // config file is present (it loads the file then prints info and exits).
            let info_only = preliminary.info;

            // If the user also passed CLI arguments (beyond the binary name itself) they
            // would silently be ignored because the file takes precedence.  This is almost
            // certainly a mistake, so fail fast with a clear message.
            if has_extra_args && !info_only {
                return Err(KmsError::ServerError(format!(
                    "Configuration file found at the default path ({}) but extra command-line \
                     arguments were also provided. When a configuration file is present, all \
                     command-line arguments and environment variables are ignored.\n\
                     Either:\n\
                     • Remove the extra arguments and edit {} directly, or\n\
                     • Point to a different file with -c/--config <path>.",
                    default_path.display(),
                    default_path.display(),
                )));
            }
            println!(
                "Configuration file {} found (default path).",
                default_path.display()
            );
            let mut config = load_file(&default_path)?;
            // Preserve the --info flag from the command line (it is not in the TOML file)
            if info_only {
                config.info = true;
            }
            return Ok(config);
        }

        eprintln!(
            "No configuration file found (-c/--config, COSMIAN_KMS_CONF, default path). Using \
             command line arguments and environment variables."
        );
        Ok(preliminary)
    }
}

impl fmt::Debug for ClapConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut x = f.debug_struct("");
        let x = x.field("config_path", &self.config_path);
        let x = x.field("print_default_config", &self.print_default_config);
        let x = x.field("db", &self.db);
        let x = if self.idp_auth.jwt_auth_provider.is_some() {
            x.field("idp_auth", &self.idp_auth)
        } else {
            x
        };
        let x = x.field("proxy", &self.proxy);
        let x = x.field("socket server", &self.socket_server);
        let x = x.field("TLS", &self.tls);
        let x = if self.socket_server.socket_server_start {
            x.field("socket server", &self.socket_server)
        } else {
            x
        };
        let x = x.field(
            "ui_index_html_folder",
            &self.ui_config.get_ui_index_html_folder(),
        );
        let x = if self.ui_config.ui_oidc_auth.ui_oidc_client_id.is_some() {
            x.field("ui_oidc_auth", &self.ui_config.ui_oidc_auth)
        } else {
            x
        };
        let kms_url = format!("{}://{}", self.http.scheme(&self.tls), self.http);
        let x = x.field("KMS http", &kms_url);
        let x = x.field("KMS public URL", &self.kms_public_url);

        let x = x.field("workspace", &self.workspace);
        let x = x.field("vendor identification", &self.vendor_identification);
        let x = x.field("default username", &self.default_username);
        let x = x.field("force default username", &self.force_default_username);
        let x = if self.google_cse_config.google_cse_enable {
            x.field(
                "google_cse_enable",
                &self.google_cse_config.google_cse_enable,
            )
            .field(
                "google_cse_disable_tokens_validation",
                &self.google_cse_config.google_cse_disable_tokens_validation,
            )
            .field(
                "google_cse_incoming_url_whitelist",
                &self.google_cse_config.google_cse_incoming_url_whitelist,
            )
            .field(
                "google_cse_migration_key",
                &self.google_cse_config.google_cse_migration_key,
            )
        } else {
            x.field(
                "google_cse_enable",
                &self.google_cse_config.google_cse_enable,
            )
        };
        let x = if self.azure_ekm_config.azure_ekm_enable {
            x.field("azure_ekm_enable", &self.azure_ekm_config.azure_ekm_enable)
                .field(
                    "azure_ekm_path_prefix",
                    &self.azure_ekm_config.azure_ekm_path_prefix,
                )
                .field(
                    "azure_ekm_disable_client_auth",
                    &self.azure_ekm_config.azure_ekm_disable_client_auth,
                )
                .field(
                    "azure_ekm_proxy_vendor",
                    &self.azure_ekm_config.azure_ekm_proxy_vendor,
                )
                .field(
                    "azure_ekm_proxy_name",
                    &self.azure_ekm_config.azure_ekm_proxy_name,
                )
                .field(
                    "azure_ekm_ekm_vendor",
                    &self.azure_ekm_config.azure_ekm_ekm_vendor,
                )
                .field(
                    "azure_ekm_ekm_product",
                    &self.azure_ekm_config.azure_ekm_ekm_product,
                )
        } else {
            x.field("azure_ekm_enable", &self.azure_ekm_config.azure_ekm_enable)
        };
        let x = x.field(
            "Microsoft Double Key Encryption URL",
            &self.ms_dke_service_url,
        );
        let x = x.field("telemetry", &self.logging);
        let x = x.field("info", &self.info);
        let x = x.field("HSM admin username", &self.hsm.hsm_admin);
        let x = x.field(
            "hsm_model",
            if self.hsm.hsm_slot.is_empty() {
                &"NO HSM"
            } else {
                &self.hsm.hsm_model
            },
        );
        let x = x.field("hsm_slots", &self.hsm.hsm_slot);
        let x = x.field(
            "hsm_passwords",
            &self
                .hsm
                .hsm_password
                .iter()
                .map(|_| "********")
                .collect::<Vec<&str>>(),
        );
        let x = x.field("key wrapping key", &self.key_encryption_key);
        let x = x.field("default unwrap type", &self.default_unwrap_type);
        let x = x.field("non_revocable_key_id", &self.non_revocable_key_id);
        let x = x.field("privileged_users", &self.privileged_users);

        let x = x.field("aws_xks_config", &self.aws_xks_config);
        let x = if self.aws_xks_config.aws_xks_enable {
            x.field("aws_xks_enable", &self.aws_xks_config.aws_xks_enable)
                .field("aws_xks_region", &self.aws_xks_config.aws_xks_region)
                .field("aws_xks_service", &self.aws_xks_config.aws_xks_service)
                .field(
                    "aws_xks_sigv4_access_key_id",
                    &self.aws_xks_config.aws_xks_sigv4_access_key_id,
                )
                .field(
                    "aws_xks_sigv4_secret_access_key",
                    &self.aws_xks_config.aws_xks_sigv4_secret_access_key,
                )
        } else {
            x.field("aws_xks_enable", &self.aws_xks_config.aws_xks_enable)
        };
        let x = x.field("kmip", &self.kmip_policy);

        x.finish()
    }
}

#[cfg(test)]
#[allow(unsafe_code, clippy::unwrap_used, clippy::expect_used)]
mod tests {
    //! Configuration precedence tests
    //!
    //! These tests validate the configuration loading precedence:
    //! 1. Command line -c/--config (highest precedence)
    //! 2. `COSMIAN_KMS_CONF` environment variable
    //! 3. Default system path
    //! 4. Command line arguments and environment variables (lowest precedence)
    //!
    //! IMPORTANT: These tests MUST be run serially to avoid environment variable
    //! and temporary file conflicts between parallel test runs:
    //! `RUST_TEST_THREADS=1 cargo test --lib config::command_line::clap_config::tests`

    use std::{
        fs,
        path::{Path, PathBuf},
        sync::Mutex,
        time::{SystemTime, UNIX_EPOCH},
    };

    use cosmian_logger::debug;

    use super::ClapConfig;

    // Global mutex to serialize environment variable access across all tests
    static ENV_MUTEX: Mutex<()> = Mutex::new(());

    fn write_temp(contents: &str) -> PathBuf {
        let mut p = std::env::temp_dir();
        let ts = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let thread_id = std::thread::current().id();
        let fname = format!("kms_test_conf_{ts}_{thread_id:?}.toml");
        p.push(fname);
        fs::write(&p, contents).expect("write temp toml");
        p
    }

    fn cleanup_temp(path: &PathBuf) {
        drop(std::fs::remove_file(path));
    }

    fn with_clean_env<F, R>(f: F) -> R
    where
        F: FnOnce() -> R,
    {
        // Acquire mutex to serialize environment variable access.
        // Use unwrap_or_else to recover from a poisoned mutex (caused by a
        // previous test panicking while holding the lock) so that one test
        // failure does not cascade into all subsequent tests.
        let _guard = ENV_MUTEX
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);

        // Save current env state
        let original_env = std::env::var("COSMIAN_KMS_CONF").ok();

        // Clear env
        unsafe {
            std::env::remove_var("COSMIAN_KMS_CONF");
        }

        // Run test
        let result = f();

        // Restore original env state
        match original_env {
            Some(val) => unsafe { std::env::set_var("COSMIAN_KMS_CONF", val) },
            None => unsafe { std::env::remove_var("COSMIAN_KMS_CONF") },
        }

        result
        // Mutex is automatically released when _guard goes out of scope
    }

    fn set_var(key: &str, value: &Path) {
        unsafe {
            std::env::set_var(key, value.display().to_string());
        }
    }

    fn default_path_exists() -> bool {
        std::path::Path::new(&super::get_default_config_path()).exists()
    }

    #[test]
    fn precedence_cli_config_over_env_and_default() {
        with_clean_env(|| {
            let cli_file = write_temp("[http]\nport=12345\n");
            let env_file = write_temp("[http]\nport=54321\n");
            set_var("COSMIAN_KMS_CONF", &env_file);
            // Command line args like --port are completely ignored when -c config file is used
            let args = vec!["kms", "-c", cli_file.to_str().unwrap(), "--port", "9999"];
            let cfg = ClapConfig::load_from_args(args).expect("load from args");
            assert_eq!(
                cfg.http.port, 12345,
                "-c config file takes precedence and ignores all other args"
            );
            cleanup_temp(&cli_file);
            cleanup_temp(&env_file);
        });
    }

    #[test]
    fn precedence_env_config_over_default() {
        with_clean_env(|| {
            // Create a unique temp file to simulate the COSMIAN_KMS_CONF file
            let env_file = write_temp("[http]\nport=23456\n");
            set_var("COSMIAN_KMS_CONF", &env_file);

            // Command line args are completely ignored when env config file exists
            let args = vec!["kms", "--port", "1111"];
            let cfg = ClapConfig::load_from_args(args).expect("load from args");
            assert_eq!(
                cfg.http.port, 23456,
                "env config file ignores all command line args"
            );

            cleanup_temp(&env_file);
        });
    }

    /// RAII guard that removes a file when dropped, ensuring cleanup even on panic.
    struct RemoveOnDrop(PathBuf);
    impl Drop for RemoveOnDrop {
        fn drop(&mut self) {
            drop(std::fs::remove_file(&self.0));
        }
    }

    #[test]
    fn precedence_default_config_loads_without_args() {
        with_clean_env(|| {
            if default_path_exists() {
                eprintln!(
                    "Skipping precedence_default_config_loads_without_args: default config \
                     already exists"
                );
                return;
            }
            let default_content = "[http]\nport=34567\n";
            let default_path = PathBuf::from(super::get_default_config_path());
            if let Some(parent) = default_path.parent() {
                drop(std::fs::create_dir_all(parent));
            }
            if std::fs::write(&default_path, default_content).is_ok() {
                let _cleanup = RemoveOnDrop(default_path);
                // No extra args beyond the binary name → config file is loaded
                let args = vec!["kms"];
                let cfg = ClapConfig::load_from_args(args).expect("load from args");
                assert_eq!(
                    cfg.http.port, 34567,
                    "default config file should be loaded when no extra args are given"
                );
            } else {
                eprintln!(
                    "Skipping precedence_default_config_loads_without_args: cannot write to \
                     default path"
                );
            }
        });
    }

    #[test]
    fn default_config_with_extra_args_is_error() {
        with_clean_env(|| {
            if default_path_exists() {
                eprintln!(
                    "Skipping default_config_with_extra_args_is_error: default config already \
                     exists"
                );
                return;
            }
            let default_content = "[http]\nport=34567\n";
            let default_path = PathBuf::from(super::get_default_config_path());
            if let Some(parent) = default_path.parent() {
                drop(std::fs::create_dir_all(parent));
            }
            if std::fs::write(&default_path, default_content).is_ok() {
                let _cleanup = RemoveOnDrop(default_path);
                // Extra CLI args when a default config exists should be rejected
                let args = vec!["kms", "--port", "2222"];
                let res = ClapConfig::load_from_args(args);
                assert!(
                    res.is_err(),
                    "should error when default config exists and extra args are given"
                );
                let err_msg = res.unwrap_err().to_string();
                assert!(
                    err_msg.contains("extra command-line arguments were also provided"),
                    "error message should mention extra args conflict: {err_msg}"
                );
            } else {
                eprintln!(
                    "Skipping default_config_with_extra_args_is_error: cannot write to default \
                     path"
                );
            }
        });
    }

    #[test]
    fn env_config_nonexistent_falls_back_to_args() {
        with_clean_env(|| {
            set_var(
                "COSMIAN_KMS_CONF",
                &PathBuf::from("/nonexistent/config.toml"),
            );
            if default_path_exists() {
                eprintln!(
                    "Skipping env_config_nonexistent_falls_back_to_args: default config exists"
                );
                return;
            }
            let args = vec!["kms", "--port", "5555"];
            let cfg = ClapConfig::load_from_args(args).expect("load from args");
            assert_eq!(
                cfg.http.port, 5555,
                "nonexistent env config should fall back to args"
            );
        });
    }

    #[test]
    fn uses_args_when_no_files() {
        with_clean_env(|| {
            if default_path_exists() {
                // Avoid false negative if a real default file is present on dev box
                eprintln!("Skipping uses_args_when_no_files: default config path exists");
                return;
            }
            let args = vec!["kms", "--port", "7777"]; // should be honored
            let cfg = ClapConfig::load_from_args(args).expect("load from args");
            assert_eq!(cfg.http.port, 7777);
        });
    }

    #[test]
    fn error_when_cli_file_missing() {
        with_clean_env(|| {
            let args = vec!["kms", "-c", "/non/existent/xxxx__nope.toml"];
            let res = ClapConfig::load_from_args(args);
            assert!(res.is_err(), "should error for missing -c file");
            let err_msg = res.unwrap_err().to_string();
            assert!(err_msg.contains("Configuration file specified with -c/--config not found"));
        });
    }

    #[test]
    fn error_when_cli_file_invalid_toml() {
        with_clean_env(|| {
            let invalid_file = write_temp("invalid toml content [[[");
            let args = vec!["kms", "-c", invalid_file.to_str().unwrap()];
            let res = ClapConfig::load_from_args(args);
            assert!(res.is_err(), "should error for invalid toml file");
            let err_msg = res.unwrap_err().to_string();
            assert!(err_msg.contains("Cannot parse kms server config"));
            cleanup_temp(&invalid_file);
        });
    }

    #[test]
    fn complete_precedence_chain() {
        with_clean_env(|| {
            // Test the complete precedence: -c > COSMIAN_KMS_CONF > default > args
            let cli_file = write_temp("[http]\nport=11111\n");
            let env_file = write_temp("[http]\nport=22222\n");

            // 1. CLI config wins over everything
            set_var("COSMIAN_KMS_CONF", &env_file);
            let args = vec!["kms", "-c", cli_file.to_str().unwrap(), "--port", "9999"];
            let cfg = ClapConfig::load_from_args(args).expect("load from args");
            assert_eq!(cfg.http.port, 11111, "CLI config should win");

            // 2. Test env config in a separate isolated scope
            cleanup_temp(&cli_file);

            let args = vec!["kms", "--port", "8888"];
            let cfg = ClapConfig::load_from_args(args).expect("load from args");
            assert_eq!(
                cfg.http.port, 22222,
                "Env config should win when no CLI config"
            );

            cleanup_temp(&env_file);

            // 3. Clear env var and test args win when no config files
            unsafe {
                std::env::remove_var("COSMIAN_KMS_CONF");
            }
            if !default_path_exists() {
                let args = vec!["kms", "--port", "7777"];
                let cfg = ClapConfig::load_from_args(args).expect("load from args");
                assert_eq!(cfg.http.port, 7777, "Args should win when no config files");
            }
        });
    }

    #[test]
    #[expect(clippy::unwrap_used)]
    fn test_server_configuration_file() {
        let conf = ClapConfig::default();
        let conf_str = toml::to_string_pretty(&conf).unwrap();
        debug!("Configuration TOML: {conf_str}");
    }

    #[test]
    #[expect(clippy::unwrap_used)]
    fn test_server_idp() {
        let mut conf = ClapConfig::default();
        conf.idp_auth.jwt_auth_provider = Some(vec![
            "https://issuer1.example.com,jwks_uri_1,audience1,audience2".to_owned(),
            "https://issuer2.example.com,,audience3".to_owned(),
            "https://issuer3.example.com".to_owned(),
        ]);
        let conf_str = toml::to_string_pretty(&conf).unwrap();
        debug!("Configuration TOML: {conf_str}");
    }
}
