use rumdl_lib::config::{Config, RuleConfig, normalize_key};
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::*;
use std::collections::BTreeMap;
use std::fs;

fn create_test_config() -> Config {
    let mut config = Config::default();

    // Add MD013 config
    let mut md013_values = BTreeMap::new();
    md013_values.insert(normalize_key("line_length"), toml::Value::Integer(120));
    md013_values.insert(normalize_key("code_blocks"), toml::Value::Boolean(false));
    md013_values.insert(normalize_key("headings"), toml::Value::Boolean(true));
    let md013_config = RuleConfig {
        severity: None,
        values: md013_values,
    };
    config.rules.insert(normalize_key("MD013"), md013_config);

    // Add MD004 config
    let mut md004_values = BTreeMap::new();
    md004_values.insert(normalize_key("style"), toml::Value::String("asterisk".to_string()));
    let md004_config = RuleConfig {
        severity: None,
        values: md004_values,
    };
    config.rules.insert(normalize_key("MD004"), md004_config);

    config
}

// Helper function to apply configuration to specific rules
// Now returns a new vector instead of modifying in place
fn apply_rule_configs(rules_in: &Vec<Box<dyn Rule>>, config: &Config) -> Vec<Box<dyn Rule>> {
    let mut rules_out: Vec<Box<dyn Rule>> = Vec::with_capacity(rules_in.len());

    for rule_instance in rules_in {
        let rule_name = rule_instance.name();

        // Apply MD013 configuration
        if rule_name == "MD013" {
            let line_length = rumdl_lib::config::get_rule_config_value::<u64>(config, "MD013", "line_length")
                .map(|v| v as usize)
                .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);

            // Push the NEW configured instance
            rules_out.push(Box::new(MD013LineLength::new(
                line_length,
                code_blocks,
                tables,
                headings,
                strict,
            )));
            continue; // Go to the next rule in the input vector
        }

        // Apply MD004 configuration
        if rule_name == "MD004" {
            let style = rumdl_lib::config::get_rule_config_value::<String>(config, "MD004", "style")
                .unwrap_or_else(|| "consistent".to_string());
            let ul_style = match style.as_str() {
                "asterisk" => rumdl_lib::rules::md004_unordered_list_style::UnorderedListStyle::Asterisk,
                "plus" => rumdl_lib::rules::md004_unordered_list_style::UnorderedListStyle::Plus,
                "dash" => rumdl_lib::rules::md004_unordered_list_style::UnorderedListStyle::Dash,
                _ => rumdl_lib::rules::md004_unordered_list_style::UnorderedListStyle::Consistent,
            };
            // Push the NEW configured instance
            rules_out.push(Box::new(MD004UnorderedListStyle::new(ul_style)));
            continue; // Go to the next rule in the input vector
        }

        // If rule doesn't need configuration, push a clone of the original instance
        rules_out.push(rule_instance.clone());
    }
    rules_out // Return the new vector
}

#[test]
fn test_apply_rule_configs() {
    // Create test rules using all_rules()
    let initial_rules = rumdl_lib::rules::all_rules(&rumdl_lib::config::Config::default());

    // Create a test config
    let config = create_test_config();

    // Apply configs to rules using LOCAL helper, getting a NEW vector
    let configured_rules = apply_rule_configs(&initial_rules, &config);

    // Test content that would trigger different behaviors based on config
    let test_content = r#"# Heading

This is a line that exceeds the default 80 characters but is less than the configured 120 characters.

* Item 1
- Item 2
+ Item 3
"#;

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

    // Check MD013 behavior - should not trigger on >80 but <120 chars
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(md013_warnings, 0, "MD013 should not trigger with line_length 120");

    // Check MD004 behavior - should warn on dash and plus (not asterisk)
    let md004_warnings: Vec<_> = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD004"))
        .collect();
    assert_eq!(
        md004_warnings.len(),
        2,
        "MD004 should trigger for all unordered list items with non-asterisk markers in explicit style mode, matching markdownlint"
    );

    // Make sure the non-configured rule (MD001) still works normally
    let md001_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD001"))
        .count();
    assert_eq!(
        md001_warnings,
        0, // MD001 doesn't trigger on this content anyway
        "MD001 should not trigger on this content"
    );
}

#[test]
fn test_config_priority() {
    // Test that rule-specific configs override defaults

    // Create test rules with defaults using all_rules()
    let initial_rules = rumdl_lib::rules::all_rules(&rumdl_lib::config::Config::default());

    // Create config with different line_length
    let mut config = create_test_config(); // line_length: 120

    // Apply configs using LOCAL helper, getting a NEW vector
    let configured_rules_1 = apply_rule_configs(&initial_rules, &config);

    // Test with a line that's 100 chars (exceeds default but within config).
    // Uses words with spaces so trailing-word replacement doesn't forgive the whole line.
    let line_100_chars = "# Test

"
    .to_owned()
        + &"ab ".repeat(32)
        + "abcd"; // 32 * 3 + 4 = 100 chars on line 3, prefix = 97

    // Run linting with the NEW configured rules vector
    let warnings = rumdl_lib::lint(
        &line_100_chars,
        &configured_rules_1,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
        None,
        None,
    )
    .expect("Linting should succeed");

    // Should not trigger MD013 because config value is 120
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(
        md013_warnings, 0,
        "MD013 should not trigger with configured line_length 120"
    );

    // Now change config to 50 chars
    let mut md013_values = BTreeMap::new();
    md013_values.insert(normalize_key("line_length"), toml::Value::Integer(50));
    let md013_config = RuleConfig {
        severity: None,
        values: md013_values,
    };
    // Need to use normalized key for insertion
    config.rules.insert(normalize_key("MD013"), md013_config);

    // Re-apply configs using LOCAL helper, getting ANOTHER NEW vector
    let configured_rules_2 = apply_rule_configs(&initial_rules, &config);

    // Should now trigger MD013
    let warnings = rumdl_lib::lint(
        &line_100_chars,
        &configured_rules_2,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
        None,
        None,
    )
    .expect("Linting should succeed");
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(md013_warnings, 1, "MD013 should trigger with configured line_length 50");
}

#[test]
fn test_partial_rule_config() {
    // Test that partial configurations only override specified fields

    // Create rules using all_rules()
    let initial_rules = rumdl_lib::rules::all_rules(&rumdl_lib::config::Config::default());

    // Create config with only line_length specified
    let mut rules_map = BTreeMap::new();
    let mut md013_values = BTreeMap::new();
    md013_values.insert(normalize_key("line_length"), toml::Value::Integer(100));
    // Note: code_blocks not specified, should keep default value
    let md013_config = RuleConfig {
        severity: None,
        values: md013_values,
    };
    // Use normalized key
    rules_map.insert(normalize_key("MD013"), md013_config);

    let mut config = Config::default();
    config.rules = rules_map;

    // Apply configs using LOCAL helper, getting a NEW vector
    let configured_rules_1 = apply_rule_configs(&initial_rules, &config);

    // Test with a regular line that exceeds 80 chars but not 100 chars
    let test_content =
        "This is a regular line that is longer than 80 characters but shorter than 100 characters in length.";

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

    // Should NOT trigger MD013 because line_length is set to 100
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(md013_warnings, 0, "MD013 should not trigger with line_length 100");

    // Now update config to set line_length to 60
    let mut rules_map = BTreeMap::new();
    let mut md013_values = BTreeMap::new();
    md013_values.insert(normalize_key("line_length"), toml::Value::Integer(60));
    let md013_config = RuleConfig {
        severity: None,
        values: md013_values,
    };
    // Use normalized key
    rules_map.insert(normalize_key("MD013"), md013_config);

    let mut config = Config::default();
    config.rules = rules_map;

    // Apply configs using LOCAL helper with modified config, getting ANOTHER NEW vector
    let configured_rules_2 = apply_rule_configs(&initial_rules, &config);

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

    // Now should trigger MD013 because line_length is less than the line length
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(md013_warnings, 1, "MD013 should trigger with line_length 60");
}

#[test]
fn test_config_enable_disable() {
    // Test that config application works even when enable/disable are present in config
    // NOTE: This test no longer tests the filtering itself, but that the config *application*
    // still works correctly before filtering would hypothetically happen.

    let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
    let config_path = temp_dir.path().join("enable_disable_config.toml");

    // Config 1: Disable MD001 globally, Configure MD013
    let config_content_1 = r#"
[global]
disable = ["md001"] # Use normalized key

[MD013]
line_length = 20
"#;
    fs::write(&config_path, config_content_1).expect("Failed to write config 1");

    let config_path_str = config_path.to_str().expect("Path is valid UTF-8");
    // Load using SourcedConfig::load_with_discovery with skip_auto_discovery: true
    let sourced_config_1 = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load config 1");
    let config_1: Config = sourced_config_1.into_validated_unchecked().into(); // Convert

    // Test content with MD001 violation and MD013 violation
    let test_content = r#"
# Heading 1
### Heading 3
This line exceeds 20 characters.
"#;

    // Get all rules and apply the config using the LOCAL helper
    let initial_rules_1 = rumdl_lib::rules::all_rules(&rumdl_lib::config::Config::default());
    let configured_rules_1 = apply_rule_configs(&initial_rules_1, &config_1);

    // Run linting (MD001 should still run here as we haven't filtered)
    let warnings_1 = rumdl_lib::lint(
        test_content,
        &configured_rules_1,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
        None,
        None,
    )
    .expect("Linting should succeed");

    // Verify MD001 WAS triggered (as filtering is not tested here)
    let md001_warnings_1 = warnings_1
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD001"))
        .count();
    assert_eq!(
        md001_warnings_1, 1,
        "MD001 should run and trigger (filtering not tested)"
    );

    // Verify MD013 WAS triggered with the configured length
    let md013_warnings_1 = warnings_1
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(md013_warnings_1, 1, "MD013 should trigger once with line_length 20");

    // Config 2: Enable only MD013, Configure MD013
    let config_content_2 = r#"
[global]
enable = ["md013"] # Use normalized key

[MD013]
line_length = 20 # Set a low limit to trigger it
"#;
    fs::write(&config_path, config_content_2).expect("Failed to write config 2");

    // Load using SourcedConfig::load_with_discovery with skip_auto_discovery: true
    let sourced_config_2 = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load config 2");
    let config_2: Config = sourced_config_2.into_validated_unchecked().into(); // Convert

    // Get all rules and apply config
    let initial_rules_2 = rumdl_lib::rules::all_rules(&rumdl_lib::config::Config::default());
    let configured_rules_2 = apply_rule_configs(&initial_rules_2, &config_2);

    // Run linting
    let warnings_2 = rumdl_lib::lint(
        test_content,
        &configured_rules_2,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
        None,
        None,
    )
    .expect("Linting should succeed");

    // Verify MD013 triggers with configured length
    let md013_warnings_2 = warnings_2
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .count();
    assert_eq!(
        md013_warnings_2, 1,
        "MD013 should trigger once with line_length 20 (enable doesn't affect application)"
    );

    // Verify MD001 also triggers (filtering not tested here)
    let md001_warnings_2 = warnings_2
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD001"))
        .count();
    assert_eq!(
        md001_warnings_2, 1,
        "MD001 should run and trigger (filtering not tested)"
    );

    // Comment out the third test case as it relied on CLI args and filtering logic
    // // Test Case 3: CLI disable overrides config enable/disable
    // let check_args_cli_disable = CheckArgs {
    //     disable: Some("MD003".to_string()), // Disable MD003 via CLI
    //     ..Default::default()
    // };
    // ... rest of test case 3 removed ...
}

#[test]
fn test_disable_all_override() {
    // Test that the filtering logic (which is NOT tested here anymore)
    // would normally handle disable=["all"], but config application still works.

    let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
    let config_path = temp_dir.path().join("disable_all_config.toml");

    // Config that disables all rules and configures MD013
    let config_content = r#"
[global]
disable = ["all"]

[MD013]
line_length = 10
headings = true
"#;
    fs::write(&config_path, config_content).expect("Failed to write config");

    let config_path_str = config_path.to_str().expect("Path is valid UTF-8");
    // Load using SourcedConfig::load_with_discovery with skip_auto_discovery: true
    let sourced_config = rumdl_lib::config::SourcedConfig::load_with_discovery(Some(config_path_str), None, true)
        .expect("Failed to load config");
    let config: Config = sourced_config.into_validated_unchecked().into(); // Convert

    // Get all rules and apply config
    let initial_rules = rumdl_lib::rules::all_rules(&rumdl_lib::config::Config::default());
    let configured_rules = apply_rule_configs(&initial_rules, &config);

    // Test with content that would normally trigger multiple rules
    let test_content = r#"
# Heading 1
### Heading 3

This line > 10.
"#;

    // Run linting with the configured (but not filtered) ruleset
    let warnings = rumdl_lib::lint(
        test_content,
        &configured_rules,
        false,
        rumdl_lib::config::MarkdownFlavor::Standard,
        None,
        None,
    )
    .expect("Linting should succeed");

    // Verify MD013 triggered with its configured value (10)
    let md013_warnings = warnings
        .iter()
        .filter(|w| w.rule_name.as_deref() == Some("MD013"))
        .collect::<Vec<_>>();

    assert_eq!(
        md013_warnings.len(),
        3, // <<< Change expected count back to 3 based on corrected analysis
        "MD013 should trigger 3 times with line_length 10 (disable=all doesn't affect application)"
    );
}
