//! Tests for diagnostic functionality.
//!
//! These tests require the `diagnostics` feature to be enabled.

#![cfg(feature = "diagnostics")]

use sipha_core::{
    span::Span,
    traits::{RuleId, TokenKind},
};
use sipha_error::{
    diagnostic::{Diagnostic, Level},
    error::ParseError,
    expected::Expected,
    ErrorContext,
};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum TestToken {
    Ident,
    Plus,
    Minus,
    Number,
}

impl TokenKind for TestToken {
    fn is_trivia(&self) -> bool {
        false
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum TestRule {
    Expr,
}

impl RuleId for TestRule {}

#[test]
fn test_diagnostic_creation() {
    let diagnostic = Diagnostic::new()
        .level(Level::Error)
        .message("Test error message".to_string())
        .spans(vec![Span::new(0, 10)])
        .build();

    assert_eq!(diagnostic.level, Level::Error);
    assert_eq!(diagnostic.message, "Test error message");
    assert_eq!(diagnostic.spans.len(), 1);
}

#[test]
fn test_unexpected_token_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::UnexpectedToken {
        kind: TestToken::Plus,
        span: Span::new(5, 6),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("Unexpected token"));
    assert!(diagnostic.message.contains("Plus"));
    assert_eq!(diagnostic.spans.len(), 1);
    assert_eq!(diagnostic.spans[0].start(), 5);
}

#[test]
fn test_expected_token_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::Expected {
        expected: Expected::Single(TestToken::Ident),
        found: Some(TestToken::Number),
        span: Span::new(10, 15),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("Expected"));
    assert!(diagnostic.message.contains("Ident"));
    assert!(diagnostic.message.contains("Number"));
    assert_eq!(diagnostic.spans.len(), 1);
}

#[test]
fn test_expected_set_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::Expected {
        expected: Expected::Set(&[TestToken::Ident, TestToken::Number]),
        found: Some(TestToken::Plus),
        span: Span::new(0, 1),
        rule_context: None,
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("Expected"));
    // Should mention both expected tokens
    assert!(diagnostic.message.contains("Ident") || diagnostic.message.contains("Number"));
}

#[test]
fn test_unexpected_eof_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::UnexpectedEof {
        span: Span::new(20, 20),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("Unexpected end of input"));
}

#[test]
fn test_diagnostic_with_notes() {
    let error: ParseError<TestToken, TestRule> = ParseError::UnexpectedToken {
        kind: TestToken::Plus,
        span: Span::new(5, 6),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![ErrorContext {
            rule_id: TestRule::Expr,
            span: Span::new(0, 10),
            message: "While parsing expression".to_string(),
        }],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert!(!diagnostic.notes.is_empty());
    assert!(diagnostic.notes[0].contains("While parsing expression"));
}

#[test]
fn test_diagnostic_format_with_source() {
    let error: ParseError<TestToken, TestRule> = ParseError::Expected {
        expected: Expected::Single(TestToken::Ident),
        found: Some(TestToken::Plus),
        span: Span::new(5, 6),
        rule_context: None,
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    let source = "hello + world";
    let formatted = diagnostic.format_with_source(source);

    // Should contain the error message
    assert!(formatted.contains("Expected"));
    // Should contain source code context
    assert!(formatted.contains("hello") || formatted.contains("world"));
}

#[test]
fn test_diagnostic_display() {
    let diagnostic = Diagnostic::new()
        .level(Level::Error)
        .message("Test error".to_string())
        .spans(vec![Span::new(0, 5)])
        .notes(vec!["Note 1".to_string(), "Note 2".to_string()])
        .hints(vec!["Hint 1".to_string()])
        .suggestions(vec!["Suggestion 1".to_string()])
        .build();

    let display_output = format!("{}", diagnostic);

    // Should contain the message
    assert!(display_output.contains("Test error"));
    // Miette should format notes, hints, and suggestions
    assert!(
        display_output.contains("Note")
            || display_output.contains("Hint")
            || display_output.contains("Suggestion")
    );
}

#[test]
fn test_diagnostic_to_miette_report() {
    let diagnostic = Diagnostic::new()
        .level(Level::Error)
        .message("Test error".to_string())
        .spans(vec![Span::new(0, 10)])
        .build();

    let report = diagnostic.to_miette_report(Some("test source code"));
    let report_str = format!("{report:?}");

    // Should contain the error message
    assert!(report_str.contains("Test error"));
}

#[test]
fn test_multiple_alternatives_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::MultipleAlternatives {
        alternatives: vec![
            ParseError::Expected {
                expected: Expected::Single(TestToken::Ident),
                found: Some(TestToken::Plus),
                span: Span::new(0, 1),
                rule_context: None,
                context_stack: vec![],
            },
            ParseError::Expected {
                expected: Expected::Single(TestToken::Number),
                found: Some(TestToken::Plus),
                span: Span::new(0, 1),
                rule_context: None,
                context_stack: vec![],
            },
        ],
        span: Span::new(0, 1),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("alternatives"));
    // Should have notes from the alternatives
    assert!(!diagnostic.notes.is_empty());
}

#[test]
fn test_non_associative_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::NonAssociative {
        kind: TestToken::Minus,
        span: Span::new(5, 6),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("non-associative"));
    assert!(!diagnostic.hints.is_empty());
    assert!(!diagnostic.suggestions.is_empty());
}

#[test]
fn test_range_error() {
    let error: ParseError<TestToken, TestRule> = ParseError::RangeError {
        min: 1,
        max: 3,
        found: 5,
        span: Span::new(0, 10),
        rule_context: Some(TestRule::Expr),
        context_stack: vec![],
    };

    let diagnostic = Diagnostic::from_parse_error(&error);
    assert_eq!(diagnostic.level, Level::Error);
    assert!(diagnostic.message.contains("repetitions"));
    assert!(diagnostic.message.contains("1"));
    assert!(diagnostic.message.contains("3"));
    assert!(diagnostic.message.contains("5"));
}
