use rumdl_lib::config::Config; // Ensure Config is imported
use rumdl_lib::config::RuleRegistry;
use rumdl_lib::rules::*;
use serial_test::serial;
use std::fs;
use tempfile::tempdir; // For temporary directory // Add back env import // Ensure SourcedConfig is imported

#[test]
fn test_load_config_file() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Create a temporary config file within the temp dir using full path
    let config_path = temp_path.join("test_config.toml");
    let config_content = r#"
[global]
disable = ["MD013"]
enable = ["MD001", "MD003"]
include = ["docs/*.md"]
exclude = [".git"]

[MD013]
line_length = 120
code_blocks = false
tables = true
"#;

    fs::write(&config_path, config_content).expect("Failed to write test config file");

    // Test loading the config using the full path
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let sourced_result = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true);
    assert!(
        sourced_result.is_ok(),
        "SourcedConfig loading should succeed. Error: {:?}",
        sourced_result.err()
    );

    let config: Config = sourced_result.unwrap().into();

    // Verify global settings
    assert_eq!(config.global.disable, vec!["MD013"]);
    assert_eq!(config.global.enable, vec!["MD001", "MD003"]);
    assert_eq!(config.global.include, vec!["docs/*.md"]);
    assert_eq!(config.global.exclude, vec![".git"]);
    assert!(config.global.respect_gitignore);

    // Verify rule-specific settings
    let line_length = rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD013", "line_length");
    assert_eq!(line_length, Some(120));

    let code_blocks = rumdl_lib::config::get_rule_config_value::<bool>(&config, "MD013", "code_blocks");
    assert_eq!(code_blocks, Some(false));

    let tables = rumdl_lib::config::get_rule_config_value::<bool>(&config, "MD013", "tables");
    assert_eq!(tables, Some(true));

    // No explicit cleanup needed, tempdir is dropped at end of scope
}

#[test]
fn test_load_nonexistent_config() {
    // Test loading a nonexistent config file using SourcedConfig::load
    let sourced_result =
        rumdl_lib::config::SourcedConfig::load_with_discovery(Some("nonexistent_config.toml"), None, true);
    assert!(sourced_result.is_err(), "Loading nonexistent config should fail");

    if let Err(err) = sourced_result {
        assert!(
            err.to_string().contains("Failed to read config file"),
            "Error message should indicate file reading failure"
        );
    }
}

#[test]
fn test_default_config() {
    // Reverted to simple version: No file I/O, no tempdir, no env calls needed
    let config = Config::default();

    // Check default global settings
    assert!(config.global.include.is_empty(), "Default include should be empty");
    assert!(config.global.exclude.is_empty(), "Default exclude should be empty");
    assert!(config.global.enable.is_empty(), "Default enable should be empty");
    assert!(config.global.disable.is_empty(), "Default disable should be empty");
    assert!(
        config.global.respect_gitignore,
        "Default respect_gitignore should be true"
    );

    // Check that the default rules map is empty
    assert!(config.rules.is_empty(), "Default rules map should be empty");
}

#[test]
fn test_create_default_config() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Define path for default config within the temp dir
    let config_path = temp_path.join("test_default_config.toml");

    // Delete the file first if it exists (shouldn't in temp dir, but good practice)
    if config_path.exists() {
        fs::remove_file(&config_path).expect("Failed to remove existing test file");
    }

    // Create the default config using the full path
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let result = rumdl_lib::config::create_default_config(config_path_str);
    assert!(
        result.is_ok(),
        "Creating default config should succeed: {:?}",
        result.err()
    );

    // Verify the file exists using the full path
    assert!(config_path.exists(), "Default config file should exist in temp dir");

    // Load the created config using SourcedConfig::load
    let sourced_result = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true);
    assert!(
        sourced_result.is_ok(),
        "Loading created config should succeed: {:?}",
        sourced_result.err()
    );
    // Convert to Config if needed for further assertions
    // let config: Config = sourced_result.unwrap().into();
    // Optional: Add more assertions about the loaded default config content if needed
    // No explicit cleanup needed, tempdir handles it.
}

#[test]
fn test_rule_configuration_application() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Create a temporary config file with specific rule settings using full path
    let config_path = temp_path.join("test_rule_config.toml");
    let config_content = r#"
[MD013]
line_length = 150

[MD004]
style = "asterisk"
"#;
    fs::write(&config_path, config_content).expect("Failed to write test config file");

    // Load the config using SourcedConfig::load
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let sourced_config = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load sourced config");
    // Convert to Config for rule application logic
    let config: Config = sourced_config.into();

    // Create a test rule with the loaded config
    let mut rules: Vec<Box<dyn rumdl_lib::rule::Rule>> = vec![
        Box::new(MD013LineLength::default()),
        Box::new(MD004UnorderedListStyle::new(UnorderedListStyle::Consistent)),
    ];

    // Apply configuration to rules (similar to apply_rule_configs)
    // For MD013
    if let Some(pos) = rules.iter().position(|r| r.name() == "MD013") {
        let line_length =
            rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD013", "line_length").unwrap_or(80);
        let code_blocks =
            rumdl_lib::config::get_rule_config_value::<bool>(&config, "MD013", "code_blocks").unwrap_or(true);
        let tables = rumdl_lib::config::get_rule_config_value::<bool>(&config, "MD013", "tables").unwrap_or(false);
        let headings = rumdl_lib::config::get_rule_config_value::<bool>(&config, "MD013", "headings").unwrap_or(true);
        let strict = rumdl_lib::config::get_rule_config_value::<bool>(&config, "MD013", "strict").unwrap_or(false);
        rules[pos] = Box::new(MD013LineLength::new(line_length, code_blocks, tables, headings, strict));
    }

    // Test with a file that would violate MD013 at 80 chars but not at 150
    let test_content = "# Test\n\nThis is a line that exceeds 80 characters but not 150 characters. It's specifically designed for our test case.";

    // Run the linter with our configured rules
    let warnings = rumdl_lib::lint(test_content, &rules, false, rumdl_lib::config::MarkdownFlavor::Standard)
        .expect("Linting should succeed");

    // Verify no MD013 warnings because line_length is set to 150
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();

    assert_eq!(
        md013_warnings, 0,
        "No MD013 warnings should be generated with line_length 150"
    );

    // No explicit cleanup needed.
}

#[test]
fn test_multiple_rules_configuration() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Test that multiple rules can be configured simultaneously
    let config_path = temp_path.join("test_multi_rule_config.toml");
    let config_content = r#"
[global]
disable = []

[MD013]
line_length = 100

[MD046]
style = "fenced"

[MD048]
style = "backtick"
"#;

    fs::write(&config_path, config_content).expect("Failed to write test config file");

    // Load the config using SourcedConfig::load
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let sourced_config = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load sourced config");
    // Convert to Config for rule verification
    let config: Config = sourced_config.into();

    // Verify multiple rule configs
    let md013_line_length = rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD013", "line_length");
    assert_eq!(md013_line_length, Some(100));

    let md046_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD046", "style");
    assert_eq!(md046_style, Some("fenced".to_string()));

    let md048_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD048", "style");
    assert_eq!(md048_style, Some("backtick".to_string()));

    // No explicit cleanup needed.
}

#[test]
fn test_invalid_config_format() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Create a temporary config file with invalid TOML syntax
    let config_path = temp_path.join("invalid_config.toml");
    let invalid_config_content = r#"
[global]
disable = ["MD013" # Missing closing bracket
"#;
    fs::write(&config_path, invalid_config_content).expect("Failed to write invalid config file");

    // Attempt to load the invalid config using SourcedConfig::load
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let sourced_result = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true);
    assert!(sourced_result.is_err(), "Loading invalid config should fail");

    if let Err(err) = sourced_result {
        assert!(
            err.to_string().contains("Failed to parse TOML"),
            "Error message should indicate parsing failure: {err}"
        );
    }
}

// Integration test that verifies rule behavior changes with configuration
#[test]
fn test_integration_rule_behavior() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Test interaction between config and rule behavior within the temp dir
    let config_path = temp_path.join("test_integration_config.toml");
    let config_content = r#"
[MD013]
line_length = 60 # Override default

[MD004]
style = "dash"
"#;
    fs::write(&config_path, config_content).expect("Failed to write integration config file");

    // Load config using SourcedConfig::load
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let sourced_config = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load integration config");
    let config: Config = sourced_config.into(); // Convert for use

    // Test MD013 behavior with line_length = 60
    let mut rules_md013: Vec<Box<dyn rumdl_lib::rule::Rule>> = vec![Box::new(MD013LineLength::default())];
    // Apply config specifically for MD013 test
    if let Some(pos) = rules_md013.iter().position(|r| r.name() == "MD013") {
        let line_length =
            rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD013", "line_length").unwrap_or(80);
        rules_md013[pos] = Box::new(MD013LineLength::new(line_length, true, false, true, false));
    }

    let short_content = "# Test\nThis line is short.";
    let long_content = "# Test\nThis line is definitely longer than the sixty characters limit we set.";

    let warnings_short = rumdl_lib::lint(
        short_content,
        &rules_md013,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
    )
    .unwrap();
    let warnings_long = rumdl_lib::lint(
        long_content,
        &rules_md013,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
    )
    .unwrap();

    assert!(
        warnings_short.iter().all(|w| w.rule_name.as_deref() != Some("MD013")),
        "MD013 should not trigger for short line with config"
    );
    assert!(
        warnings_long.iter().any(|w| w.rule_name.as_deref() == Some("MD013")),
        "MD013 should trigger for long line with config"
    );

    // Test MD004 behavior with style = "dash"
    // (Similar setup: create rule, apply config, test with relevant content)
    // ... add MD004 test logic here if desired ...
    // No explicit cleanup needed.
}

#[test]
fn test_config_validation_unknown_rule() {
    let temp_dir = tempdir().unwrap();
    let config_path = temp_dir.path().join("unknown_rule.toml");
    let config_content = r#"[UNKNOWN_RULE]"#;
    fs::write(&config_path, config_content).unwrap();
    let sourced =
        rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true)
            .expect("config should load successfully"); // Use load
    let rules = rumdl_lib::all_rules(&rumdl_lib::config::Config::default()); // Use all_rules instead of get_rules
    let registry = RuleRegistry::from_rules(&rules);
    let warnings = rumdl_lib::config::validate_config_sourced(&sourced, &registry); // Use validate_config_sourced
    assert_eq!(warnings.len(), 0);
}

#[test]
fn test_config_validation_unknown_option() {
    let temp_dir = tempdir().unwrap();
    let config_path = temp_dir.path().join("unknown_option.toml");
    let config_content = r#"[MD013]
unknown_opt = true"#;
    fs::write(&config_path, config_content).unwrap();
    let sourced =
        rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true)
            .expect("config should load successfully"); // Use load
    let rules = rumdl_lib::all_rules(&rumdl_lib::config::Config::default()); // Use all_rules instead of get_rules
    let registry = RuleRegistry::from_rules(&rules);
    let warnings = rumdl_lib::config::validate_config_sourced(&sourced, &registry); // Use validate_config_sourced
    assert_eq!(warnings.len(), 1);
    assert!(warnings[0].message.contains("Unknown option"));
}

#[test]
fn test_config_validation_type_mismatch() {
    let temp_dir = tempdir().unwrap();
    let config_path = temp_dir.path().join("type_mismatch.toml");
    let config_content = r#"[MD013]
line_length = "not a number""#;
    fs::write(&config_path, config_content).unwrap();
    let sourced =
        rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true)
            .expect("config should load successfully"); // Use load
    let rules = rumdl_lib::all_rules(&rumdl_lib::config::Config::default()); // Use all_rules instead of get_rules
    let registry = RuleRegistry::from_rules(&rules);
    let warnings = rumdl_lib::config::validate_config_sourced(&sourced, &registry); // Use validate_config_sourced
    assert_eq!(warnings.len(), 1);
    assert!(warnings[0].message.contains("Type mismatch"));
}

#[test]
fn test_config_validation_unknown_global_option() {
    let temp_dir = tempdir().unwrap();
    let config_path = temp_dir.path().join("unknown_global.toml");
    let config_content = r#"[global]
unknown_global = true"#;
    fs::write(&config_path, config_content).unwrap();
    let sourced =
        rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true)
            .expect("config should load successfully");
    let rules = rumdl_lib::all_rules(&rumdl_lib::config::Config::default());
    let registry = RuleRegistry::from_rules(&rules);
    let warnings = rumdl_lib::config::validate_config_sourced(&sourced, &registry);

    // Should detect the unknown global key "unknown_global"
    let global_warnings = warnings.iter().filter(|w| w.rule.is_none()).count();
    assert_eq!(
        global_warnings, 1,
        "Expected 1 unknown global option warning for 'unknown_global'"
    );

    // Verify the warning message contains "unknown_global" or "unknown-global"
    let has_unknown_key_warning = warnings
        .iter()
        .any(|w| w.message.contains("unknown_global") || w.message.contains("unknown-global"));
    assert!(
        has_unknown_key_warning,
        "Expected warning about unknown_global, got: {warnings:?}"
    );
}

#[test]
fn test_pyproject_toml_root_level_config() {
    let temp_dir = tempdir().expect("Failed to create temporary directory");
    let temp_path = temp_dir.path();

    // Create a temporary config file with specific rule settings using full path
    let config_path = temp_path.join("pyproject.toml");
    // Content for the pyproject.toml file (using [tool.rumdl])
    let config_content = r#"
[tool.rumdl]
line-length = 120
disable = ["MD033"]
enable = ["MD001", "MD004"]
include = ["docs/*.md"]
exclude = ["node_modules"]
respect-gitignore = true

# Rule-specific settings to ensure they are picked up too
[tool.rumdl.MD007]
indent = 2
"#;

    // Write the content to pyproject.toml in the temp dir
    fs::write(&config_path, config_content).expect("Failed to write test pyproject.toml");

    // Load the config using the explicit path to the temp file
    let config_path_str = config_path.to_str().expect("Path should be valid UTF-8");
    let sourced_config = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load sourced config from explicit path");

    let config: Config = sourced_config.into(); // Convert to plain config for assertions

    // Check global settings (expect normalized keys)
    assert_eq!(config.global.disable, vec!["MD033".to_string()]);
    assert_eq!(config.global.enable, vec!["MD001".to_string(), "MD004".to_string()]);
    assert_eq!(config.global.include, vec!["docs/*.md".to_string()]);
    assert_eq!(config.global.exclude, vec!["node_modules".to_string()]);
    assert!(config.global.respect_gitignore);

    // Verify rule-specific settings for MD013 (implicit via line-length)
    let line_length = rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD013", "line-length");
    assert_eq!(line_length, Some(120));

    // Verify rule-specific settings for MD007 (explicit)
    let indent = rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD007", "indent");
    assert_eq!(indent, Some(2));

    // No explicit cleanup needed, tempdir handles it.
}

#[cfg(test)]
mod config_file_parsing_tests {

    use rumdl_lib::config::SourcedConfig;
    use std::fs;
    use tempfile::tempdir;

    #[test]
    fn test_json_file_detection_and_parsing() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("config.json");

        // Valid JSON config
        let config_content = r#"{
            "MD004": { "style": "dash" },
            "MD013": { "line_length": 100 }
        }"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_ok(), "Valid JSON config should load successfully");

        let config: rumdl_lib::config::Config = result.unwrap().into();
        let md004_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD004", "style");
        assert_eq!(md004_style, Some("dash".to_string()));
    }

    #[test]
    fn test_invalid_json_syntax_error() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("invalid.json");

        // Invalid JSON syntax - unquoted key
        let config_content = r#"{ MD004: { "style": "dash" } }"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_err(), "Invalid JSON should fail to parse");

        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("Failed to parse JSON"),
            "Error should mention JSON parsing: {error_msg}"
        );
        assert!(
            error_msg.contains("key must be a string"),
            "Error should be specific about the issue: {error_msg}"
        );
    }

    #[test]
    fn test_yaml_file_detection_and_parsing() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("config.yaml");

        // Valid YAML config
        let config_content = r#"
MD004:
  style: dash
MD013:
  line_length: 100
"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_ok(), "Valid YAML config should load successfully");

        let config: rumdl_lib::config::Config = result.unwrap().into();
        let md004_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD004", "style");
        assert_eq!(md004_style, Some("dash".to_string()));
    }

    #[test]
    fn test_invalid_yaml_syntax_error() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("invalid.yaml");

        // Invalid YAML syntax - incorrect indentation/structure
        let config_content = r#"
MD004:
  style: dash
  invalid: - syntax
"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_err(), "Invalid YAML should fail to parse");

        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("Failed to parse YAML"),
            "Error should mention YAML parsing: {error_msg}"
        );
    }

    #[test]
    fn test_toml_file_detection_and_parsing() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("config.toml");

        // Valid TOML config
        let config_content = r#"
[MD004]
style = "dash"

[MD013]
line_length = 100
"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_ok(), "Valid TOML config should load successfully");

        let config: rumdl_lib::config::Config = result.unwrap().into();
        let md004_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD004", "style");
        assert_eq!(md004_style, Some("dash".to_string()));
    }

    #[test]
    fn test_invalid_toml_syntax_error() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("invalid.toml");

        // Invalid TOML syntax - missing value
        let config_content = r#"
[MD004]
style = "dash"
invalid_key =
"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_err(), "Invalid TOML should fail to parse");

        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("Failed to parse TOML"),
            "Error should mention TOML parsing: {error_msg}"
        );
        assert!(
            error_msg.contains("string values must be quoted") || error_msg.contains("invalid string"),
            "Error should describe the specific issue: {error_msg}"
        );
    }

    #[test]
    fn test_markdownlint_json_file_detection() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join(".markdownlint.json");

        // Valid markdownlint JSON config
        let config_content = r#"{
            "MD004": { "style": "asterisk" },
            "line-length": { "line_length": 120 }
        }"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_ok(), "Valid markdownlint JSON should load successfully");

        let config: rumdl_lib::config::Config = result.unwrap().into();
        let md004_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD004", "style");
        assert_eq!(md004_style, Some("asterisk".to_string()));
    }

    #[test]
    fn test_markdownlint_yaml_file_detection() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join(".markdownlint.yml");

        // Valid markdownlint YAML config
        let config_content = r#"
MD004:
  style: plus
line-length:
  line_length: 90
"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        assert!(result.is_ok(), "Valid markdownlint YAML should load successfully");

        let config: rumdl_lib::config::Config = result.unwrap().into();
        let md004_style = rumdl_lib::config::get_rule_config_value::<String>(&config, "MD004", "style");
        assert_eq!(md004_style, Some("plus".to_string()));
    }

    #[test]
    fn test_file_not_found_error() {
        let result = SourcedConfig::load_with_discovery(Some("/nonexistent/config.json"), None, true);
        assert!(result.is_err(), "Nonexistent file should fail to load");

        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("Failed to read config file"),
            "Error should mention file reading failure: {error_msg}"
        );
        assert!(
            error_msg.contains("No such file or directory"),
            "Error should mention specific I/O error: {error_msg}"
        );
    }

    #[test]
    fn test_different_file_extensions_use_correct_parsers() {
        let temp_dir = tempdir().unwrap();

        // Test that .json files get JSON parsing even if content is invalid
        let json_path = temp_dir.path().join("test.json");
        fs::write(&json_path, r#"{ invalid: json }"#).unwrap();
        let json_result = SourcedConfig::load_with_discovery(Some(json_path.to_str().unwrap()), None, true);
        assert!(json_result.is_err());
        assert!(json_result.unwrap_err().to_string().contains("Failed to parse JSON"));

        // Test that .yaml files get YAML parsing even if content is invalid
        let yaml_path = temp_dir.path().join("test.yaml");
        fs::write(&yaml_path, "invalid: - yaml").unwrap();
        let yaml_result = SourcedConfig::load_with_discovery(Some(yaml_path.to_str().unwrap()), None, true);
        assert!(yaml_result.is_err());
        assert!(yaml_result.unwrap_err().to_string().contains("Failed to parse YAML"));

        // Test that .toml files get TOML parsing
        let toml_path = temp_dir.path().join("test.toml");
        fs::write(&toml_path, "invalid = ").unwrap();
        let toml_result = SourcedConfig::load_with_discovery(Some(toml_path.to_str().unwrap()), None, true);
        assert!(toml_result.is_err());
        assert!(toml_result.unwrap_err().to_string().contains("Failed to parse TOML"));

        // Test that unknown extensions default to TOML parsing
        let unknown_path = temp_dir.path().join("test.config");
        fs::write(&unknown_path, "invalid = ").unwrap();
        let unknown_result = SourcedConfig::load_with_discovery(Some(unknown_path.to_str().unwrap()), None, true);
        assert!(unknown_result.is_err());
        assert!(unknown_result.unwrap_err().to_string().contains("Failed to parse TOML"));
    }

    #[test]
    fn test_jsonc_file_support() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("config.jsonc");

        // Valid JSONC with comments (should be parsed as JSON)
        let config_content = r#"{
            // This is a comment
            "MD004": { "style": "dash" }
        }"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        // Note: This might fail if our JSON parser doesn't support comments
        // If it fails, that's actually expected behavior - JSONC requires special handling
        if let Err(error) = result {
            let error_msg = error.to_string();
            assert!(
                error_msg.contains("Failed to parse JSON"),
                "JSONC parsing should attempt JSON first"
            );
        }
    }

    #[test]
    fn test_mixed_valid_and_invalid_config_values() {
        let temp_dir = tempdir().unwrap();
        let config_path = temp_dir.path().join("mixed.json");

        // Valid JSON structure but with some invalid config values
        let config_content = r#"{
            "MD004": { "style": "valid_dash_style", "invalid_option": "should_be_ignored" },
            "MD013": { "line_length": "not_a_number" },
            "UNKNOWN_RULE": { "some_option": "value" }
        }"#;
        fs::write(&config_path, config_content).unwrap();

        let result = SourcedConfig::load_with_discovery(Some(config_path.to_str().unwrap()), None, true);
        // Config should load successfully but invalid values should be handled gracefully
        assert!(result.is_ok(), "Config with invalid values should still load");

        // Could add validation tests here if we implement config validation warnings
    }

    #[test]
    fn test_cli_integration_config_error_messages() {
        use std::process::Command;

        let temp_dir = tempdir().unwrap();

        // Use the standard Cargo environment variable for the binary path
        let binary_path = env!("CARGO_BIN_EXE_rumdl");

        // Test JSON syntax error via CLI (without --no-config so config is actually loaded)
        let json_path = temp_dir.path().join("invalid.json");
        fs::write(&json_path, r#"{ invalid: "json" }"#).unwrap();

        let output = Command::new(binary_path)
            .args(["check", "--config", json_path.to_str().unwrap(), "README.md"])
            .output()
            .expect("Failed to execute command");

        // Should exit with code 2 for configuration error
        assert_eq!(
            output.status.code(),
            Some(2),
            "Expected exit code 2 for invalid JSON config"
        );
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let combined_output = format!("{stderr}{stdout}");
        assert!(
            combined_output.contains("Failed to parse JSON") || combined_output.contains("Config error"),
            "CLI should show JSON parsing error: stderr='{stderr}' stdout='{stdout}'"
        );

        // Test YAML syntax error via CLI
        let yaml_path = temp_dir.path().join("invalid.yaml");
        fs::write(&yaml_path, "invalid: - yaml").unwrap();

        let output = Command::new(binary_path)
            .args(["check", "--config", yaml_path.to_str().unwrap(), "README.md"])
            .output()
            .expect("Failed to execute command");

        // Should exit with code 2 for configuration error
        assert_eq!(
            output.status.code(),
            Some(2),
            "Expected exit code 2 for invalid YAML config"
        );
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let combined_output = format!("{stderr}{stdout}");
        assert!(
            combined_output.contains("Failed to parse YAML") || combined_output.contains("Config error"),
            "CLI should show YAML parsing error: stderr='{stderr}' stdout='{stdout}'"
        );

        // Test file not found error via CLI
        let output = Command::new(binary_path)
            .args(["check", "--config", "/nonexistent/config.json", "README.md"])
            .output()
            .expect("Failed to execute command");

        // Should exit with code 2 for file not found
        assert_eq!(
            output.status.code(),
            Some(2),
            "Expected exit code 2 for nonexistent config file"
        );
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let combined_output = format!("{stderr}{stdout}");
        assert!(
            combined_output.contains("Failed to read config file") || combined_output.contains("Config error"),
            "CLI should show file reading error: stderr='{stderr}' stdout='{stdout}'"
        );
    }

    #[test]
    fn test_no_config_flag_bypasses_config_loading() {
        use std::process::Command;

        let temp_dir = tempdir().unwrap();

        // Use the standard Cargo environment variable for the binary path
        let binary_path = env!("CARGO_BIN_EXE_rumdl");

        // Create an invalid config file
        let invalid_json_path = temp_dir.path().join("invalid.json");
        fs::write(&invalid_json_path, r#"{ invalid: "json" }"#).unwrap();

        // Create a simple test markdown file
        let md_path = temp_dir.path().join("test.md");
        fs::write(&md_path, "# Test\n\nSome content.\n").unwrap();

        // Test that --no-config bypasses config loading and succeeds even with invalid config
        let output = Command::new(binary_path)
            .args([
                "check",
                "--config",
                invalid_json_path.to_str().unwrap(),
                "--no-config",
                md_path.to_str().unwrap(),
            ])
            .output()
            .expect("Failed to execute command");

        // Should succeed because --no-config bypasses the invalid config
        assert!(
            output.status.success(),
            "Command with --no-config should succeed even with invalid config file. stderr='{}' stdout='{}'",
            String::from_utf8_lossy(&output.stderr),
            String::from_utf8_lossy(&output.stdout)
        );
    }

    #[test]
    fn test_auto_discovery_vs_explicit_config() {
        let temp_dir = tempdir().unwrap();
        let original_dir = std::env::current_dir().unwrap();

        // Change to temp directory for auto-discovery test
        std::env::set_current_dir(&temp_dir).unwrap();

        // Create a .markdownlint.json file for auto-discovery
        let auto_config_content = r#"{ "MD004": { "style": "asterisk" } }"#;
        fs::write(".markdownlint.json", auto_config_content).unwrap();

        // Test auto-discovery (should find .markdownlint.json)
        let auto_result = SourcedConfig::load_with_discovery(None, None, false);
        assert!(auto_result.is_ok(), "Auto-discovery should find .markdownlint.json");

        let auto_config: rumdl_lib::config::Config = auto_result.unwrap().into();
        let auto_style = rumdl_lib::config::get_rule_config_value::<String>(&auto_config, "MD004", "style");
        assert_eq!(auto_style, Some("asterisk".to_string()));

        // Create explicit config with different value
        let explicit_path = temp_dir.path().join("explicit.json");
        let explicit_config_content = r#"{ "MD004": { "style": "dash" } }"#;
        fs::write(&explicit_path, explicit_config_content).unwrap();

        // Test explicit config (should override auto-discovery)
        let explicit_result = SourcedConfig::load_with_discovery(Some(explicit_path.to_str().unwrap()), None, false);
        assert!(explicit_result.is_ok(), "Explicit config should load successfully");

        let explicit_config: rumdl_lib::config::Config = explicit_result.unwrap().into();
        let explicit_style = rumdl_lib::config::get_rule_config_value::<String>(&explicit_config, "MD004", "style");
        assert_eq!(explicit_style, Some("dash".to_string()));

        // Test skip auto-discovery (should not find .markdownlint.json)
        let skip_result = SourcedConfig::load_with_discovery(None, None, true);
        assert!(skip_result.is_ok(), "Skip auto-discovery should succeed");

        let skip_config: rumdl_lib::config::Config = skip_result.unwrap().into();
        let skip_style = rumdl_lib::config::get_rule_config_value::<String>(&skip_config, "MD004", "style");
        assert_eq!(skip_style, None, "Skip auto-discovery should not load any config");

        // Restore original directory
        std::env::set_current_dir(original_dir).unwrap();
    }
}

#[test]
#[serial(cwd)]
fn test_user_configuration_discovery() {
    use std::env;

    let original_dir = env::current_dir().unwrap();

    // Create temporary directories
    let temp_dir = tempdir().unwrap();
    let project_dir = temp_dir.path().join("project");
    let config_dir = temp_dir.path().join("config");
    let rumdl_config_dir = config_dir.join("rumdl");

    fs::create_dir_all(&project_dir).unwrap();
    fs::create_dir_all(&rumdl_config_dir).unwrap();

    // Create user config file
    let user_config_path = rumdl_config_dir.join("rumdl.toml");
    let user_config_content = r#"
[global]
line-length = 88
disable = ["MD041"]

[MD007]
indent = 4
"#;
    fs::write(&user_config_path, user_config_content).unwrap();

    // Change to project directory (which has no config)
    env::set_current_dir(&project_dir).unwrap();

    // Test that user config is loaded when no project config exists
    // Pass the config_dir directly instead of setting XDG_CONFIG_HOME
    let sourced = rumdl_lib::config::SourcedConfig::load_with_discovery_impl(None, None, false, Some(&config_dir))
        .expect("Should load user config");

    let config: Config = sourced.into();

    // Verify user config was loaded
    assert_eq!(
        config.global.line_length, 88,
        "Should load line-length from user config"
    );
    assert_eq!(
        config.global.disable,
        vec!["MD041"],
        "Should load disabled rules from user config"
    );

    // Verify rule-specific settings
    let indent = rumdl_lib::config::get_rule_config_value::<usize>(&config, "MD007", "indent");
    assert_eq!(indent, Some(4), "Should load MD007 indent from user config");

    // Now create a project config
    let project_config_path = project_dir.join(".rumdl.toml");
    let project_config_content = r#"
[global]
line-length = 100

[MD007]
indent = 2
"#;
    fs::write(&project_config_path, project_config_content).unwrap();

    // Test that project config takes precedence over user config
    let sourced_with_project =
        rumdl_lib::config::SourcedConfig::load_with_discovery_impl(None, None, false, Some(&config_dir))
            .expect("Should load project config");

    let config_with_project: Config = sourced_with_project.into();

    // Verify project config takes precedence
    assert_eq!(
        config_with_project.global.line_length, 100,
        "Project config should override user config"
    );
    let project_indent = rumdl_lib::config::get_rule_config_value::<usize>(&config_with_project, "MD007", "indent");
    assert_eq!(
        project_indent,
        Some(2),
        "Project MD007 config should override user config"
    );

    // Restore original environment
    env::set_current_dir(original_dir).unwrap();
}

#[test]
#[serial(cwd)]
fn test_user_configuration_file_precedence() {
    use std::env;

    let original_dir = env::current_dir().unwrap();

    // Create temporary directories
    let temp_dir = tempdir().unwrap();
    let project_dir = temp_dir.path().join("project");
    let config_dir = temp_dir.path().join("config");
    let rumdl_config_dir = config_dir.join("rumdl");

    fs::create_dir_all(&project_dir).unwrap();
    fs::create_dir_all(&rumdl_config_dir).unwrap();

    // Create multiple user config files to test precedence
    // .rumdl.toml (highest precedence)
    let dot_rumdl_path = rumdl_config_dir.join(".rumdl.toml");
    fs::write(
        &dot_rumdl_path,
        r#"[global]
line-length = 77"#,
    )
    .unwrap();

    // rumdl.toml (middle precedence)
    let rumdl_path = rumdl_config_dir.join("rumdl.toml");
    fs::write(
        &rumdl_path,
        r#"[global]
line-length = 88"#,
    )
    .unwrap();

    // pyproject.toml (lowest precedence)
    let pyproject_path = rumdl_config_dir.join("pyproject.toml");
    fs::write(
        &pyproject_path,
        r#"[tool.rumdl.global]
line-length = 99"#,
    )
    .unwrap();

    // Change to project directory (which has no config)
    env::set_current_dir(&project_dir).unwrap();

    // Test that .rumdl.toml is loaded first - pass config_dir directly
    let sourced = rumdl_lib::config::SourcedConfig::load_with_discovery_impl(None, None, false, Some(&config_dir))
        .expect("Should load user config");

    let config: Config = sourced.into();
    assert_eq!(
        config.global.line_length, 77,
        ".rumdl.toml should have highest precedence"
    );

    // Remove .rumdl.toml and test again
    fs::remove_file(&dot_rumdl_path).unwrap();

    let sourced2 = rumdl_lib::config::SourcedConfig::load_with_discovery_impl(None, None, false, Some(&config_dir))
        .expect("Should load user config");

    let config2: Config = sourced2.into();
    assert_eq!(
        config2.global.line_length, 88,
        "rumdl.toml should be loaded when .rumdl.toml is absent"
    );

    // Remove rumdl.toml and test again
    fs::remove_file(&rumdl_path).unwrap();

    let sourced3 = rumdl_lib::config::SourcedConfig::load_with_discovery_impl(None, None, false, Some(&config_dir))
        .expect("Should load user config");

    let config3: Config = sourced3.into();
    assert_eq!(
        config3.global.line_length, 99,
        "pyproject.toml should be loaded when other configs are absent"
    );

    // Restore original environment
    env::set_current_dir(original_dir).unwrap();
}
