use serde_json::json;
use shelly::{
    validate_client_message_invariants, validate_server_message_invariants,
    validate_server_message_sequence, ClientMessage, ServerMessage, PROTOCOL_VERSION_V1,
    SUPPORTED_PROTOCOL_VERSIONS,
};

struct MatrixCase<'a, T> {
    label: &'a str,
    message: T,
    expected_error: Option<&'a str>,
}

#[test]
fn client_connect_protocol_version_matrix() {
    let cases = vec![
        MatrixCase {
            label: "supported_current_version",
            message: serde_json::from_value::<ClientMessage>(json!({
                "type": "connect",
                "protocol": PROTOCOL_VERSION_V1
            }))
            .unwrap(),
            expected_error: None,
        },
        MatrixCase {
            label: "unsupported_legacy_version",
            message: serde_json::from_value::<ClientMessage>(json!({
                "type": "connect",
                "protocol": "shelly/0"
            }))
            .unwrap(),
            expected_error: Some("unsupported_protocol_version"),
        },
        MatrixCase {
            label: "unsupported_future_version",
            message: serde_json::from_value::<ClientMessage>(json!({
                "type": "connect",
                "protocol": "shelly/99"
            }))
            .unwrap(),
            expected_error: Some("unsupported_protocol_version"),
        },
    ];

    for case in cases {
        let result = validate_client_message_invariants(&case.message);
        match case.expected_error {
            None => assert!(
                result.is_ok(),
                "matrix case '{}' should pass, got {:?}",
                case.label,
                result.err()
            ),
            Some(expected_code) => {
                let err = result.expect_err("matrix case should fail");
                assert_eq!(
                    err.code, expected_code,
                    "matrix case '{}' emitted unexpected code",
                    case.label
                );
            }
        }
    }
}

#[test]
fn hello_fallback_path_matrix() {
    let cases = vec![
        MatrixCase {
            label: "fresh_without_reason",
            message: serde_json::from_value::<ServerMessage>(json!({
                "type": "hello",
                "session_id": "sid-1",
                "target": "root",
                "revision": 0,
                "protocol": PROTOCOL_VERSION_V1,
                "resume_status": "fresh"
            }))
            .unwrap(),
            expected_error: None,
        },
        MatrixCase {
            label: "fallback_with_reason",
            message: serde_json::from_value::<ServerMessage>(json!({
                "type": "hello",
                "session_id": "sid-2",
                "target": "root",
                "revision": 4,
                "server_revision": 7,
                "protocol": PROTOCOL_VERSION_V1,
                "resume_status": "fallback",
                "resume_reason": "resume_token_stale"
            }))
            .unwrap(),
            expected_error: None,
        },
        MatrixCase {
            label: "fallback_missing_reason",
            message: serde_json::from_value::<ServerMessage>(json!({
                "type": "hello",
                "session_id": "sid-3",
                "target": "root",
                "revision": 1,
                "protocol": PROTOCOL_VERSION_V1,
                "resume_status": "fallback"
            }))
            .unwrap(),
            expected_error: Some("missing_resume_reason"),
        },
        MatrixCase {
            label: "server_revision_regression",
            message: serde_json::from_value::<ServerMessage>(json!({
                "type": "hello",
                "session_id": "sid-4",
                "target": "root",
                "revision": 9,
                "server_revision": 8,
                "protocol": PROTOCOL_VERSION_V1
            }))
            .unwrap(),
            expected_error: Some("server_revision_regression"),
        },
    ];

    for case in cases {
        let result = validate_server_message_invariants(&case.message);
        match case.expected_error {
            None => assert!(
                result.is_ok(),
                "matrix case '{}' should pass, got {:?}",
                case.label,
                result.err()
            ),
            Some(expected_code) => {
                let err = result.expect_err("matrix case should fail");
                assert_eq!(
                    err.code, expected_code,
                    "matrix case '{}' emitted unexpected code",
                    case.label
                );
            }
        }
    }
}

#[test]
fn sequence_ordering_matrix() {
    let valid = vec![
        serde_json::from_value::<ServerMessage>(json!({
            "type": "hello",
            "session_id": "sid-ok",
            "target": "root",
            "revision": 0,
            "protocol": PROTOCOL_VERSION_V1
        }))
        .unwrap(),
        serde_json::from_value::<ServerMessage>(json!({
            "type": "patch",
            "target": "root",
            "html": "<p>0</p>",
            "revision": 1
        }))
        .unwrap(),
        serde_json::from_value::<ServerMessage>(json!({
            "type": "diff",
            "target": "root",
            "revision": 2,
            "slots": [{"index": 0, "html": "1"}]
        }))
        .unwrap(),
    ];
    assert!(validate_server_message_sequence(&valid).is_ok());

    let hello_not_first = vec![
        serde_json::from_value::<ServerMessage>(json!({
            "type": "patch",
            "target": "root",
            "html": "<p>0</p>",
            "revision": 1
        }))
        .unwrap(),
        serde_json::from_value::<ServerMessage>(json!({
            "type": "hello",
            "session_id": "sid-bad-order",
            "target": "root",
            "revision": 1,
            "protocol": PROTOCOL_VERSION_V1
        }))
        .unwrap(),
    ];
    let err = validate_server_message_sequence(&hello_not_first).expect_err("must fail");
    assert_eq!(err.code, "hello_not_first");

    let non_monotonic = vec![
        serde_json::from_value::<ServerMessage>(json!({
            "type": "hello",
            "session_id": "sid-bad-rev",
            "target": "root",
            "revision": 0,
            "protocol": PROTOCOL_VERSION_V1
        }))
        .unwrap(),
        serde_json::from_value::<ServerMessage>(json!({
            "type": "patch",
            "target": "root",
            "html": "<p>0</p>",
            "revision": 5
        }))
        .unwrap(),
        serde_json::from_value::<ServerMessage>(json!({
            "type": "diff",
            "target": "root",
            "revision": 5,
            "slots": [{"index": 0, "html": "1"}]
        }))
        .unwrap(),
    ];
    let err = validate_server_message_sequence(&non_monotonic).expect_err("must fail");
    assert_eq!(err.code, "non_monotonic_revision");
}

#[test]
fn supported_versions_constant_is_consistent() {
    assert!(
        SUPPORTED_PROTOCOL_VERSIONS.contains(&PROTOCOL_VERSION_V1),
        "supported versions should include the current protocol constant"
    );
    let mut dedup = SUPPORTED_PROTOCOL_VERSIONS.to_vec();
    dedup.sort_unstable();
    dedup.dedup();
    assert_eq!(
        dedup.len(),
        SUPPORTED_PROTOCOL_VERSIONS.len(),
        "supported protocol versions must not contain duplicates"
    );
}
