//! Property-based tests for hl7v2::parser using proptest.
//!
//! Tests cover:
//! - Parser roundtrip invariance (parse → write → parse)
//! - Valid messages should always parse
//! - Invalid messages should return errors (not panic)
//! - Edge cases with random data

#![expect(
    clippy::arithmetic_side_effects,
    clippy::assertions_on_result_states,
    clippy::indexing_slicing,
    clippy::let_underscore_must_use,
    clippy::manual_let_else,
    clippy::uninlined_format_args,
    clippy::unwrap_used,
    reason = "pre-existing parser property test debt moved into hl7v2; cleanup is split from topology collapse"
)]

use crate::model::*;
use crate::{get, get_presence, parse, parse_batch};
use proptest::prelude::*;

// =============================================================================
// Custom Strategies for HL7 Data Generation
// =============================================================================

/// Generate a valid segment ID (3 uppercase letters or digits)
fn segment_id_strategy() -> impl Strategy<Value = String> {
    "[A-Z0-9]{3}"
}

/// Generate a valid field value (no delimiters or control characters)
fn _field_value_strategy() -> impl Strategy<Value = String> {
    "[A-Za-z0-9 .,_-]{0,50}"
}

/// Generate a simple field value without delimiters
fn simple_field_value() -> impl Strategy<Value = String> {
    "[A-Za-z0-9]{1,20}"
}

/// Generate a valid MSH segment with standard delimiters
fn msh_segment_strategy() -> impl Strategy<Value = String> {
    (
        simple_field_value(), // Sending app
        simple_field_value(), // Sending facility
        simple_field_value(), // Receiving app
        simple_field_value(), // Receiving facility
        "[0-9]{14}",          // DateTime
        simple_field_value(), // Message type trigger
        simple_field_value(), // Message control ID
        "[PAT]",              // Processing ID
        "2\\.[0-9]\\.[0-9]",  // Version
    )
        .prop_map(
            |(app, fac, recv_app, recv_fac, dt, trigger, ctrl, proc, ver)| {
                format!(
                    "MSH|^~\\&|{}|{}|{}|{}|{}||ADT^{}|{}|{}|{}",
                    app, fac, recv_app, recv_fac, dt, trigger, ctrl, proc, ver
                )
            },
        )
}

/// Generate a simple PID segment
fn pid_segment_strategy() -> impl Strategy<Value = String> {
    (
        simple_field_value(), // Patient ID
        simple_field_value(), // Last name
        simple_field_value(), // First name
    )
        .prop_map(|(id, last, first)| format!("PID|1||{}^^^HOSP^MR||{}^{}", id, last, first))
}

/// Generate a valid HL7 message with MSH and optional PID
fn valid_message_strategy() -> impl Strategy<Value = String> {
    (
        msh_segment_strategy(),
        prop::option::of(pid_segment_strategy()),
    )
        .prop_map(|(msh, pid)| match pid {
            Some(pid) => format!("{}\r{}\r", msh, pid),
            None => format!("{}\r", msh),
        })
}

/// Generate random bytes that may or may not be valid HL7
fn _random_bytes_strategy() -> impl Strategy<Value = Vec<u8>> {
    prop::collection::vec(any::<u8>(), 0..1000)
}

// =============================================================================
// Roundtrip Tests
// =============================================================================

proptest! {
    #[test]
    fn test_roundtrip_simple_message(
        app in simple_field_value(),
        fac in simple_field_value(),
        recv_app in simple_field_value(),
        recv_fac in simple_field_value(),
        dt in "[0-9]{14}",
        trigger in simple_field_value(),
        ctrl in simple_field_value(),
        proc in "[PAT]",
        ver in "2\\.[0-9]\\.[0-9]"
    ) {
        let hl7 = format!(
            "MSH|^~\\&|{}|{}|{}|{}|{}||ADT^{}|{}|{}|{}\r",
            app, fac, recv_app, recv_fac, dt, trigger, ctrl, proc, ver
        );

        // Parse the message
        let message = match parse(hl7.as_bytes()) {
            Ok(m) => m,
            Err(_) => return Ok(()), // Some inputs may be invalid, that's OK
        };

        // Verify basic structure
        prop_assert!(!message.segments.is_empty());
        prop_assert_eq!(&message.segments[0].id, b"MSH");
    }

    #[test]
    fn test_roundtrip_with_pid(
        app in simple_field_value(),
        fac in simple_field_value(),
        patient_id in simple_field_value(),
        last_name in simple_field_value(),
        first_name in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|{}|{}|RecvApp|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||{}^^^HOSP^MR||{}^{}\r",
            app, fac, patient_id, last_name, first_name
        );

        // Parse the message
        let message = parse(hl7.as_bytes())?;

        // Verify structure
        prop_assert_eq!(message.segments.len(), 2);
        prop_assert_eq!(&message.segments[0].id, b"MSH");
        prop_assert_eq!(&message.segments[1].id, b"PID");

        // Verify field access
        prop_assert_eq!(get(&message, "PID.3.1"), Some(patient_id.as_str()));
        prop_assert_eq!(get(&message, "PID.5.1"), Some(last_name.as_str()));
        prop_assert_eq!(get(&message, "PID.5.2"), Some(first_name.as_str()));
    }

    // Note: Custom delimiter test removed due to too many rejections
    // The test works but proptest rejects too many cases where delimiters match
}

// =============================================================================
// No Panic Tests
// =============================================================================

proptest! {
    #[test]
    fn test_random_bytes_never_panics(bytes in prop::collection::vec(any::<u8>(), 0..1000)) {
        // Random bytes should either parse successfully or return an error
        // They should NEVER panic
        let _ = parse(&bytes);
    }

    #[test]
    fn test_random_string_never_panics(s in ".*") {
        // Random strings should either parse successfully or return an error
        // They should NEVER panic
        let _ = parse(s.as_bytes());
    }

    #[test]
    fn test_random_bytes_batch_never_panics(bytes in prop::collection::vec(any::<u8>(), 0..1000)) {
        // Random bytes for batch parsing should never panic
        let _ = parse_batch(&bytes);
    }
}

// =============================================================================
// Valid Message Parsing Tests
// =============================================================================

proptest! {
    #[test]
    fn test_valid_message_always_parses(hl7 in valid_message_strategy()) {
        // A valid message should always parse successfully
        let result = parse(hl7.as_bytes());
        prop_assert!(result.is_ok(), "Valid message should parse: {:?}", result);

        let message = result.unwrap();
        prop_assert!(!message.segments.is_empty());
        prop_assert_eq!(&message.segments[0].id, b"MSH");
    }

    #[test]
    fn test_message_with_repeating_fields(
        app in simple_field_value(),
        name1 in simple_field_value(),
        name2 in simple_field_value(),
        name3 in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|{}|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||123||{}~{}~{}\r",
            app, name1, name2, name3
        );

        let message = parse(hl7.as_bytes())?;

        // Verify all repetitions are accessible
        prop_assert_eq!(get(&message, "PID.5[1].1"), Some(name1.as_str()));
        prop_assert_eq!(get(&message, "PID.5[2].1"), Some(name2.as_str()));
        prop_assert_eq!(get(&message, "PID.5[3].1"), Some(name3.as_str()));
    }

    #[test]
    fn test_message_with_components(
        id in simple_field_value(),
        namespace in simple_field_value(),
        type_code in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||{}^^^{}^{}\r",
            id, namespace, type_code
        );

        let message = parse(hl7.as_bytes())?;

        // Verify components are accessible
        prop_assert_eq!(get(&message, "PID.3.1"), Some(id.as_str()));
        prop_assert_eq!(get(&message, "PID.3.4"), Some(namespace.as_str()));
        prop_assert_eq!(get(&message, "PID.3.5"), Some(type_code.as_str()));
    }

    #[test]
    fn test_message_with_subcomponents(
        value1 in simple_field_value(),
        value2 in simple_field_value(),
        value3 in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||123||{}&{}&{}\r",
            value1, value2, value3
        );

        let message = parse(hl7.as_bytes())?;

        // The get function returns the first subcomponent by default
        prop_assert_eq!(get(&message, "PID.5.1"), Some(value1.as_str()));
    }
}

// =============================================================================
// Edge Case Tests
// =============================================================================

proptest! {
    #[test]
    fn test_empty_field_handling(
        app in simple_field_value(),
        fac in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|{}|{}|||||ADT^A01|MSG123|P|2.5\rPID|1|||||||\r",
            app, fac
        );

        let message = parse(hl7.as_bytes())?;

        // Empty fields should be parsed correctly
        match get_presence(&message, "PID.3.1") {
            Presence::Empty | Presence::Missing => {}
            Presence::Value(_) => prop_assert!(false, "Expected empty or missing"),
            Presence::Null => {}
        }
    }

    #[test]
    fn test_long_field_value(value in "[A-Za-z0-9]{1,1000}") {
        let hl7 = format!(
            "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||{}||Test\r",
            value
        );

        let message = parse(hl7.as_bytes())?;

        prop_assert_eq!(get(&message, "PID.3.1"), Some(value.as_str()));
    }

    #[test]
    fn test_many_segments(num_segments in 1usize..50) {
        let mut hl7 = String::from("MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\r");
        for i in 0..num_segments {
            hl7.push_str(&format!("OBX|{}|ST|Test||Value\r", i));
        }

        let message = parse(hl7.as_bytes())?;

        prop_assert_eq!(message.segments.len(), 1 + num_segments);
    }

    #[test]
    fn test_many_repetitions(num_reps in 1usize..20) {
        let mut field_value = String::new();
        for i in 0..num_reps {
            if i > 0 {
                field_value.push('~');
            }
            field_value.push_str(&format!("Name{}", i));
        }

        let hl7 = format!(
            "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||123||{}\r",
            field_value
        );

        let message = parse(hl7.as_bytes())?;

        // First repetition
        prop_assert_eq!(get(&message, "PID.5.1"), Some("Name0"));

        // Last repetition
        let last_name = format!("Name{}", num_reps - 1);
        prop_assert_eq!(get(&message, &format!("PID.5[{}].1", num_reps)), Some(last_name.as_str()));
    }
}

// =============================================================================
// Delimiter Validation Tests
// =============================================================================

#[test]
fn test_delimiter_uniqueness_required() {
    // Same delimiters should fail
    let hl7 = "MSH|||||App|Fac\r";
    let result = parse(hl7.as_bytes());
    assert!(result.is_err());
}

#[test]
fn test_standard_delimiters_work() {
    let hl7 = "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\r";
    let message = parse(hl7.as_bytes()).unwrap();

    assert_eq!(message.delims.field, '|');
    assert_eq!(message.delims.comp, '^');
    assert_eq!(message.delims.rep, '~');
    assert_eq!(message.delims.esc, '\\');
    assert_eq!(message.delims.sub, '&');
}

// =============================================================================
// Segment ID Tests
// =============================================================================

proptest! {
    #[test]
    fn test_valid_segment_ids(seg_id in segment_id_strategy()) {
        let hl7 = format!(
            "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\r{}|1\r",
            seg_id
        );

        let result = parse(hl7.as_bytes());
        // Should parse successfully with any valid 3-char segment ID
        prop_assert!(result.is_ok());
    }
}

// =============================================================================
// Field Access Tests
// =============================================================================

proptest! {
    #[test]
    fn test_field_access_consistency(
        app in simple_field_value(),
        fac in simple_field_value(),
        recv_app in simple_field_value(),
        recv_fac in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|{}|{}|{}|{}|20250128120000||ADT^A01|MSG123|P|2.5\r",
            app, fac, recv_app, recv_fac
        );

        let message = parse(hl7.as_bytes())?;

        // MSH field numbering is special (MSH-1 is the field separator)
        prop_assert_eq!(get(&message, "MSH.3"), Some(app.as_str()));
        prop_assert_eq!(get(&message, "MSH.4"), Some(fac.as_str()));
        prop_assert_eq!(get(&message, "MSH.5"), Some(recv_app.as_str()));
        prop_assert_eq!(get(&message, "MSH.6"), Some(recv_fac.as_str()));
    }

    #[test]
    fn test_missing_field_returns_none(field_num in 100usize..1000) {
        let hl7 = "MSH|^~\\&|App|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||123||Test\r";
        let message = parse(hl7.as_bytes())?;

        let path = format!("PID.{}.1", field_num);
        prop_assert_eq!(get(&message, &path), None);
    }
}

// =============================================================================
// Batch Property Tests
// =============================================================================

proptest! {
    #[test]
    fn test_single_message_as_batch(
        app in simple_field_value(),
        patient_id in simple_field_value()
    ) {
        let hl7 = format!(
            "MSH|^~\\&|{}|Fac|Recv|RecvFac|20250128120000||ADT^A01|MSG123|P|2.5\rPID|1||{}||Test\r",
            app, patient_id
        );

        let batch = parse_batch(hl7.as_bytes())?;

        prop_assert!(batch.header.is_none());
        prop_assert!(batch.trailer.is_none());
        prop_assert_eq!(batch.messages.len(), 1);
    }
}
