//! Protocol parsing tests

use xfr::protocol::{
    AggregateInterval, ControlMessage, Direction, Protocol, StreamInterval, StreamResult,
    TcpInfoSnapshot, TestResult, UdpIntervalProgress,
};

#[test]
fn test_hello_serialization() {
    let msg = ControlMessage::client_hello();
    let json = msg.serialize().unwrap();

    assert!(json.contains("\"type\":\"hello\""));
    assert!(json.contains("\"version\":\"1.1\""));
    assert!(json.contains("\"client\":\"xfr/"));
    assert!(json.contains("\"capabilities\""));
    assert!(json.contains("\"single_port_tcp\""));
}

#[test]
fn test_server_hello_serialization() {
    let msg = ControlMessage::server_hello();
    let json = msg.serialize().unwrap();

    assert!(json.contains("\"type\":\"hello\""));
    assert!(json.contains("\"server\":\"xfr/"));
    assert!(json.contains("\"capabilities\""));
}

#[test]
fn test_test_start_roundtrip() {
    let msg = ControlMessage::TestStart {
        id: "test-123".to_string(),
        protocol: Protocol::Tcp,
        streams: 4,
        duration_secs: 30,
        direction: Direction::Upload,
        bitrate: None,
        congestion: Some("bbr".to_string()),
        mptcp: false,
        dscp: None,
        window_size: None,
        zerocopy: false,
        mtu_probe: false,
        tcp_nodelay: false,
    };

    let json = msg.serialize().unwrap();
    assert!(json.contains("\"congestion\":\"bbr\""));
    assert!(
        !json.contains("\"mptcp\""),
        "mptcp=false should be omitted for backward compatibility"
    );
    let decoded = ControlMessage::deserialize(&json).unwrap();

    match decoded {
        ControlMessage::TestStart {
            id,
            protocol,
            streams,
            duration_secs,
            direction,
            bitrate,
            congestion,
            ..
        } => {
            assert_eq!(id, "test-123");
            assert_eq!(protocol, Protocol::Tcp);
            assert_eq!(streams, 4);
            assert_eq!(duration_secs, 30);
            assert_eq!(direction, Direction::Upload);
            assert!(bitrate.is_none());
            assert_eq!(congestion, Some("bbr".to_string()));
        }
        _ => panic!("Wrong message type"),
    }
}

#[test]
fn test_test_start_backward_compat_without_mptcp_field() {
    // Simulates an older client that doesn't send the mptcp field.
    let json = r#"{"type":"test_start","id":"legacy-123","protocol":"tcp","streams":2,"duration_secs":15,"direction":"upload"}"#;
    let decoded = ControlMessage::deserialize(json).unwrap();

    match decoded {
        ControlMessage::TestStart { id, mptcp, .. } => {
            assert_eq!(id, "legacy-123");
            assert!(!mptcp);
        }
        _ => panic!("Wrong message type"),
    }
}

#[test]
fn test_udp_test_start() {
    let msg = ControlMessage::TestStart {
        id: "udp-test".to_string(),
        protocol: Protocol::Udp,
        streams: 1,
        duration_secs: 10,
        direction: Direction::Download,
        bitrate: Some(1_000_000_000),
        congestion: None,
        mptcp: false,
        dscp: None,
        window_size: None,
        zerocopy: false,
        mtu_probe: false,
        tcp_nodelay: false,
    };

    let json = msg.serialize().unwrap();
    assert!(json.contains("\"protocol\":\"udp\""));
    assert!(json.contains("\"bitrate\":1000000000"));
    assert!(json.contains("\"direction\":\"download\""));
}

#[test]
fn test_interval_message() {
    let msg = ControlMessage::Interval {
        id: "test-123".to_string(),
        elapsed_ms: 1000,
        streams: vec![
            StreamInterval {
                id: 0,
                bytes: 125_000_000,
                retransmits: Some(2),
                jitter_ms: None,
                lost: None,
                error: None,
                rtt_us: Some(1234),
                cwnd: Some(65535),
            },
            StreamInterval {
                id: 1,
                bytes: 125_000_000,
                retransmits: Some(1),
                jitter_ms: None,
                lost: None,
                error: None,
                rtt_us: Some(1200),
                cwnd: Some(32768),
            },
        ],
        aggregate: AggregateInterval {
            bytes: 250_000_000,
            throughput_mbps: 2000.0,
            retransmits: Some(3),
            jitter_ms: None,
            lost: None,
            udp_progress: None,
            rtt_us: Some(1217),
            cwnd: Some(98303),
            bytes_sent: None,
            bytes_received: None,
            throughput_send_mbps: None,
            throughput_recv_mbps: None,
        },
    };

    let json = msg.serialize().unwrap();
    let decoded = ControlMessage::deserialize(&json).unwrap();

    match decoded {
        ControlMessage::Interval {
            elapsed_ms,
            streams,
            aggregate,
            ..
        } => {
            assert_eq!(elapsed_ms, 1000);
            assert_eq!(streams.len(), 2);
            assert_eq!(aggregate.throughput_mbps, 2000.0);
            assert_eq!(aggregate.rtt_us, Some(1217));
            assert_eq!(aggregate.cwnd, Some(98303));
            assert_eq!(streams[0].rtt_us, Some(1234));
            assert_eq!(streams[1].cwnd, Some(32768));
        }
        _ => panic!("Wrong message type"),
    }
}

#[test]
fn test_interval_forward_compat() {
    // Interval JSON with extra unknown fields should still deserialize (serde ignores them)
    let json = r#"{"type":"interval","id":"test-1","elapsed_ms":1000,"streams":[{"id":0,"bytes":100,"retransmits":0,"rtt_us":500,"cwnd":1024,"future_field":99}],"aggregate":{"bytes":100,"throughput_mbps":0.8,"retransmits":0,"rtt_us":500,"cwnd":1024,"future_field":99}}"#;
    let decoded = ControlMessage::deserialize(json).unwrap();
    match decoded {
        ControlMessage::Interval {
            streams, aggregate, ..
        } => {
            assert_eq!(streams[0].rtt_us, Some(500));
            assert_eq!(streams[0].cwnd, Some(1024));
            assert_eq!(aggregate.rtt_us, Some(500));
            assert_eq!(aggregate.cwnd, Some(1024));
        }
        _ => panic!("Expected Interval"),
    }
}

#[test]
fn test_interval_backward_compat() {
    // Interval JSON without rtt_us/cwnd should deserialize with None
    let json = r#"{"type":"interval","id":"test-1","elapsed_ms":1000,"streams":[{"id":0,"bytes":100,"retransmits":0}],"aggregate":{"bytes":100,"throughput_mbps":0.8,"retransmits":0}}"#;
    let decoded = ControlMessage::deserialize(json).unwrap();
    match decoded {
        ControlMessage::Interval {
            streams, aggregate, ..
        } => {
            assert!(streams[0].rtt_us.is_none());
            assert!(streams[0].cwnd.is_none());
            assert!(aggregate.rtt_us.is_none());
            assert!(aggregate.cwnd.is_none());
            assert!(aggregate.udp_progress.is_none());
        }
        _ => panic!("Expected Interval"),
    }
}

#[test]
fn test_aggregate_interval_udp_progress_roundtrip() {
    // Wire-protocol smoke for the v0.9.11 addition. Serializing with
    // `udp_progress: Some(_)` must include the cumulative counts; the
    // derived percent is computed by the receiver, not shipped over the
    // wire. Deserializing a payload without the field yields None so the
    // client can render an explicit "unknown" state instead of a stale 0%.
    let agg = AggregateInterval {
        bytes: 1_000,
        throughput_mbps: 8.0,
        retransmits: None,
        jitter_ms: Some(0.4),
        lost: Some(5),
        udp_progress: Some(UdpIntervalProgress {
            packets_received: 195,
            packets_lost: 5,
        }),
        rtt_us: None,
        cwnd: None,
        bytes_sent: None,
        bytes_received: None,
        throughput_send_mbps: None,
        throughput_recv_mbps: None,
    };
    let json = serde_json::to_string(&agg).unwrap();
    assert!(json.contains("\"udp_progress\""));
    assert!(json.contains("\"packets_received\":195"));
    assert!(json.contains("\"packets_lost\":5"));
    let decoded: AggregateInterval = serde_json::from_str(&json).unwrap();
    let progress = decoded.udp_progress.unwrap();
    assert_eq!(progress.packets_received, 195);
    assert_eq!(progress.packets_lost, 5);
    assert_eq!(progress.lost_percent(), Some(2.5));

    // Old-server payload (no udp_progress) deserializes to None — the TUI
    // uses this absence to render "--%" instead of a fake 0%.
    let legacy = r#"{"bytes":1000,"throughput_mbps":8.0,"lost":5}"#;
    let decoded: AggregateInterval = serde_json::from_str(legacy).unwrap();
    assert!(decoded.udp_progress.is_none());
}

#[test]
fn test_udp_interval_progress_lost_percent_zero_traffic() {
    // Denominator-zero guard: before any packets are observed, lost_percent
    // must return None so the TUI can render "--%" rather than report 0%
    // as if it were a fresh observation.
    let p = UdpIntervalProgress {
        packets_received: 0,
        packets_lost: 0,
    };
    assert!(p.lost_percent().is_none());
}

#[test]
fn test_result_message() {
    let result = TestResult {
        id: "test-123".to_string(),
        bytes_total: 1_250_000_000,
        duration_ms: 10_000,
        throughput_mbps: 1000.0,
        streams: vec![StreamResult {
            id: 0,
            bytes: 1_250_000_000,
            throughput_mbps: 1000.0,
            retransmits: Some(42),
            jitter_ms: None,
            lost: None,
        }],
        tcp_info: Some(TcpInfoSnapshot {
            retransmits: 42,
            rtt_us: 1234,
            rtt_var_us: 100,
            cwnd: 65535,
            bytes_acked: None,
        }),
        udp_stats: None,
        bytes_sent: None,
        bytes_received: None,
        throughput_send_mbps: None,
        throughput_recv_mbps: None,
        mtu_probe: None,
    };

    let msg = ControlMessage::Result(result);
    let json = msg.serialize().unwrap();

    assert!(json.contains("\"bytes_total\":1250000000"));
    assert!(json.contains("\"throughput_mbps\":1000.0"));
    assert!(json.contains("\"rtt_us\":1234"));
}

#[test]
fn test_large_result_message_exceeds_old_8k_guard_but_fits_64k() {
    // Regression coverage for high parallel stream counts on control channel:
    // - old guard (8192) was too small
    // - new guard (65536) should still provide DoS protection
    let streams: Vec<StreamResult> = (0u8..128u8)
        .map(|i| StreamResult {
            id: i,
            bytes: u64::MAX,
            throughput_mbps: 99_999_999.9,
            retransmits: Some(u64::MAX),
            jitter_ms: None,
            lost: None,
        })
        .collect();

    let result = TestResult {
        id: "wide-result".to_string(),
        bytes_total: u64::MAX,
        duration_ms: 10_000,
        throughput_mbps: 99_999_999.9,
        streams,
        tcp_info: Some(TcpInfoSnapshot {
            retransmits: u64::MAX,
            rtt_us: 1234,
            rtt_var_us: 567,
            cwnd: u32::MAX,
            bytes_acked: None,
        }),
        udp_stats: None,
        bytes_sent: None,
        bytes_received: None,
        throughput_send_mbps: None,
        throughput_recv_mbps: None,
        mtu_probe: None,
    };

    let msg = ControlMessage::Result(result);
    let json = msg.serialize().unwrap();

    assert!(
        json.len() > 8192,
        "test fixture should exceed old 8KB guard, got {} bytes",
        json.len()
    );
    assert!(
        json.len() < 65536,
        "control message should remain under 64KB guard, got {} bytes",
        json.len()
    );
}

#[test]
fn test_error_message() {
    let msg = ControlMessage::error("Port already in use");
    let json = msg.serialize().unwrap();

    assert!(json.contains("\"type\":\"error\""));
    assert!(json.contains("\"message\":\"Port already in use\""));
}

#[test]
fn test_cancel_message() {
    let msg = ControlMessage::Cancel {
        id: "test-123".to_string(),
        reason: "User requested".to_string(),
    };

    let json = msg.serialize().unwrap();
    let decoded = ControlMessage::deserialize(&json).unwrap();

    match decoded {
        ControlMessage::Cancel { id, reason } => {
            assert_eq!(id, "test-123");
            assert_eq!(reason, "User requested");
        }
        _ => panic!("Wrong message type"),
    }
}

#[test]
fn test_protocol_display() {
    assert_eq!(format!("{}", Protocol::Tcp), "TCP");
    assert_eq!(format!("{}", Protocol::Udp), "UDP");
}

#[test]
fn test_direction_display() {
    assert_eq!(format!("{}", Direction::Upload), "Upload");
    assert_eq!(format!("{}", Direction::Download), "Download");
    assert_eq!(format!("{}", Direction::Bidir), "Bidirectional");
}

#[test]
fn test_data_hello_serialization() {
    let msg = ControlMessage::DataHello {
        test_id: "test-123".to_string(),
        stream_index: 0,
    };
    let json = msg.serialize().unwrap();

    assert!(json.contains("\"type\":\"data_hello\""));
    assert!(json.contains("\"test_id\":\"test-123\""));

    // Test roundtrip
    let decoded = ControlMessage::deserialize(&json).unwrap();
    match decoded {
        ControlMessage::DataHello {
            test_id,
            stream_index,
        } => {
            assert_eq!(test_id, "test-123");
            assert_eq!(stream_index, 0);
        }
        _ => panic!("Expected DataHello"),
    }
}

#[test]
fn test_pause_message_roundtrip() {
    let msg = ControlMessage::Pause {
        id: "test-456".to_string(),
    };
    let json = msg.serialize().unwrap();
    let decoded = ControlMessage::deserialize(&json).unwrap();

    match decoded {
        ControlMessage::Pause { id } => {
            assert_eq!(id, "test-456");
        }
        _ => panic!("Expected Pause"),
    }
}

#[test]
fn test_resume_message_roundtrip() {
    let msg = ControlMessage::Resume {
        id: "test-789".to_string(),
    };
    let json = msg.serialize().unwrap();
    let decoded = ControlMessage::deserialize(&json).unwrap();

    match decoded {
        ControlMessage::Resume { id } => {
            assert_eq!(id, "test-789");
        }
        _ => panic!("Expected Resume"),
    }
}

#[test]
fn test_pause_resume_capability_in_hello() {
    let client = ControlMessage::client_hello();
    let client_json = client.serialize().unwrap();
    assert!(
        client_json.contains("\"pause_resume\""),
        "client_hello should advertise pause_resume capability"
    );

    let server = ControlMessage::server_hello();
    let server_json = server.serialize().unwrap();
    assert!(
        server_json.contains("\"pause_resume\""),
        "server_hello should advertise pause_resume capability"
    );
}
