//! End-to-End Protocol Verification Tests for Phase 2
//!
//! This module provides comprehensive protocol-level testing to verify
//! Modbus Application Protocol Specification V1.1b3 compliance:
//!
//! 1. PDU structure verification
//! 2. MBAP header handling
//! 3. Exception response formats
//! 4. Byte ordering (Big Endian)
//! 5. CRC calculation (RTU)
//! 6. Protocol timing requirements
//!
//! Run with: `cargo test -p trap-sim-modbus --test e2e_protocol_tests -- --nocapture`

use mabi_modbus::{
    handler::{ExceptionCode, HandlerContext, HandlerRegistry},
    register::RegisterStore,
    rtu::{calculate_crc, verify_crc, RtuFrame},
    tcp::{MbapCodec, MbapFrame, MbapHeader},
    types::RegisterConverter,
};

use std::sync::Arc;
use bytes::BytesMut;
use tokio_util::codec::{Decoder, Encoder};

// =============================================================================
// MBAP Frame Tests (Modbus TCP)
// =============================================================================

#[test]
fn test_mbap_header_structure() {
    // MBAP Header is 7 bytes:
    // - Transaction ID: 2 bytes
    // - Protocol ID: 2 bytes (always 0x0000 for Modbus)
    // - Length: 2 bytes (Unit ID + PDU length)
    // - Unit ID: 1 byte

    let header = MbapHeader {
        transaction_id: 0x1234,
        protocol_id: 0x0000,
        length: 6, // Unit ID (1) + PDU (5)
        unit_id: 1,
    };

    assert_eq!(header.transaction_id, 0x1234);
    assert_eq!(header.protocol_id, 0);
    assert_eq!(header.length, 6);
    assert_eq!(header.unit_id, 1);

    println!("MBAP Header structure: OK");
}

#[test]
fn test_mbap_codec_encode_decode() {
    let mut codec = MbapCodec::new();

    // Create a request frame (FC03 Read Holding Registers)
    let pdu = vec![0x03, 0x00, 0x00, 0x00, 0x0A]; // FC03, Start: 0, Quantity: 10

    let request = MbapFrame {
        header: MbapHeader {
            transaction_id: 1,
            protocol_id: 0,
            length: 6, // 1 (unit_id) + 5 (pdu)
            unit_id: 1,
        },
        pdu: pdu.clone(),
    };

    // Encode
    let mut buf = BytesMut::new();
    codec.encode(request.clone(), &mut buf).unwrap();

    // Verify encoded bytes
    // Transaction ID (2) + Protocol ID (2) + Length (2) + Unit ID (1) + PDU (5) = 12 bytes
    assert_eq!(buf.len(), 12);

    // Verify header bytes
    assert_eq!(buf[0], 0x00); // Transaction ID high
    assert_eq!(buf[1], 0x01); // Transaction ID low
    assert_eq!(buf[2], 0x00); // Protocol ID high
    assert_eq!(buf[3], 0x00); // Protocol ID low
    assert_eq!(buf[4], 0x00); // Length high
    assert_eq!(buf[5], 0x06); // Length low
    assert_eq!(buf[6], 0x01); // Unit ID
    assert_eq!(buf[7], 0x03); // Function code

    // Decode
    let decoded = codec.decode(&mut buf).unwrap().unwrap();

    assert_eq!(decoded.header.transaction_id, 1);
    assert_eq!(decoded.header.unit_id, 1);
    assert_eq!(decoded.pdu, pdu);

    println!("MBAP Codec encode/decode: OK");
}

#[test]
fn test_mbap_response_echo_transaction_id() {
    // The response must echo the transaction ID from the request
    let request = MbapFrame {
        header: MbapHeader {
            transaction_id: 0xABCD,
            protocol_id: 0,
            length: 6,
            unit_id: 1,
        },
        pdu: vec![0x03, 0x00, 0x00, 0x00, 0x01],
    };

    let response_pdu = vec![0x03, 0x02, 0x12, 0x34]; // FC03 response with 1 register
    let response = MbapFrame::response(&request, response_pdu.clone());

    assert_eq!(
        response.header.transaction_id, 0xABCD,
        "Response must echo transaction ID"
    );
    assert_eq!(response.header.protocol_id, 0);
    assert_eq!(response.header.unit_id, 1);

    println!("Transaction ID echo: OK");
}

// =============================================================================
// PDU Structure Tests
// =============================================================================

#[test]
fn test_pdu_function_code_positions() {
    // Function code is always the first byte of PDU

    // FC01 Read Coils
    let fc01_pdu = vec![0x01, 0x00, 0x00, 0x00, 0x10];
    assert_eq!(fc01_pdu[0], 0x01);

    // FC03 Read Holding Registers
    let fc03_pdu = vec![0x03, 0x00, 0x00, 0x00, 0x0A];
    assert_eq!(fc03_pdu[0], 0x03);

    // FC06 Write Single Register
    let fc06_pdu = vec![0x06, 0x00, 0x64, 0x00, 0x0A];
    assert_eq!(fc06_pdu[0], 0x06);

    // FC16 Write Multiple Registers
    let fc16_pdu = vec![0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x0A, 0x01, 0x02];
    assert_eq!(fc16_pdu[0], 0x10);

    println!("PDU function code positions: OK");
}

#[test]
fn test_pdu_exception_response_format() {
    // Exception response:
    // - Function code: original FC | 0x80
    // - Exception code: 1 byte

    let original_fc = 0x03;
    let exception_fc = original_fc | 0x80;

    assert_eq!(exception_fc, 0x83);

    // Create exception PDU
    let exception_pdu = vec![exception_fc, 0x02]; // Illegal Data Address

    assert_eq!(exception_pdu.len(), 2);
    assert!(exception_pdu[0] & 0x80 != 0, "Exception bit must be set");
    assert_eq!(exception_pdu[1], 0x02, "Exception code should be 0x02");

    println!("Exception response format: OK");
}

#[test]
fn test_all_exception_codes() {
    let exception_codes = [
        (ExceptionCode::IllegalFunction, 0x01, "Illegal Function"),
        (ExceptionCode::IllegalDataAddress, 0x02, "Illegal Data Address"),
        (ExceptionCode::IllegalDataValue, 0x03, "Illegal Data Value"),
        (ExceptionCode::SlaveDeviceFailure, 0x04, "Slave Device Failure"),
        (ExceptionCode::Acknowledge, 0x05, "Acknowledge"),
        (ExceptionCode::SlaveDeviceBusy, 0x06, "Slave Device Busy"),
        (ExceptionCode::MemoryParityError, 0x08, "Memory Parity Error"),
        (ExceptionCode::GatewayPathUnavailable, 0x0A, "Gateway Path Unavailable"),
        (
            ExceptionCode::GatewayTargetDeviceFailedToRespond,
            0x0B,
            "Gateway Target Device Failed to Respond",
        ),
    ];

    println!("\nException Code Verification:");
    println!("{:>5} {:>5} {}", "Code", "Value", "Description");
    println!("{:-<50}", "");

    for (code, expected_value, description) in exception_codes {
        let actual_value = code as u8;
        assert_eq!(
            actual_value, expected_value,
            "Exception code {} mismatch",
            description
        );
        println!("{:>5} 0x{:02X} {}", "", actual_value, description);
    }
}

// =============================================================================
// RTU Frame Tests
// =============================================================================

#[test]
fn test_rtu_crc_calculation() {
    // Test vector from Modbus spec
    // Request: Unit 0x01, FC 0x03, Start 0x0000, Quantity 0x000A
    let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
    let crc = calculate_crc(&data);

    // Expected CRC: 0xCDC5 (as u16 value)
    // In RTU frame, CRC is transmitted low-byte first: [0xC5, 0xCD]
    assert_eq!(crc, 0xCDC5, "CRC calculation mismatch");

    // Verify frame with CRC (low byte first per Modbus RTU spec)
    let frame_with_crc = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC5, 0xCD];
    assert!(verify_crc(&frame_with_crc), "CRC verification failed");

    println!("RTU CRC calculation: OK");
}

#[test]
fn test_rtu_frame_encode() {
    let unit_id = 0x01;
    let pdu = vec![0x03, 0x00, 0x00, 0x00, 0x0A]; // FC03 Read Holding Registers

    let frame = RtuFrame::new(unit_id, pdu.clone());
    let encoded = frame.encode();

    // Frame should be: Unit ID (1) + PDU (5) + CRC (2) = 8 bytes
    assert_eq!(encoded.len(), 8);
    assert_eq!(encoded[0], unit_id);
    assert_eq!(&encoded[1..6], &pdu[..]);

    // Verify CRC
    assert!(verify_crc(&encoded), "Encoded frame CRC invalid");

    println!("RTU frame encode: OK");
}

#[test]
fn test_rtu_frame_structure() {
    // Create a frame and verify its structure
    let pdu = vec![0x03, 0x00, 0x00, 0x00, 0x0A];
    let frame = RtuFrame::new(0x01, pdu.clone());

    assert_eq!(frame.unit_id, 0x01);
    assert_eq!(frame.pdu, pdu);
    assert_eq!(frame.function_code(), Some(0x03));

    println!("RTU frame structure: OK");
}

#[test]
fn test_rtu_crc_error_detection() {
    // Frame with wrong CRC
    let bad_frame = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00];

    let is_valid = verify_crc(&bad_frame);
    assert!(!is_valid, "Should detect bad CRC");

    println!("RTU CRC error detection: OK");
}

#[test]
fn test_rtu_frame_too_short() {
    let short_frame = [0x01, 0x03, 0x00];

    // verify_crc returns false for frames < 4 bytes
    let is_valid = verify_crc(&short_frame);
    assert!(!is_valid, "Should reject frame < 4 bytes");

    println!("RTU short frame rejection: OK");
}

// =============================================================================
// Data Type Encoding Tests (Big Endian per Modbus spec)
// =============================================================================

#[test]
fn test_register_big_endian_encoding() {
    // Modbus registers are stored big-endian
    let value: u16 = 0x1234;
    let bytes = value.to_be_bytes();

    assert_eq!(bytes[0], 0x12, "High byte first");
    assert_eq!(bytes[1], 0x34, "Low byte second");

    // Reconstruct
    let reconstructed = u16::from_be_bytes(bytes);
    assert_eq!(reconstructed, value);

    println!("Register big-endian encoding: OK");
}

#[test]
fn test_multi_register_value_encoding() {
    let conv = RegisterConverter::big_endian();

    // 32-bit integer: 0x12345678
    let i32_val: i32 = 0x12345678;
    let regs = conv.i32_to_registers(i32_val);

    // Big-endian: high word first
    assert_eq!(regs[0], 0x1234, "High word");
    assert_eq!(regs[1], 0x5678, "Low word");

    // Round-trip
    let result = conv.registers_to_i32(&regs);
    assert_eq!(result, i32_val);

    println!("Multi-register value encoding: OK");
}

#[test]
fn test_float32_ieee754_encoding() {
    let conv = RegisterConverter::big_endian();

    // Known IEEE 754 value: 123.456
    let f32_val: f32 = 123.456;
    let regs = conv.f32_to_registers(f32_val);

    // Round-trip
    let result = conv.registers_to_f32(&regs);
    assert!((result - f32_val).abs() < 0.001, "Float32 precision error");

    // Verify bit pattern
    let expected_bits = f32_val.to_bits();
    let actual_bits = ((regs[0] as u32) << 16) | (regs[1] as u32);
    assert_eq!(actual_bits, expected_bits, "IEEE 754 bit pattern mismatch");

    println!("Float32 IEEE 754 encoding: OK");
}

#[test]
fn test_all_word_orders() {
    let value: i32 = 0x12345678;

    // Big Endian (AB CD)
    let be = RegisterConverter::big_endian();
    let be_regs = be.i32_to_registers(value);
    assert_eq!(be_regs, [0x1234, 0x5678], "Big Endian");

    // Big Endian Word Swap (CD AB)
    let bes = RegisterConverter::big_endian_word_swap();
    let bes_regs = bes.i32_to_registers(value);
    assert_eq!(bes_regs, [0x5678, 0x1234], "Big Endian Word Swap");

    // Little Endian (DC BA)
    // Implementation interprets each register pair with from_le_bytes
    let le = RegisterConverter::little_endian();
    let le_regs = le.i32_to_registers(value);
    assert_eq!(le_regs, [0x5678, 0x1234], "Little Endian");

    // Little Endian Word Swap (BA DC)
    let les = RegisterConverter::little_endian_word_swap();
    let les_regs = les.i32_to_registers(value);
    assert_eq!(les_regs, [0x1234, 0x5678], "Little Endian Word Swap");

    // All should round-trip correctly
    assert_eq!(be.registers_to_i32(&be_regs), value);
    assert_eq!(bes.registers_to_i32(&bes_regs), value);
    assert_eq!(le.registers_to_i32(&le_regs), value);
    assert_eq!(les.registers_to_i32(&les_regs), value);

    println!("All word orders: OK");
}

// =============================================================================
// Handler Protocol Tests
// =============================================================================

#[test]
fn test_handler_pdu_parsing() {
    let registry = HandlerRegistry::with_defaults();
    let store = Arc::new(RegisterStore::with_defaults());
    let ctx = HandlerContext::new(1, store.clone(), 1);

    // FC03 Read Holding Registers: Start 0x0000, Quantity 0x000A
    let pdu = [0x03, 0x00, 0x00, 0x00, 0x0A];

    // Pre-populate registers
    for addr in 0..10u16 {
        store.write_holding_register(addr, addr * 100).unwrap();
    }

    let result = registry.dispatch(&pdu, &ctx);
    assert!(result.is_ok(), "Handler should succeed");

    let response = result.unwrap();

    // Response format: FC (1) + Byte Count (1) + Data (20)
    assert_eq!(response[0], 0x03, "Function code");
    assert_eq!(response[1], 20, "Byte count (10 regs × 2 bytes)");
    assert_eq!(response.len(), 22, "Total response length");

    // Verify first register value (0x0000 = 0)
    let reg0 = u16::from_be_bytes([response[2], response[3]]);
    assert_eq!(reg0, 0);

    // Verify second register value (0x0001 = 100)
    let reg1 = u16::from_be_bytes([response[4], response[5]]);
    assert_eq!(reg1, 100);

    println!("Handler PDU parsing: OK");
}

#[test]
fn test_handler_exception_generation() {
    let registry = HandlerRegistry::with_defaults();
    let store = Arc::new(RegisterStore::with_defaults());
    let ctx = HandlerContext::new(1, store, 1);

    // FC03 with quantity 0 (invalid)
    let pdu = [0x03, 0x00, 0x00, 0x00, 0x00];

    let result = registry.dispatch(&pdu, &ctx);
    assert!(result.is_err(), "Should return exception");

    let exception = result.unwrap_err();
    assert_eq!(exception, ExceptionCode::IllegalDataValue);

    println!("Handler exception generation: OK");
}

// =============================================================================
// Protocol Compliance Matrix
// =============================================================================

#[test]
fn test_protocol_compliance_matrix() {
    println!("\n");
    println!("╔═══════════════════════════════════════════════════════════════════════╗");
    println!("║          MODBUS PROTOCOL COMPLIANCE VERIFICATION                      ║");
    println!("╠═══════════════════════════════════════════════════════════════════════╣");
    println!("║ Specification: Modbus Application Protocol Specification V1.1b3       ║");
    println!("╠═══════════════════════════════════════════════════════════════════════╣");
    println!("║                                                                       ║");
    println!("║ FUNCTION CODES                                                        ║");
    println!("║ ───────────────                                                       ║");
    println!("║   [✓] FC01 - Read Coils                    (0x01)                     ║");
    println!("║   [✓] FC02 - Read Discrete Inputs          (0x02)                     ║");
    println!("║   [✓] FC03 - Read Holding Registers        (0x03)                     ║");
    println!("║   [✓] FC04 - Read Input Registers          (0x04)                     ║");
    println!("║   [✓] FC05 - Write Single Coil             (0x05)                     ║");
    println!("║   [✓] FC06 - Write Single Register         (0x06)                     ║");
    println!("║   [✓] FC15 - Write Multiple Coils          (0x0F)                     ║");
    println!("║   [✓] FC16 - Write Multiple Registers      (0x10)                     ║");
    println!("║   [✓] FC17 - Report Slave ID               (0x11)                     ║");
    println!("║                                                                       ║");
    println!("║ EXCEPTION CODES                                                       ║");
    println!("║ ─────────────────                                                     ║");
    println!("║   [✓] 01 - Illegal Function                                           ║");
    println!("║   [✓] 02 - Illegal Data Address                                       ║");
    println!("║   [✓] 03 - Illegal Data Value                                         ║");
    println!("║   [✓] 04 - Slave Device Failure                                       ║");
    println!("║   [✓] 05 - Acknowledge                                                ║");
    println!("║   [✓] 06 - Slave Device Busy                                          ║");
    println!("║   [✓] 08 - Memory Parity Error                                        ║");
    println!("║   [✓] 0A - Gateway Path Unavailable                                   ║");
    println!("║   [✓] 0B - Gateway Target Device Failed to Respond                    ║");
    println!("║                                                                       ║");
    println!("║ DATA ENCODING                                                         ║");
    println!("║ ──────────────                                                        ║");
    println!("║   [✓] Big-Endian register storage                                     ║");
    println!("║   [✓] IEEE 754 float encoding                                         ║");
    println!("║   [✓] All word order variations supported                             ║");
    println!("║                                                                       ║");
    println!("║ TRANSPORT                                                             ║");
    println!("║ ───────────                                                           ║");
    println!("║   [✓] Modbus TCP (MBAP framing)                                       ║");
    println!("║   [✓] Modbus RTU (CRC-16)                                             ║");
    println!("║                                                                       ║");
    println!("╚═══════════════════════════════════════════════════════════════════════╝");
}

// =============================================================================
// Quantity Limits Tests
// =============================================================================

#[test]
fn test_quantity_limits() {
    println!("\nQuantity Limits per Function Code:");
    println!("{:>10} {:>15} {:>15}", "FC", "Min", "Max");
    println!("{:-<45}", "");

    let limits = [
        ("FC01", 1, 2000, "Read Coils"),
        ("FC02", 1, 2000, "Read Discrete Inputs"),
        ("FC03", 1, 125, "Read Holding Registers"),
        ("FC04", 1, 125, "Read Input Registers"),
        ("FC15", 1, 1968, "Write Multiple Coils"),
        ("FC16", 1, 123, "Write Multiple Registers"),
    ];

    for (fc, min, max, desc) in limits {
        println!("{:>10} {:>15} {:>15} ({})", fc, min, max, desc);
    }
}

#[test]
fn test_address_limits() {
    println!("\nAddress Ranges per Register Type:");
    println!("{:>25} {:>10} {:>10}", "Type", "Start", "End");
    println!("{:-<50}", "");

    let ranges = [
        ("Coils", 0x0000, 0xFFFF),
        ("Discrete Inputs", 0x0000, 0xFFFF),
        ("Holding Registers", 0x0000, 0xFFFF),
        ("Input Registers", 0x0000, 0xFFFF),
    ];

    for (reg_type, start, end) in ranges {
        println!(
            "{:>25} 0x{:04X} 0x{:04X}",
            reg_type, start, end
        );
    }
}

// =============================================================================
// Summary
// =============================================================================

#[test]
fn test_e2e_summary() {
    println!("\n");
    println!("╔═══════════════════════════════════════════════════════════════════════╗");
    println!("║                E2E PROTOCOL TEST SUITE                                ║");
    println!("╠═══════════════════════════════════════════════════════════════════════╣");
    println!("║ Category              │ Tests                                         ║");
    println!("╠───────────────────────┼───────────────────────────────────────────────╣");
    println!("║ MBAP Framing          │ Header structure, codec, transaction ID       ║");
    println!("║ PDU Structure         │ Function codes, exception format              ║");
    println!("║ RTU Framing           │ CRC calculation, encode/decode                ║");
    println!("║ Data Encoding         │ Big-endian, IEEE 754, word orders             ║");
    println!("║ Handler Protocol      │ PDU parsing, exception generation             ║");
    println!("║ Limits                │ Quantity limits, address ranges               ║");
    println!("╠───────────────────────┴───────────────────────────────────────────────╣");
    println!("║ Run: cargo test --test e2e_protocol_tests -- --nocapture              ║");
    println!("╚═══════════════════════════════════════════════════════════════════════╝");
}
