use serde_json::json;
use shelly::{
    validate_client_message_invariants, validate_server_message_invariants,
    validate_server_message_sequence, ClientMessage, Event, ServerMessage,
};
use std::{fs, path::PathBuf};

fn corpus_paths(group: &str) -> Vec<PathBuf> {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("..")
        .join("..")
        .join("fuzz")
        .join("corpus")
        .join(group);
    let mut entries = fs::read_dir(&root)
        .unwrap_or_else(|err| panic!("failed to read corpus {}: {err}", root.display()))
        .map(|entry| entry.expect("corpus entry").path())
        .collect::<Vec<_>>();
    entries.sort();
    entries
}

#[test]
fn protocol_message_corpus_is_parseable_and_exercises_invariants() {
    let files = corpus_paths("protocol_message");
    assert!(!files.is_empty(), "protocol fuzz corpus must not be empty");

    for path in files {
        let source = fs::read_to_string(&path)
            .unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display()));
        let parsed_value: serde_json::Value = serde_json::from_str(&source)
            .unwrap_or_else(|err| panic!("invalid JSON corpus {}: {err}", path.display()));

        let mut exercised_message_path = false;
        if let Ok(message) = serde_json::from_str::<ClientMessage>(&source) {
            let _ = validate_client_message_invariants(&message);
            exercised_message_path = true;
        }
        if let Ok(message) = serde_json::from_str::<ServerMessage>(&source) {
            let _ = validate_server_message_invariants(&message);
            exercised_message_path = true;
        }
        if let Ok(messages) = serde_json::from_str::<Vec<ServerMessage>>(&source) {
            let _ = validate_server_message_sequence(&messages);
            exercised_message_path = true;
        }

        assert!(
            exercised_message_path || parsed_value.is_object() || parsed_value.is_array(),
            "protocol corpus must exercise message decode or include structured JSON: {}",
            path.display()
        );
    }
}

#[test]
fn browser_payload_corpus_exercises_event_conversion_paths() {
    let files = corpus_paths("browser_event_payload");
    assert!(
        !files.is_empty(),
        "browser payload corpus must not be empty"
    );

    for path in files {
        let source = fs::read_to_string(&path)
            .unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display()));
        let value: serde_json::Value = serde_json::from_str(&source)
            .unwrap_or_else(|err| panic!("invalid JSON corpus {}: {err}", path.display()));
        let wrapped = json!({
            "type": "event",
            "event": "browser_event",
            "target": "corpus-target",
            "value": value,
            "metadata": {"tag": "FUZZ"}
        });
        let message: ClientMessage = serde_json::from_value(wrapped).unwrap_or_else(|err| {
            panic!("failed to decode wrapped corpus {}: {err}", path.display())
        });
        let _ = validate_client_message_invariants(&message);
        let _ = Event::try_from(message);
    }
}
