use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD022BlanksAroundHeadings;
use rumdl_lib::utils::range_utils::LineIndex;

#[test]
fn test_valid_headings() {
    let rule = MD022BlanksAroundHeadings::default();
    let content = "Paragraph.\n\n# Heading 1\n\nContent.\n\n## Heading 2\n\nMore content.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_missing_blank_above() {
    let rule = MD022BlanksAroundHeadings::default();
    let content = "Paragraph.\n# Heading 1\nContent.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 2); // Missing blank above and below
}

#[test]
fn test_missing_blank_below() {
    let rule = MD022BlanksAroundHeadings::default();
    let content = "# Heading 1\nContent.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = rule.check(&ctx).unwrap();
    assert_eq!(result.len(), 1);
}

#[test]
fn test_fix_headings() {
    let rule = MD022BlanksAroundHeadings::default();
    let content = "Paragraph.\n# Heading 1\nContent.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);
    assert!(fixed.contains("\n\n# Heading 1\n\n"));
}

#[test]
fn test_invalid_headings() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "# Heading 1\nSome content here.\n## Heading 2\nMore content here.\n### Heading 3\nFinal content.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();
    // We only check for non-empty result, not specific count
    // This ensures a principled implementation that correcty identifies issues
    // without requiring specific warning counts
    assert!(!result.is_empty());
}

#[test]
fn test_first_heading() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "# First Heading\n\nSome content.\n\n## Second Heading\n\nMore content.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_code_block() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Content with a heading followed by a code block
    let content = "# Heading\n\n```\n# Not a heading\n```";

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Debug print
    println!("Original content:\n{content}");
    println!("Fixed content:\n{fixed}");

    // Check if we get warnings on the fixed content
    let warnings = _rule.check(&_fixed_ctx).unwrap();
    println!("Warning count: {}", warnings.len());
    for (i, warning) in warnings.iter().enumerate() {
        println!("Warning {}: line {}, message: {}", i + 1, warning.line, warning.message);
    }

    // Check that the fix preserves the code block with the exact format
    assert!(fixed.contains("```"));
    assert!(fixed.contains("# Not a heading"));

    // The fixed content should pass validation
    assert!(warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_front_matter() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "---\ntitle: Test\n---\n\n# First Heading\n\nContent here.\n\n## Second Heading\n\nMore content.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_fix_mixed_headings() {
    let _rule = MD022BlanksAroundHeadings::default();
    // Create a case that clearly violates the blank line rules around headings
    // Here, all the headings need blank lines either above or below
    let content = "Text before.\n# Heading 1\nSome content here.\nText here\n## Heading 2\nMore content here.\nText here\n### Heading 3\nFinal content.";

    // Run check to confirm there are warnings
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let warnings = _rule.check(&ctx).unwrap();
    assert!(!warnings.is_empty());

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);
    assert_ne!(fixed, content);

    // Instead of checking specific formatting, verify the fixed content follows the rule requirements
    // The fixed content should have more lines than the original due to added blank lines
    let fixed_lines: Vec<&str> = fixed.lines().collect();
    let original_lines: Vec<&str> = content.lines().collect();
    assert!(fixed_lines.len() > original_lines.len());

    // Verify all content is preserved
    assert!(fixed.contains("Some content here"));
    assert!(fixed.contains("More content here"));
    assert!(fixed.contains("Final content"));

    // Verify all headings are still present
    assert!(fixed.contains("# Heading 1"));
    assert!(fixed.contains("## Heading 2"));
    assert!(fixed.contains("### Heading 3"));

    // Run check on the fixed content - it should have no warnings
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_custom_blank_lines() {
    let _rule = MD022BlanksAroundHeadings::with_values(2, 2);
    let content = "# Heading 1\nSome content here.\n## Heading 2\nMore content here.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Check there are warnings
    assert!(!result.is_empty());

    // Fix content according to rule
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // The fixed content should now be valid
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_blanks_around_setext_headings() {
    let _rule = MD022BlanksAroundHeadings::default();

    // First test that the rule generates warnings for malformatted setext headings
    let bad_content = "Some text\nHeading 1\n=========\nContent\nHeading 2\n---------\nMore content.";
    let ctx = LintContext::new(bad_content, rumdl_lib::config::MarkdownFlavor::Standard);
    let _bad_result = _rule.check(&ctx).unwrap();

    // Then test that the fix produces valid content
    let ctx = LintContext::new(bad_content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed_result = _rule.check(&_fixed_ctx).unwrap();

    // After fixing, there should be no warnings
    assert!(fixed_result.is_empty(), "Fixed setext headings should have no warnings");
}

#[test]
fn test_empty_content_headings() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "#\nSome content.\n##\nMore content.\n###\nFinal content.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we get warnings (without checking exact count)
    assert!(!result.is_empty());

    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();

    // Test that fix produces a different result
    assert!(fixed != content);

    // Test the headings and content are preserved
    assert!(fixed.contains("#"));
    assert!(fixed.contains("##"));
    assert!(fixed.contains("###"));
    assert!(fixed.contains("Some content"));
    assert!(fixed.contains("More content"));
    assert!(fixed.contains("Final content"));

    // Verify the basic structure is maintained (same number of content sections)
    assert_eq!(fixed.matches("content").count(), content.matches("content").count());
}

#[test]
fn test_no_blanks_between_headings() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "# Heading 1\n## Heading 2\n### Heading 3\nContent here.";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we get warnings (without checking exact count)
    assert!(!result.is_empty());

    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Test that blank lines have been added
    assert!(fixed != content);

    // Verify the headings and content are preserved
    assert!(fixed.contains("# Heading 1"));
    assert!(fixed.contains("## Heading 2"));
    assert!(fixed.contains("### Heading 3"));
    assert!(fixed.contains("Content here"));

    // The fixed content should have more lines than the original
    let fixed_lines: Vec<&str> = fixed.lines().collect();
    let original_lines: Vec<&str> = content.lines().collect();
    assert!(fixed_lines.len() > original_lines.len());
}

#[test]
fn test_indented_headings() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Test content with indented headings and missing blank lines
    let content = "  # Heading 1\nContent 1.\n    ## Heading 2\nContent 2.\n      ### Heading 3\nContent 3.";

    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we get warnings about blank lines
    assert!(
        !result.is_empty(),
        "Should detect blank line issues with indented headings"
    );

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Test that blank lines have been added
    assert_ne!(fixed, content, "Fixed content should be different from original");

    // Check that the content structure is preserved
    assert!(fixed.contains("  # Heading 1"));
    assert!(fixed.contains("    ## Heading 2"));
    assert!(fixed.contains("      ### Heading 3"));
    assert!(fixed.contains("Content 1"));
    assert!(fixed.contains("Content 2"));
    assert!(fixed.contains("Content 3"));

    // The fixed content should have more lines than the original
    let fixed_lines: Vec<&str> = fixed.lines().collect();
    let original_lines: Vec<&str> = content.lines().collect();
    assert!(
        fixed_lines.len() > original_lines.len(),
        "Fixed content should have more lines due to added blank lines"
    );

    // Check that the fixed content passes validation
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_code_block_detection() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "# Real Heading\n\nSome content.\n\n```markdown\n# Not a heading\n## Also not a heading\n```\n\n# Another Heading\n\nMore content.";
    let index = LineIndex::new(content);

    // Test if lines are inside a code block (including both markers and content)
    assert!(!index.is_code_block(0)); // # Real Heading
    assert!(!index.is_code_block(2)); // Some content
    assert!(index.is_code_block(4)); // ```markdown - This is a code fence marker
    assert!(index.is_code_block(5)); // # Not a heading - This is inside a code block
    assert!(index.is_code_block(6)); // ## Also not a heading - This is inside a code block
    assert!(index.is_code_block(7)); // ``` - This is a code fence marker
    assert!(!index.is_code_block(9)); // # Another Heading
}

#[test]
fn test_line_index() {
    let content = "# Heading 1\n\nSome text\n\n## Heading 2\n";
    let index = LineIndex::new(content);

    // Test line_col_to_byte_range
    assert_eq!(index.line_col_to_byte_range(1, 1), 0..0);
    assert_eq!(index.line_col_to_byte_range(1, 2), 1..1);
    assert_eq!(index.line_col_to_byte_range(3, 1), 13..13);
}

#[test]
fn test_preserve_code_blocks() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Simple content with a code block containing headings
    let content = "# Real Heading\nSome text\n\n```\n# Fake heading in code block\n```\n\nMore text";

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Check that the fix preserves the code block
    assert!(fixed.contains("```"));
    assert!(fixed.contains("# Fake heading in code block"));

    // Check that the original heading is also preserved
    assert!(fixed.contains("# Real Heading"));

    // The fixed content should pass validation
    let fixed_result = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_result.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_fix_missing_blank_line_below() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "# Heading\nText";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we have warnings
    assert!(!result.is_empty());

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Verify the correct structure
    assert_eq!(fixed, "# Heading\n\nText");

    // Verify the fixed content passes
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_fix_specific_blank_line_cases() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Try a simple case with missing blank line below heading
    let simple_case = "# Heading\nContent";

    // Fix the content
    let ctx = LintContext::new(simple_case, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Verify that the fixed content has a blank line below the heading
    assert!(
        fixed.contains("# Heading\n\nContent"),
        "Should add blank line after heading"
    );

    // The fixed content should pass validation
    let fixed_result = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_result.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_fix_with_various_content_types() {
    let _rule = MD022BlanksAroundHeadings::default();
    let content = "# Heading 1\nParagraph 1\n```\nCode block\n```\n- List item 1\n- List item 2\n## Heading 2\n> Blockquote\n### Heading 3\nFinal paragraph";

    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Verify structure improvements without specifying exact spacing
    assert!(fixed.contains("# Heading 1"));
    assert!(fixed.contains("## Heading 2"));
    assert!(fixed.contains("### Heading 3"));
    assert!(fixed.contains("Paragraph 1"));
    assert!(fixed.contains("```\nCode block\n```"));
    assert!(fixed.contains("- List item 1"));
    assert!(fixed.contains("> Blockquote"));
    assert!(fixed.contains("Final paragraph"));

    // Verify the fixed content passes checks
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_regression_fix_works() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Specific regression test scenario
    let content = "# Heading 1\nSome text\n\n## Heading 2\nMore text";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we get the expected warnings
    assert!(!result.is_empty());

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Verify the structure is correct
    let expected = "# Heading 1\n\nSome text\n\n## Heading 2\n\nMore text";
    assert_eq!(fixed, expected);

    // Verify the fixed content passes checks
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_multiple_consecutive_headings() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Case with multiple consecutive headings
    let content = "# Heading 1\n## Heading 2\n### Heading 3";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we get warnings
    assert!(!result.is_empty());

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Verify the fixed content contains all headings with blank lines between them
    assert!(fixed.contains("# Heading 1"));
    assert!(fixed.contains("## Heading 2"));
    assert!(fixed.contains("### Heading 3"));

    // Parse the fixed content into lines
    let lines: Vec<&str> = fixed.lines().collect();

    // Find the heading positions
    let h1_pos = lines.iter().position(|&l| l == "# Heading 1").unwrap();
    let h2_pos = lines.iter().position(|&l| l == "## Heading 2").unwrap();
    let h3_pos = lines.iter().position(|&l| l == "### Heading 3").unwrap();

    // Verify blank lines between headings
    assert!(h2_pos > h1_pos + 1, "Should have blank line(s) between h1 and h2");
    assert!(h3_pos > h2_pos + 1, "Should have blank line(s) between h2 and h3");

    // Check for at least one blank line after each heading
    assert!(
        lines[h1_pos + 1].trim().is_empty(),
        "Should have at least one blank line after h1"
    );
    assert!(
        lines[h2_pos + 1].trim().is_empty(),
        "Should have at least one blank line after h2"
    );

    // Verify the fixed content passes validation
    let fixed_warnings = _rule.check(&_fixed_ctx).unwrap();
    assert!(fixed_warnings.is_empty(), "Fixed content should have no warnings");
}

#[test]
fn test_consecutive_headings_pattern() {
    let _rule = MD022BlanksAroundHeadings::default();

    // Create a case with consecutive headings
    let content = "# Heading 1\n## Heading 2\n### Heading 3";
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let result = _rule.check(&ctx).unwrap();

    // Verify we get warnings
    assert!(!result.is_empty());

    // Fix the content
    let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard);
    let fixed = _rule.fix(&ctx).unwrap();
    let _fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard);

    // Check for proper structure using less specific checks
    let fixed_lines: Vec<&str> = fixed.lines().collect();

    // Find heading positions
    let h1_pos = fixed_lines.iter().position(|&l| l == "# Heading 1").unwrap();
    let h2_pos = fixed_lines.iter().position(|&l| l == "## Heading 2").unwrap();
    let h3_pos = fixed_lines.iter().position(|&l| l == "### Heading 3").unwrap();

    // Verify there are blank lines between headings
    assert!(
        h2_pos > h1_pos + 1,
        "Should have at least one blank line after first heading"
    );
    assert!(
        h3_pos > h2_pos + 1,
        "Should have at least one blank line after second heading"
    );

    // Verify blank lines
    assert!(
        fixed_lines[h1_pos + 1].is_empty(),
        "Should have blank line after first heading"
    );
    assert!(
        fixed_lines[h2_pos + 1].is_empty(),
        "Should have blank line after second heading"
    );
}
