//! Appendix B — Error conditions.

use crate::{parse, ParseError, parse_iter};

// B.1 BOM_ERROR

#[test]
fn bom_error_detected() {
    let input = b"\xEF\xBB\xBFKEY=value\n";
    let errors = parse(input).unwrap_err();
    assert!(errors.contains(&ParseError::BomError));
}

#[test]
fn bom_error_no_line_number() {
    // BomError has no line field — just verify the variant.
    let input = b"\xEF\xBB\xBFKEY=value\n";
    let errors = parse(input).unwrap_err();
    assert_eq!(errors[0], ParseError::BomError);
}

// B.2 INVALID_UTF8_ERROR

#[test]
fn invalid_utf8_error() {
    // 0xFF is not valid UTF-8.
    let input = b"KEY=\xFF\n";
    let errors = parse(input).unwrap_err();
    assert_eq!(errors, vec![ParseError::InvalidUtf8Error { line: 1 }]);
}

#[test]
fn invalid_utf8_has_line_number() {
    let input = b"OK=fine\nBAD=\xFF\n";
    let errors = parse(input).unwrap_err();
    assert_eq!(errors, vec![ParseError::InvalidUtf8Error { line: 2 }]);
}

// B.3 INVALID_CHARACTER_ERROR

#[test]
fn standalone_cr_error() {
    // Bare CR (not CRLF).
    let input = b"KEY=val\r ue\n";
    let errors = parse(input).unwrap_err();
    assert_eq!(errors, vec![ParseError::InvalidCharacterError { line: 1 }]);
}

#[test]
fn nul_character_error() {
    let input = b"KEY=val\x00ue\n";
    let errors = parse(input).unwrap_err();
    assert_eq!(errors, vec![ParseError::InvalidCharacterError { line: 1 }]);
}

// B.4 EMPTY_KEY_ERROR

#[test]
fn empty_key_error() {
    let errors = parse(b"=value\n").unwrap_err();
    assert_eq!(errors, vec![ParseError::EmptyKeyError { line: 1 }]);
}

#[test]
fn empty_key_with_leading_whitespace() {
    let errors = parse(b"   =value\n").unwrap_err();
    assert_eq!(errors, vec![ParseError::EmptyKeyError { line: 1 }]);
}

// B.5 MISSING_OPERATOR_ERROR

#[test]
fn missing_operator_error() {
    let errors = parse(b"KEYONLY\n").unwrap_err();
    assert_eq!(errors, vec![ParseError::MissingOperatorError { line: 1 }]);
}

// B.6 INVALID_KEY_ERROR

#[test]
fn invalid_key_dash() {
    let errors = parse(b"key-name=v\n").unwrap_err();
    assert_eq!(errors, vec![ParseError::InvalidKeyError { line: 1 }]);
}

#[test]
fn invalid_key_starts_digit() {
    let errors = parse(b"1key=v\n").unwrap_err();
    assert_eq!(errors, vec![ParseError::InvalidKeyError { line: 1 }]);
}

// B.7 MISSING_FINAL_EOL_ERROR

#[test]
fn missing_final_eol_error() {
    // No trailing newline.
    let errors = parse(b"KEY=value").unwrap_err();
    assert_eq!(errors, vec![ParseError::MissingFinalEolError]);
}

#[test]
fn missing_final_eol_with_multiple_lines() {
    let errors = parse(b"A=1\nB=2").unwrap_err();
    assert_eq!(errors, vec![ParseError::MissingFinalEolError]);
}

// Priority ordering

#[test]
fn priority_utf8_before_invalid_char() {
    // An invalid UTF-8 sequence should be reported as INVALID_UTF8_ERROR (B.2),
    // not INVALID_CHARACTER_ERROR (B.3).
    let input = b"\xFF\r\n";
    let errors = parse(input).unwrap_err();
    assert_eq!(errors[0], ParseError::InvalidUtf8Error { line: 1 });
}

// Display / Error impl

#[test]
fn display_bom_error() {
    let s = ParseError::BomError.to_string();
    assert!(s.contains("BOM_ERROR"));
}

#[test]
fn display_missing_final_eol() {
    let s = ParseError::MissingFinalEolError.to_string();
    assert!(s.contains("MISSING_FINAL_EOL_ERROR"));
}

#[test]
fn display_invalid_key_error() {
    let s = ParseError::InvalidKeyError { line: 5 }.to_string();
    assert!(s.contains("INVALID_KEY_ERROR") && s.contains('5'));
}

// Streaming API also yields errors.

#[test]
fn streaming_yields_errors() {
    let input = b"OK=1\n=bad\nOK=2\n";
    let results: Vec<_> = parse_iter(input).collect();
    assert!(results.iter().any(|r| r == &Err(ParseError::EmptyKeyError { line: 2 })));
}

// Multiple errors across lines.

#[test]
fn multiple_errors_across_lines() {
    let input = b"=empty\nmissing\n";
    let errors = parse(input).unwrap_err();
    assert!(errors.contains(&ParseError::EmptyKeyError { line: 1 }));
    assert!(errors.contains(&ParseError::MissingOperatorError { line: 2 }));
}
