use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

/// Matches `${ssm:/path/to/param}` references in config values.
static SSM_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$\{ssm:([^}]+)\}").unwrap());

/// Replace every `${ssm:...}` reference in `input` with the parameter value
/// fetched from SSM Parameter Store.
async fn resolve_ssm_refs(
    aws: &crate::integrations::aws::AwsClient,
    input: &str,
) -> crate::error::Result<String> {
    let mut result = input.to_string();
    for cap in SSM_PATTERN.captures_iter(input) {
        let param_name = &cap[1];
        let value = aws.get_ssm_parameter(param_name).await?;
        result = result.replace(&format!("${{ssm:{}}}", param_name), &value);
    }
    Ok(result)
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Root configuration loaded from `apiforge.toml`.
pub struct Config {
    pub project: ProjectConfig,
    pub git: GitConfig,
    pub docker: DockerConfig,
    pub kubernetes: KubernetesConfig,
    pub aws: AwsConfig,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cloudfront: Option<CloudFrontConfig>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub github: Option<GitHubConfig>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub notifications: Option<NotificationsConfig>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub health_check: Option<HealthCheckConfig>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Project identity and language metadata.
pub struct ProjectConfig {
    pub name: String,
    pub language: Language,
}

#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
/// Supported project language types for version file detection.
pub enum Language {
    Rust,
    Node,
    Python,
    Go,
    Java,
}

impl Language {
    pub fn version_file(&self) -> &str {
        match self {
            Language::Rust => "Cargo.toml",
            Language::Node => "package.json",
            Language::Python => "pyproject.toml",
            Language::Go => "go.mod",
            Language::Java => "pom.xml",
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Git settings for branch checks, tagging, and commit behavior.
pub struct GitConfig {
    pub main_branch: String,
    pub tag_format: String,
    #[serde(default = "default_true")]
    pub changelog: bool,
    pub commit_message: String,
    #[serde(default = "default_remote")]
    pub remote: String,
    #[serde(default = "default_true")]
    pub require_clean: bool,
    #[serde(default = "default_true")]
    pub require_main_branch: bool,
    /// Timeout in seconds for git fetch operations (default: 60)
    #[serde(default = "default_git_fetch_timeout")]
    pub fetch_timeout_secs: u64,
    /// Timeout in seconds for git push operations (default: 120)
    #[serde(default = "default_git_push_timeout")]
    pub push_timeout_secs: u64,
    /// Timeout in seconds for other git operations (default: 30)
    #[serde(default = "default_git_operation_timeout")]
    pub operation_timeout_secs: u64,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Docker build and push settings.
pub struct DockerConfig {
    pub registry: DockerRegistry,
    pub repository: String,
    #[serde(default = "default_dockerfile")]
    pub dockerfile: String,
    #[serde(default = "default_context")]
    pub context: String,
    pub tags: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub build_args: Option<HashMap<String, String>>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
/// Supported container registry backends.
pub enum DockerRegistry {
    AwsEcr,
    DockerHub,
    Ghcr,
    Custom,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Kubernetes deployment rollout settings.
pub struct KubernetesConfig {
    pub context: String,
    pub namespace: String,
    pub deployment: String,
    pub manifest_path: String,
    pub image_field: String,
    #[serde(default = "default_rollout_timeout")]
    pub rollout_timeout: u64,
    #[serde(default = "default_min_ready_percent")]
    pub min_ready_percent: u8,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// AWS configuration used by cloud integrations.
pub struct AwsConfig {
    pub region: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub profile: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// CloudFront cache invalidation after a successful deploy.
pub struct CloudFrontConfig {
    /// CloudFront distribution ID (e.g. `E1ABCD23EFGH45`).
    pub distribution_id: String,
    /// Paths to invalidate (defaults to everything).
    #[serde(default = "default_cloudfront_paths")]
    pub paths: Vec<String>,
}

fn default_cloudfront_paths() -> Vec<String> {
    vec!["/*".to_string()]
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// GitHub release settings.
pub struct GitHubConfig {
    pub repository: String,
    pub token: String,
    #[serde(default = "default_true")]
    pub create_release: bool,
    #[serde(default = "default_false")]
    pub prerelease: bool,
    #[serde(default = "default_false")]
    pub draft: bool,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Outbound notification configuration.
pub struct NotificationsConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slack: Option<SlackConfig>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub webhook: Option<WebhookConfig>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Slack notification settings.
pub struct SlackConfig {
    pub webhook_url: String,
    pub message: String,
    #[serde(default = "default_notify_on")]
    pub notify_on: NotifyOn,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Generic webhook notification settings.
pub struct WebhookConfig {
    pub url: String,
    #[serde(default = "default_method")]
    pub method: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub headers: Option<HashMap<String, String>>,
    pub body: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
/// Notification trigger mode.
pub enum NotifyOn {
    Success,
    Failure,
    Both,
}

#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "UPPERCASE")]
/// HTTP methods supported by health checks.
pub enum HttpMethod {
    #[default]
    GET,
    POST,
    HEAD,
    PUT,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
/// Health endpoint polling configuration.
pub struct HealthCheckConfig {
    pub url: String,
    #[serde(default = "default_http_method")]
    pub method: HttpMethod,
    #[serde(default = "default_expected_status")]
    pub expected_status: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expected_body_field: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expected_body_value: Option<String>,
    #[serde(default = "default_health_timeout")]
    pub timeout: u64,
    #[serde(default = "default_health_interval")]
    pub interval: u64,
}

fn default_http_method() -> HttpMethod {
    HttpMethod::GET
}

fn default_true() -> bool {
    true
}

fn default_false() -> bool {
    false
}

fn default_remote() -> String {
    "origin".to_string()
}

fn default_dockerfile() -> String {
    "Dockerfile".to_string()
}

fn default_context() -> String {
    ".".to_string()
}

fn default_rollout_timeout() -> u64 {
    300
}

fn default_min_ready_percent() -> u8 {
    100
}

fn default_notify_on() -> NotifyOn {
    NotifyOn::Both
}

fn default_method() -> String {
    "POST".to_string()
}

fn default_git_fetch_timeout() -> u64 {
    60
}

fn default_git_push_timeout() -> u64 {
    120
}

fn default_git_operation_timeout() -> u64 {
    30
}

fn default_expected_status() -> u16 {
    200
}

fn default_health_timeout() -> u64 {
    60
}

fn default_health_interval() -> u64 {
    5
}

impl Config {
    pub fn from_file(path: &PathBuf) -> crate::error::Result<Self> {
        let content = std::fs::read_to_string(path).map_err(|e| {
            crate::error::ApiForgeError::Config(format!("Failed to read config file: {}", e))
        })?;

        let mut config: Config = toml::from_str(&content).map_err(|e| {
            crate::error::ApiForgeError::Config(format!("Failed to parse config: {}", e))
        })?;

        config.resolve_env_vars()?;
        config.validate()?;
        Ok(config)
    }

    /// Resolve `${VAR}` environment variable references in fields that may
    /// carry secrets or environment-specific values (tokens, webhook URLs).
    ///
    /// Fails with `EnvVarMissing` if a referenced variable is not set, so a
    /// misconfigured environment surfaces at config load instead of as an
    /// opaque auth failure mid-release.
    fn resolve_env_vars(&mut self) -> crate::error::Result<()> {
        use crate::utils::env::resolve_env_vars;

        if let Some(ref mut github) = self.github {
            github.token = resolve_env_vars(&github.token)?;
            github.repository = resolve_env_vars(&github.repository)?;
        }

        if let Some(ref mut notifications) = self.notifications {
            if let Some(ref mut slack) = notifications.slack {
                slack.webhook_url = resolve_env_vars(&slack.webhook_url)?;
            }
            if let Some(ref mut webhook) = notifications.webhook {
                webhook.url = resolve_env_vars(&webhook.url)?;
                webhook.body = resolve_env_vars(&webhook.body)?;
                if let Some(ref mut headers) = webhook.headers {
                    for value in headers.values_mut() {
                        *value = resolve_env_vars(value)?;
                    }
                }
            }
        }

        if let Some(ref mut hc) = self.health_check {
            hc.url = resolve_env_vars(&hc.url)?;
        }

        Ok(())
    }

    /// Whether any secret-bearing field contains an `${ssm:/path}` reference.
    /// Used to avoid constructing an AWS client (and requiring credentials)
    /// for projects that don't use SSM at all.
    pub fn has_ssm_references(&self) -> bool {
        let mut fields: Vec<&str> = Vec::new();
        if let Some(ref github) = self.github {
            fields.push(&github.token);
        }
        if let Some(ref notifications) = self.notifications {
            if let Some(ref slack) = notifications.slack {
                fields.push(&slack.webhook_url);
            }
            if let Some(ref webhook) = notifications.webhook {
                fields.push(&webhook.url);
                fields.push(&webhook.body);
                if let Some(ref headers) = webhook.headers {
                    fields.extend(headers.values().map(|v| v.as_str()));
                }
            }
        }
        if let Some(ref hc) = self.health_check {
            fields.push(&hc.url);
        }
        fields.iter().any(|f| SSM_PATTERN.is_match(f))
    }

    /// Resolve `${ssm:/path/to/param}` references in secret-bearing fields
    /// via AWS SSM Parameter Store (with decryption). No-op when the config
    /// contains no SSM references. Call after loading, before using secrets.
    pub async fn resolve_ssm_parameters(&mut self) -> crate::error::Result<()> {
        if !self.has_ssm_references() {
            return Ok(());
        }

        if self.aws.region.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "aws.region is required to resolve ${ssm:...} references".to_string(),
            ));
        }

        let aws = if let Some(ref profile) = self.aws.profile {
            crate::integrations::aws::AwsClient::with_profile(&self.aws.region, profile).await?
        } else {
            crate::integrations::aws::AwsClient::new(&self.aws.region).await?
        };

        if let Some(ref mut github) = self.github {
            github.token = resolve_ssm_refs(&aws, &github.token).await?;
        }
        if let Some(ref mut notifications) = self.notifications {
            if let Some(ref mut slack) = notifications.slack {
                slack.webhook_url = resolve_ssm_refs(&aws, &slack.webhook_url).await?;
            }
            if let Some(ref mut webhook) = notifications.webhook {
                webhook.url = resolve_ssm_refs(&aws, &webhook.url).await?;
                webhook.body = resolve_ssm_refs(&aws, &webhook.body).await?;
                if let Some(ref mut headers) = webhook.headers {
                    for value in headers.values_mut() {
                        *value = resolve_ssm_refs(&aws, value).await?;
                    }
                }
            }
        }
        if let Some(ref mut hc) = self.health_check {
            hc.url = resolve_ssm_refs(&aws, &hc.url).await?;
        }

        Ok(())
    }

    pub fn validate(&self) -> crate::error::Result<()> {
        // Git validations
        if !self.git.tag_format.contains("{version}") {
            return Err(crate::error::ApiForgeError::Config(
                "git.tag_format must contain {version} placeholder".to_string(),
            ));
        }

        if self.git.fetch_timeout_secs == 0 {
            return Err(crate::error::ApiForgeError::Config(
                "git.fetch_timeout_secs must be greater than 0".to_string(),
            ));
        }

        if self.git.push_timeout_secs == 0 {
            return Err(crate::error::ApiForgeError::Config(
                "git.push_timeout_secs must be greater than 0".to_string(),
            ));
        }

        if self.git.operation_timeout_secs == 0 {
            return Err(crate::error::ApiForgeError::Config(
                "git.operation_timeout_secs must be greater than 0".to_string(),
            ));
        }

        // Docker validations
        if self.docker.repository.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "docker.repository cannot be empty".to_string(),
            ));
        }

        if self.docker.tags.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "docker.tags must have at least one tag pattern".to_string(),
            ));
        }

        // Kubernetes validations
        if self.kubernetes.namespace.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "kubernetes.namespace cannot be empty".to_string(),
            ));
        }

        if self.kubernetes.deployment.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "kubernetes.deployment cannot be empty".to_string(),
            ));
        }

        if self.kubernetes.context.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "kubernetes.context cannot be empty".to_string(),
            ));
        }

        if self.kubernetes.min_ready_percent > 100 {
            return Err(crate::error::ApiForgeError::Config(
                "kubernetes.min_ready_percent must be between 0-100".to_string(),
            ));
        }

        if self.kubernetes.rollout_timeout == 0 {
            return Err(crate::error::ApiForgeError::Config(
                "kubernetes.rollout_timeout must be greater than 0".to_string(),
            ));
        }

        // AWS region validation (if ECR is used)
        if matches!(self.docker.registry, DockerRegistry::AwsEcr) && self.aws.region.is_empty() {
            return Err(crate::error::ApiForgeError::Config(
                "aws.region is required when using ECR registry".to_string(),
            ));
        }

        // CloudFront validations
        if let Some(ref cf) = self.cloudfront {
            if cf.distribution_id.is_empty() {
                return Err(crate::error::ApiForgeError::Config(
                    "cloudfront.distribution_id cannot be empty".to_string(),
                ));
            }
            if cf.paths.is_empty() {
                return Err(crate::error::ApiForgeError::Config(
                    "cloudfront.paths must have at least one path".to_string(),
                ));
            }
            for path in &cf.paths {
                if !path.starts_with('/') {
                    return Err(crate::error::ApiForgeError::Config(format!(
                        "cloudfront path '{}' must start with '/'",
                        path
                    )));
                }
            }
            if self.aws.region.is_empty() {
                return Err(crate::error::ApiForgeError::Config(
                    "aws.region is required when using CloudFront invalidation".to_string(),
                ));
            }
        }

        // Docker tag format validation
        // Compile regex once outside the loop
        let tag_regex = regex::Regex::new(r"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$").unwrap();
        for tag in &self.docker.tags {
            // Docker tags must be <= 128 chars, start with letter/number,
            // and only contain letters, numbers, periods, underscores, dashes
            if tag.is_empty() {
                return Err(crate::error::ApiForgeError::Config(
                    "docker.tags cannot contain empty strings".to_string(),
                ));
            }

            // Resolve supported placeholders to validate final docker tag syntax.
            // Supported placeholders mirror docker tag expansion in steps.
            let resolved_tag = tag
                .replace("{version}", "1.2.3")
                .replace("{major}", "1")
                .replace("{minor}", "2")
                .replace("{patch}", "3")
                .replace("{git_sha}", "abcdef0")
                .replace("{git_sha_full}", "abcdef0123456789");

            if resolved_tag.contains('{') || resolved_tag.contains('}') {
                return Err(crate::error::ApiForgeError::Config(format!(
                    "docker tag '{}' contains unsupported placeholder(s). Supported placeholders: {{version}}, {{major}}, {{minor}}, {{patch}}, {{git_sha}}, {{git_sha_full}}",
                    tag
                )));
            }

            if resolved_tag.len() > 128 {
                return Err(crate::error::ApiForgeError::Config(format!(
                    "docker tag '{}' exceeds 128 character limit after template resolution",
                    tag
                )));
            }
            if !tag_regex.is_match(&resolved_tag) {
                return Err(crate::error::ApiForgeError::Config(format!(
                    "docker tag '{}' has invalid format after template resolution ('{}'). Tags must start with alphanumeric and contain only [a-zA-Z0-9_.-]",
                    tag, resolved_tag
                )));
            }
        }

        // Kubernetes image field validation
        if !self.kubernetes.manifest_path.is_empty() {
            // The image field in kubernetes config should be a valid reference format
            // Pattern: [registry/]repository[:tag][@digest]
            // For now just ensure it's not empty when we expect to update images
        }

        // Health check validations
        if let Some(ref hc) = self.health_check {
            if hc.url.is_empty() {
                return Err(crate::error::ApiForgeError::Config(
                    "health_check.url cannot be empty".to_string(),
                ));
            }

            if hc.timeout == 0 {
                return Err(crate::error::ApiForgeError::Config(
                    "health_check.timeout must be greater than 0".to_string(),
                ));
            }

            if hc.interval == 0 {
                return Err(crate::error::ApiForgeError::Config(
                    "health_check.interval must be greater than 0".to_string(),
                ));
            }
        }

        // Notification validations
        if let Some(ref notify) = self.notifications {
            if let Some(ref slack) = notify.slack {
                if slack.webhook_url.is_empty() {
                    return Err(crate::error::ApiForgeError::Config(
                        "notifications.slack.webhook_url cannot be empty".to_string(),
                    ));
                }
            }
        }

        Ok(())
    }

    pub fn save(&self, path: &PathBuf) -> crate::error::Result<()> {
        let content = toml::to_string_pretty(self).map_err(|e| {
            crate::error::ApiForgeError::Config(format!("Failed to serialize config: {}", e))
        })?;

        std::fs::write(path, content).map_err(|e| {
            crate::error::ApiForgeError::Config(format!("Failed to write config file: {}", e))
        })?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    fn write_config(content: &str) -> tempfile::NamedTempFile {
        let mut file = tempfile::NamedTempFile::new().unwrap();
        file.write_all(content.as_bytes()).unwrap();
        file
    }

    fn base_config(extra: &str) -> String {
        format!(
            r#"[project]
name = "test"
language = "rust"

[git]
main_branch = "main"
tag_format = "v{{version}}"
commit_message = "release"

[docker]
registry = "aws_ecr"
repository = "test"
tags = ["{{version}}"]

[kubernetes]
context = "prod"
namespace = "default"
deployment = "test"
manifest_path = "k8s/deployment.yaml"
image_field = "0"

[aws]
region = "us-east-1"
{extra}"#
        )
    }

    #[test]
    fn test_from_file_resolves_env_vars_in_secrets() {
        std::env::set_var("APIFORGE_TEST_GH_TOKEN", "ghx_resolved_token");
        let file = write_config(&base_config(
            "\n[github]\nrepository = \"owner/repo\"\ntoken = \"${APIFORGE_TEST_GH_TOKEN}\"\n",
        ));

        let config = Config::from_file(&file.path().to_path_buf()).unwrap();
        assert_eq!(
            config.github.unwrap().token,
            "ghx_resolved_token",
            "env var reference must be resolved at load time"
        );
    }

    #[test]
    fn test_from_file_errors_on_missing_env_var() {
        std::env::remove_var("APIFORGE_TEST_MISSING_TOKEN");
        let file = write_config(&base_config(
            "\n[github]\nrepository = \"owner/repo\"\ntoken = \"${APIFORGE_TEST_MISSING_TOKEN}\"\n",
        ));

        let err = Config::from_file(&file.path().to_path_buf()).unwrap_err();
        assert!(
            err.to_string().contains("APIFORGE_TEST_MISSING_TOKEN"),
            "error should name the missing variable, got: {err}"
        );
    }

    #[test]
    fn test_from_file_resolves_slack_webhook_env() {
        std::env::set_var(
            "APIFORGE_TEST_SLACK_URL",
            "https://hooks.slack.com/services/T0/B0/x",
        );
        let file = write_config(&base_config(
            "\n[notifications.slack]\nwebhook_url = \"${APIFORGE_TEST_SLACK_URL}\"\nmessage = \"{{ version }}\"\n",
        ));

        let config = Config::from_file(&file.path().to_path_buf()).unwrap();
        assert_eq!(
            config.notifications.unwrap().slack.unwrap().webhook_url,
            "https://hooks.slack.com/services/T0/B0/x"
        );
    }

    #[test]
    fn test_validate_rejects_unknown_tag_placeholder() {
        let file = write_config(&base_config("").replace("{version}\"]", "{unknown}\"]"));
        assert!(Config::from_file(&file.path().to_path_buf()).is_err());
    }
}

#[cfg(test)]
mod aws_feature_tests {
    use super::*;
    use std::io::Write;

    fn write_config(content: &str) -> tempfile::NamedTempFile {
        let mut file = tempfile::NamedTempFile::new().unwrap();
        file.write_all(content.as_bytes()).unwrap();
        file
    }

    fn base_config(extra: &str) -> String {
        format!(
            r#"[project]
name = "test"
language = "rust"

[git]
main_branch = "main"
tag_format = "v{{version}}"
commit_message = "release"

[docker]
registry = "ghcr"
repository = "test"
tags = ["{{version}}"]

[kubernetes]
context = "prod"
namespace = "default"
deployment = "test"
manifest_path = "k8s/deployment.yaml"
image_field = "0"

[aws]
region = "us-east-1"
{extra}"#
        )
    }

    #[test]
    fn test_cloudfront_config_parses_with_default_paths() {
        let file = write_config(&base_config(
            "\n[cloudfront]\ndistribution_id = \"E1ABCD23EFGH45\"\n",
        ));
        let config = Config::from_file(&file.path().to_path_buf()).unwrap();
        let cf = config.cloudfront.unwrap();
        assert_eq!(cf.distribution_id, "E1ABCD23EFGH45");
        assert_eq!(cf.paths, vec!["/*".to_string()]);
    }

    #[test]
    fn test_cloudfront_rejects_empty_distribution() {
        let file = write_config(&base_config("\n[cloudfront]\ndistribution_id = \"\"\n"));
        assert!(Config::from_file(&file.path().to_path_buf()).is_err());
    }

    #[test]
    fn test_cloudfront_rejects_relative_paths() {
        let file = write_config(&base_config(
            "\n[cloudfront]\ndistribution_id = \"E1\"\npaths = [\"api/*\"]\n",
        ));
        assert!(Config::from_file(&file.path().to_path_buf()).is_err());
    }

    #[test]
    fn test_ssm_reference_detection() {
        let file = write_config(&base_config(
            "\n[github]\nrepository = \"o/r\"\ntoken = \"${ssm:/apiforge/github-token}\"\n",
        ));
        let config = Config::from_file(&file.path().to_path_buf()).unwrap();
        assert!(config.has_ssm_references());
        // env-var resolution must NOT consume the ssm reference
        assert_eq!(
            config.github.as_ref().unwrap().token,
            "${ssm:/apiforge/github-token}"
        );
    }

    #[test]
    fn test_no_ssm_references_in_plain_config() {
        let file = write_config(&base_config(""));
        let config = Config::from_file(&file.path().to_path_buf()).unwrap();
        assert!(!config.has_ssm_references());
    }
}
