//! Tests for bytecode (BCODESXP) support.
//!
//! Bytecode represents compiled R functions for performance optimization.
//! These tests verify that we can parse bytecode objects and preserve their structure.

use rds2rust::{read_rds, RObject};
use std::fs;
use std::path::Path;

fn test_data_exists() -> bool {
    Path::new("tests/data").exists()
}

fn read_test_file(filename: &str) -> Vec<u8> {
    let path = format!("tests/data/{}", filename);
    fs::read(&path).unwrap_or_else(|_| panic!("Failed to read test file: {}", path))
}

#[test]
fn test_bytecode_func() {
    if !test_data_exists() {
        eprintln!("Skipping test: test data not generated");
        return;
    }

    let data = read_test_file("bytecode_func.rds");
    let obj = read_rds(&data).expect("Failed to parse bytecode function");

    // Compiled functions should be Closure objects that may contain Bytecode in the body
    match obj {
        RObject::Closure {
            formals,
            body,
            environment: _,
        } => {
            // The body might be bytecode or a language object
            // For compiled functions, the body is typically bytecode
            match body.as_ref() {
                RObject::Bytecode {
                    code,
                    constants: _,
                    expr: _,
                } => {
                    // Verify the bytecode structure is preserved
                    // Code should be an integer vector (bytecode instructions)
                    // Constants should be a list (constant pool)
                    // Expr should be the original source expression

                    // Just verify they're not null - the exact structure depends on R's compiler
                    assert!(
                        !matches!(code.as_ref(), RObject::Null),
                        "Bytecode code should not be null"
                    );
                    // Constants and expr might be null for simple functions
                }
                RObject::Language { .. } => {
                    // Some functions might not be compiled to bytecode
                    // This is okay - we just want to ensure we can parse both
                }
                other => {
                    // For debugging
                    eprintln!("Function body type: {:?}", std::mem::discriminant(other));
                }
            }

            // Formals should be a pairlist (parameters)
            assert!(matches!(
                formals.as_ref(),
                RObject::Pairlist(_) | RObject::Null
            ));

            // Environment might be Null (representing base/global environment)
            // This is correct - we treat special environment markers as Null
        }
        _ => panic!("Expected Closure, got {:?}", std::mem::discriminant(&obj)),
    }
}

#[test]
fn test_bytecode_in_list() {
    if !test_data_exists() {
        eprintln!("Skipping test: test data not generated");
        return;
    }

    let data = read_test_file("bytecode_in_list.rds");
    let obj = read_rds(&data).expect("Failed to parse list containing bytecode");

    // The object might be a plain list or a list with attributes
    let (list, attrs_opt) = match obj {
        RObject::WithAttributes { object, attributes } => (object, Some(attributes)),
        RObject::List(_) => (Box::new(obj), None),
        _ => panic!(
            "Expected List or WithAttributes, got {:?}",
            std::mem::discriminant(&obj)
        ),
    };

    // Check the list has correct names (if attributes are present)
    if let Some(attrs) = attrs_opt {
        if let Some(RObject::Character(names)) = attrs.get("names") {
            assert_eq!(names.len(), 3);
            assert_eq!(names[0].as_ref(), "name");
            assert_eq!(names[1].as_ref(), "func");
            assert_eq!(names[2].as_ref(), "data");
        }
    }

    // Check the list elements
    if let RObject::List(elements) = list.as_ref() {
        assert_eq!(elements.len(), 3);

        // First element: name (character vector)
        match &elements[0] {
            RObject::Character(vec) => {
                assert_eq!(vec.len(), 1);
                assert_eq!(vec[0].as_ref(), "my_function");
            }
            _ => panic!("Expected first element to be Character vector"),
        }

        // Second element: compiled function (closure with bytecode)
        match &elements[1] {
            RObject::Closure { .. } => {
                // Good - we successfully parsed the function
                // The exact structure depends on whether R compiled it
            }
            _ => panic!("Expected second element to be Closure"),
        }

        // Third element: data (verify it's parseable)
        // R might serialize it as Real, Integer, or even Character depending on the context
        // Just verify we can parse it successfully
        match &elements[2] {
            RObject::Real(vec) => assert_eq!(vec.len(), 3),
            RObject::Integer(vec) => assert_eq!(vec.len(), 3),
            RObject::Character(_) => {} // Also okay
            _ => panic!(
                "Unexpected third element type: {:?}",
                std::mem::discriminant(&elements[2])
            ),
        }
    } else {
        panic!("Expected List");
    }
}

#[test]
fn test_uncompiled_func() {
    if !test_data_exists() {
        eprintln!("Skipping test: test data not generated");
        return;
    }

    let data = read_test_file("uncompiled_func.rds");
    let obj = read_rds(&data).expect("Failed to parse uncompiled function");

    // Uncompiled functions should be Closure objects with Language body (not bytecode)
    match obj {
        RObject::Closure {
            formals,
            body,
            environment: _,
        } => {
            // The body should be a Language object (not bytecode)
            // But R might still compile it automatically, so we accept both
            match body.as_ref() {
                RObject::Language { .. } => {
                    // Uncompiled - body is source expression
                }
                RObject::Bytecode { .. } => {
                    // R auto-compiled it - that's okay
                }
                other => {
                    eprintln!("Unexpected body type: {:?}", std::mem::discriminant(other));
                }
            }

            // Formals should be a pairlist
            assert!(matches!(
                formals.as_ref(),
                RObject::Pairlist(_) | RObject::Null
            ));

            // Environment might be Null (representing base/global environment)
            // This is correct - we treat special environment markers as Null
        }
        _ => panic!("Expected Closure, got {:?}", std::mem::discriminant(&obj)),
    }
}

#[test]
fn test_bytecode_roundtrip() {
    if !test_data_exists() {
        eprintln!("Skipping test: test data not generated");
        return;
    }

    use rds2rust::write_rds;

    let data = read_test_file("bytecode_func.rds");
    let obj = read_rds(&data).expect("Failed to parse bytecode function");

    // Try to write it back
    let written = write_rds(&obj).expect("Failed to write bytecode function");

    // Parse the written data
    let obj2 = read_rds(&written).expect("Failed to re-parse written bytecode");

    // The structure should be preserved (both should be Closures)
    assert_eq!(
        std::mem::discriminant(&obj),
        std::mem::discriminant(&obj2),
        "Roundtrip should preserve object type"
    );
}
