use std::collections::HashMap;

use biolib::binary_format::utils::InMemoryIndexableBuffer;
use biolib::binary_format::ModuleInput;
use biolib::binary_format::ModuleOutputV2;

#[test]
fn serialize_deserialize_empty() {
    let stdin = b"";
    let args: Vec<String> = vec![];
    let files: HashMap<String, Vec<u8>> = HashMap::new();

    let serialized = ModuleInput::serialize(stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert!(deserialized.stdin.is_empty());
    assert!(deserialized.arguments.is_empty());
    assert!(deserialized.files.is_empty());
}

#[test]
fn serialize_deserialize_with_stdin() {
    let stdin = b"hello world";
    let args: Vec<String> = vec![];
    let files: HashMap<String, Vec<u8>> = HashMap::new();

    let serialized = ModuleInput::serialize(stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert_eq!(deserialized.stdin, b"hello world");
    assert!(deserialized.arguments.is_empty());
    assert!(deserialized.files.is_empty());
}

#[test]
fn serialize_deserialize_with_args() {
    let stdin = b"";
    let args = vec!["--name".to_string(), "World".to_string()];
    let files: HashMap<String, Vec<u8>> = HashMap::new();

    let serialized = ModuleInput::serialize(stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert!(deserialized.stdin.is_empty());
    assert_eq!(deserialized.arguments, vec!["--name", "World"]);
    assert!(deserialized.files.is_empty());
}

#[test]
fn serialize_deserialize_with_files() {
    let stdin = b"";
    let args: Vec<String> = vec![];
    let mut files = HashMap::new();
    files.insert("input.txt".to_string(), b"file content".to_vec());

    let serialized = ModuleInput::serialize(stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert!(deserialized.stdin.is_empty());
    assert!(deserialized.arguments.is_empty());
    assert_eq!(deserialized.files.len(), 1);
    assert_eq!(deserialized.files["input.txt"], b"file content");
}

#[test]
fn serialize_deserialize_full() {
    let stdin = b"stdin data";
    let args = vec!["--flag".to_string(), "value".to_string()];
    let mut files = HashMap::new();
    files.insert("a.txt".to_string(), b"aaa".to_vec());
    files.insert("b.bin".to_string(), vec![0u8, 1, 2, 255]);

    let serialized = ModuleInput::serialize(stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert_eq!(deserialized.stdin, b"stdin data");
    assert_eq!(deserialized.arguments, vec!["--flag", "value"]);
    assert_eq!(deserialized.files.len(), 2);
    assert_eq!(deserialized.files["a.txt"], b"aaa");
    assert_eq!(deserialized.files["b.bin"], vec![0u8, 1, 2, 255]);
}

#[test]
fn reject_double_slash_in_file_path() {
    let stdin = b"";
    let args: Vec<String> = vec![];
    let mut files = HashMap::new();
    files.insert("path//to/file.txt".to_string(), b"data".to_vec());

    let result = ModuleInput::serialize(stdin, &args, &files);
    assert!(result.is_err());
}

#[test]
fn serialize_deserialize_binary_stdin() {
    let stdin: Vec<u8> = (0..=255).collect();
    let args: Vec<String> = vec![];
    let files: HashMap<String, Vec<u8>> = HashMap::new();

    let serialized = ModuleInput::serialize(&stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert_eq!(deserialized.stdin, stdin);
}

#[test]
fn serialize_deserialize_unicode_args() {
    let stdin = b"";
    let args = vec!["--emoji".to_string(), "hello".to_string()];
    let files: HashMap<String, Vec<u8>> = HashMap::new();

    let serialized = ModuleInput::serialize(stdin, &args, &files).unwrap();
    let deserialized = ModuleInput::deserialize(&serialized).unwrap();

    assert_eq!(deserialized.arguments[0], "--emoji");
    assert_eq!(deserialized.arguments[1], "hello");
}

#[test]
fn module_output_v2_from_bytes() {
    // Build a minimal ModuleOutputV2 binary:
    // version=1, type=11, stdout_len=5, stderr_len=3, files_info_len=0, files_data_len=0, exit_code=0
    // then stdout="hello", stderr="err"
    let mut data = Vec::new();
    data.push(1u8); // version
    data.push(11u8); // package type
    data.extend_from_slice(&5u64.to_be_bytes()); // stdout length
    data.extend_from_slice(&3u64.to_be_bytes()); // stderr length
    data.extend_from_slice(&0u64.to_be_bytes()); // files info length
    data.extend_from_slice(&0u64.to_be_bytes()); // files data length
    data.extend_from_slice(&0u16.to_be_bytes()); // exit code
    data.extend_from_slice(b"hello"); // stdout
    data.extend_from_slice(b"err"); // stderr

    let buffer = InMemoryIndexableBuffer::new(data);
    let mut output = ModuleOutputV2::new(Box::new(buffer));
    assert_eq!(output.get_exit_code().unwrap(), 0);
    assert_eq!(output.get_stdout().unwrap(), b"hello");
    assert_eq!(output.get_stderr().unwrap(), b"err");
    assert!(output.get_files().unwrap().is_empty());
}

#[test]
fn module_output_v2_with_files() {
    let mut data = Vec::new();
    data.push(1u8); // version
    data.push(11u8); // package type

    let stdout = b"out";
    let stderr = b"";
    let file_path = b"/output.txt";
    let file_data = b"file content here";

    // files_info: path_len (4 bytes) + path + data_len (8 bytes)
    let files_info_len = 4 + file_path.len() + 8;

    data.extend_from_slice(&(stdout.len() as u64).to_be_bytes());
    data.extend_from_slice(&(stderr.len() as u64).to_be_bytes());
    data.extend_from_slice(&(files_info_len as u64).to_be_bytes());
    data.extend_from_slice(&(file_data.len() as u64).to_be_bytes());
    data.extend_from_slice(&42u16.to_be_bytes()); // exit code 42
    data.extend_from_slice(stdout);
    data.extend_from_slice(stderr);

    // files info
    data.extend_from_slice(&(file_path.len() as u32).to_be_bytes());
    data.extend_from_slice(file_path);
    data.extend_from_slice(&(file_data.len() as u64).to_be_bytes());

    // files data
    data.extend_from_slice(file_data);

    let buffer = InMemoryIndexableBuffer::new(data);
    let mut output = ModuleOutputV2::new(Box::new(buffer));
    assert_eq!(output.get_exit_code().unwrap(), 42);
    assert_eq!(output.get_stdout().unwrap(), b"out");
    assert!(output.get_stderr().unwrap().is_empty());

    let files = output.get_files().unwrap();
    assert_eq!(files.len(), 1);
    assert_eq!(files[0].path, "/output.txt");

    let file_content = output.get_file_data(&files[0]).unwrap();
    assert_eq!(file_content, b"file content here");
}
