//! Tests for diagnostics functionality

#[cfg(test)]
mod tests {
    use tower_lsp::lsp_types::DiagnosticSeverity;

    use crate::lsp::{
        diagnostics,
        document::Document,
    };

    #[test]
    fn test_parse_error_diagnostics() {
        let invalid = "character { missing name }";
        let doc = Document::new(invalid.to_string());

        assert!(!doc.parse_errors.is_empty(), "Should have parse errors");
        assert!(doc.ast.is_none(), "Should not have AST");
    }

    #[test]
    fn test_multiple_parse_errors() {
        let invalid = r#"
character {
}
template {
}
invalid syntax here
"#;
        let doc = Document::new(invalid.to_string());

        // Should report errors (may be combined into one or multiple)
        assert!(!doc.parse_errors.is_empty());
    }

    #[test]
    fn test_no_errors_for_valid_code() {
        let valid = "character Alice { age: 7 }";
        let doc = Document::new(valid.to_string());

        assert!(
            doc.parse_errors.is_empty(),
            "Valid code should have no errors"
        );
        assert!(doc.ast.is_some(), "Should have parsed AST");
    }

    #[test]
    fn test_error_positions() {
        let invalid = "character Alice { age: }"; // Missing value
        let doc = Document::new(invalid.to_string());

        if !doc.parse_errors.is_empty() {
            let error = &doc.parse_errors[0];
            // Error should have valid position
            assert!(error.start < invalid.len());
            assert!(error.end <= invalid.len());
            assert!(error.start <= error.end);
        }
    }

    #[test]
    fn test_error_messages_are_descriptive() {
        let invalid = "character Alice { age: }";
        let doc = Document::new(invalid.to_string());

        if !doc.parse_errors.is_empty() {
            let error = &doc.parse_errors[0];
            assert!(
                !error.message.is_empty(),
                "Error message should not be empty"
            );
            assert!(
                error.message.len() > 10,
                "Error message should be descriptive"
            );
        }
    }

    #[test]
    fn test_error_severity() {
        use crate::lsp::document::ErrorSeverity;

        let invalid = "character { }";
        let doc = Document::new(invalid.to_string());

        if !doc.parse_errors.is_empty() {
            let error = &doc.parse_errors[0];
            // Parse errors should be Error severity
            assert!(matches!(error.severity, ErrorSeverity::Error));
        }
    }

    #[test]
    fn test_unclosed_brace_error() {
        let invalid = "character Alice { age: 7";
        let doc = Document::new(invalid.to_string());

        assert!(!doc.parse_errors.is_empty(), "Should detect unclosed brace");
    }

    #[test]
    fn test_unexpected_token_error() {
        let invalid = "character Alice } age: 7 {"; // Backwards braces
        let doc = Document::new(invalid.to_string());

        assert!(
            !doc.parse_errors.is_empty(),
            "Should detect unexpected tokens"
        );
    }

    #[test]
    fn test_invalid_field_syntax() {
        let invalid = "character Alice { age = 7 }"; // Wrong operator
        let doc = Document::new(invalid.to_string());

        // May or may not parse depending on parser flexibility
        // Just verify we handle it gracefully
        assert!(doc.ast.is_some() || !doc.parse_errors.is_empty());
    }

    #[test]
    fn test_empty_input() {
        let doc = Document::new("".to_string());

        // Empty input is valid - just no declarations
        assert!(doc.parse_errors.is_empty());
    }

    #[test]
    fn test_whitespace_only() {
        let doc = Document::new("   \n\n\t\t  ".to_string());

        // Whitespace only is valid
        assert!(doc.parse_errors.is_empty());
    }

    #[test]
    fn test_comments_only() {
        let doc = Document::new("// Just comments\n// Nothing else".to_string());

        // Comments only is valid
        assert!(doc.parse_errors.is_empty());
    }

    // Tests for diagnostics utility functions
    #[test]
    fn test_byte_offset_to_position_start() {
        let text = "hello world";
        let pos = diagnostics::byte_offset_to_position(text, 0);
        assert_eq!(pos.line, 0);
        assert_eq!(pos.character, 0);
    }

    #[test]
    fn test_byte_offset_to_position_middle() {
        let text = "hello world";
        let pos = diagnostics::byte_offset_to_position(text, 6);
        assert_eq!(pos.line, 0);
        assert_eq!(pos.character, 6);
    }

    #[test]
    fn test_byte_offset_to_position_multiline() {
        let text = "line 1\nline 2\nline 3";
        let pos = diagnostics::byte_offset_to_position(text, 7); // Start of line 2
        assert_eq!(pos.line, 1);
        assert_eq!(pos.character, 0);
    }

    #[test]
    fn test_byte_offset_to_position_beyond_text() {
        let text = "short";
        // Should not panic when offset is beyond text length
        let _ = diagnostics::byte_offset_to_position(text, 1000);
    }

    #[test]
    fn test_create_diagnostic() {
        let text = "character Alice {}";
        let diag = diagnostics::create_diagnostic(
            text,
            0,
            9,
            "Test message".to_string(),
            DiagnosticSeverity::ERROR,
        );

        assert_eq!(diag.message, "Test message");
        assert_eq!(diag.severity, Some(DiagnosticSeverity::ERROR));
        assert_eq!(diag.source, Some("storybook".to_string()));
    }

    #[test]
    fn test_create_diagnostic_with_warning() {
        let text = "test";
        let diag = diagnostics::create_diagnostic(
            text,
            0,
            4,
            "Warning message".to_string(),
            DiagnosticSeverity::WARNING,
        );

        assert_eq!(diag.severity, Some(DiagnosticSeverity::WARNING));
    }

    #[test]
    fn test_compute_diagnostics_valid() {
        let text = "character Alice { age: 7 }";
        let diags = diagnostics::compute_diagnostics(text);
        // Valid code should produce no diagnostics from the placeholder implementation
        // (The real diagnostics come from document.rs)
        // Just verify the function runs without panicking - result may vary
        let _ = diags;
    }

    #[test]
    fn test_compute_diagnostics_unclosed_brace() {
        let text = "} extra closing brace";
        let diags = diagnostics::compute_diagnostics(text);
        // Should detect the unexpected closing brace
        assert!(!diags.is_empty());
        assert!(diags[0].message.contains("closing brace"));
    }

    #[test]
    fn test_braces_in_strings_are_ignored() {
        // This was the bug: braces inside string literals were being counted
        let text = r#"character Alice {
    description: "A person with { and } in their bio"
    age: 7
}"#;
        let diags = diagnostics::compute_diagnostics(text);
        // Should NOT report any errors - braces in strings should be ignored
        assert!(
            diags.is_empty(),
            "Braces in strings should not trigger errors"
        );
    }

    #[test]
    fn test_actual_unmatched_brace_detected() {
        // But actual unmatched braces should still be detected
        let text = r#"character Alice {
    age: 7
}
}"#; // Extra closing brace
        let diags = diagnostics::compute_diagnostics(text);
        assert!(
            !diags.is_empty(),
            "Actual unmatched braces should be detected"
        );
        assert!(diags[0].message.contains("closing brace"));
    }

    #[test]
    fn test_braces_in_comments_are_ignored() {
        // Comments should also be ignored
        let text = r#"character Alice {
    age: 7 // This has { and } in comment
}"#;
        let diags = diagnostics::compute_diagnostics(text);
        assert!(
            diags.is_empty(),
            "Braces in comments should not trigger errors"
        );
    }
}
