use calcpad_engine::context::EvalContext;
use calcpad_engine::pipeline::{eval_line, eval_sheet};
use calcpad_engine::types::ResultType;
use calcpad_engine::ffi::{FfiResponse, FfiSheetResponse};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;

// Re-declare FFI functions for testing
extern "C" {
    fn calcpad_eval_line(input: *const c_char) -> *mut c_char;
    fn calcpad_eval_sheet(lines: *const *const c_char, count: i32) -> *mut c_char;
    fn calcpad_free_result(ptr: *mut c_char);
}

// ===== Pipeline tests =====

#[test]
fn pipeline_eval_simple_addition() {
    let mut ctx = EvalContext::new();
    let result = eval_line("2 + 3", &mut ctx);
    assert_eq!(result.result_type(), ResultType::Number);
    assert_eq!(result.metadata.display, "5");
    assert_eq!(result.metadata.raw_value, Some(5.0));
}

#[test]
fn pipeline_eval_multiplication() {
    let mut ctx = EvalContext::new();
    let result = eval_line("6 * 7", &mut ctx);
    assert_eq!(result.metadata.display, "42");
}

#[test]
fn pipeline_eval_division() {
    let mut ctx = EvalContext::new();
    let result = eval_line("10 / 4", &mut ctx);
    assert_eq!(result.metadata.raw_value, Some(2.5));
}

#[test]
fn pipeline_eval_exponentiation() {
    let mut ctx = EvalContext::new();
    let result = eval_line("2 ^ 10", &mut ctx);
    assert_eq!(result.metadata.display, "1024");
}

#[test]
fn pipeline_eval_parentheses() {
    let mut ctx = EvalContext::new();
    let result = eval_line("(2 + 3) * 4", &mut ctx);
    assert_eq!(result.metadata.display, "20");
}

#[test]
fn pipeline_eval_unary_neg() {
    let mut ctx = EvalContext::new();
    let result = eval_line("-5 + 3", &mut ctx);
    assert_eq!(result.metadata.display, "-2");
}

#[test]
fn pipeline_eval_percentage() {
    let mut ctx = EvalContext::new();
    let result = eval_line("100 - 20%", &mut ctx);
    assert_eq!(result.metadata.display, "80");
}

#[test]
fn pipeline_eval_unit_number() {
    let mut ctx = EvalContext::new();
    let result = eval_line("5kg", &mut ctx);
    assert_eq!(result.result_type(), ResultType::UnitValue);
    assert_eq!(result.metadata.display, "5 kg");
}

#[test]
fn pipeline_eval_currency() {
    let mut ctx = EvalContext::new();
    let result = eval_line("$20", &mut ctx);
    assert_eq!(result.result_type(), ResultType::CurrencyValue);
    assert!(result.metadata.display.contains("20"));
}

#[test]
fn pipeline_eval_comparison() {
    let mut ctx = EvalContext::new();
    let result = eval_line("5 > 3", &mut ctx);
    assert_eq!(result.result_type(), ResultType::Boolean);
    assert_eq!(result.metadata.display, "true");
}

#[test]
fn pipeline_eval_division_by_zero() {
    let mut ctx = EvalContext::new();
    let result = eval_line("10 / 0", &mut ctx);
    assert_eq!(result.result_type(), ResultType::Error);
}

#[test]
fn pipeline_eval_empty_input() {
    let mut ctx = EvalContext::new();
    let result = eval_line("", &mut ctx);
    assert_eq!(result.result_type(), ResultType::Empty);
}

#[test]
fn pipeline_eval_comment_only() {
    let mut ctx = EvalContext::new();
    let result = eval_line("// this is a comment", &mut ctx);
    assert_eq!(result.result_type(), ResultType::NonCalculable);
}

// ===== Variable assignment tests (eval_sheet) =====

#[test]
fn pipeline_eval_sheet_basic() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["2 + 3", "10 * 2"], &mut ctx);
    assert_eq!(results.len(), 2);
    assert_eq!(results[0].metadata.display, "5");
    assert_eq!(results[1].metadata.display, "20");
}

#[test]
fn pipeline_eval_sheet_variable_assignment() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["x = 5", "x + 3"], &mut ctx);
    assert_eq!(results.len(), 2);
    assert_eq!(results[0].metadata.display, "5");
    assert_eq!(results[1].metadata.display, "8");
}

#[test]
fn pipeline_eval_sheet_multiple_variables() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["a = 10", "b = 20", "a + b"], &mut ctx);
    assert_eq!(results.len(), 3);
    assert_eq!(results[0].metadata.display, "10");
    assert_eq!(results[1].metadata.display, "20");
    assert_eq!(results[2].metadata.display, "30");
}

#[test]
fn pipeline_eval_sheet_variable_reassignment() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["x = 5", "x = 10", "x"], &mut ctx);
    assert_eq!(results.len(), 3);
    assert_eq!(results[2].metadata.display, "10");
}

#[test]
fn pipeline_eval_sheet_undefined_variable() {
    let mut ctx = EvalContext::new();
    let results = eval_sheet(&["y + 3"], &mut ctx);
    assert_eq!(results[0].result_type(), ResultType::Error);
}

// ===== FFI function tests =====

#[test]
fn ffi_eval_line_basic() {
    let input = CString::new("2 + 3").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        let response: FfiResponse = serde_json::from_str(result_str).unwrap();

        assert_eq!(response.schema_version, "1.0");
        assert_eq!(response.result.result_type(), ResultType::Number);
        assert_eq!(response.result.metadata.display, "5");
        assert_eq!(response.result.metadata.raw_value, Some(5.0));

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn ffi_eval_line_complex_expression() {
    let input = CString::new("(10 + 5) * 2").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        let response: FfiResponse = serde_json::from_str(result_str).unwrap();

        assert_eq!(response.result.metadata.display, "30");

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn ffi_eval_line_null_input() {
    unsafe {
        let result_ptr = calcpad_eval_line(ptr::null());
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        let response: FfiResponse = serde_json::from_str(result_str).unwrap();

        assert_eq!(response.result.result_type(), ResultType::Error);

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn ffi_eval_sheet_basic() {
    let line1 = CString::new("2 + 3").unwrap();
    let line2 = CString::new("10 * 2").unwrap();
    let lines: Vec<*const c_char> = vec![line1.as_ptr(), line2.as_ptr()];

    unsafe {
        let result_ptr = calcpad_eval_sheet(lines.as_ptr(), 2);
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        let response: FfiSheetResponse = serde_json::from_str(result_str).unwrap();

        assert_eq!(response.schema_version, "1.0");
        assert_eq!(response.results.len(), 2);
        assert_eq!(response.results[0].metadata.display, "5");
        assert_eq!(response.results[1].metadata.display, "20");

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn ffi_eval_sheet_with_variables() {
    let line1 = CString::new("x = 5").unwrap();
    let line2 = CString::new("x + 10").unwrap();
    let lines: Vec<*const c_char> = vec![line1.as_ptr(), line2.as_ptr()];

    unsafe {
        let result_ptr = calcpad_eval_sheet(lines.as_ptr(), 2);
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        let response: FfiSheetResponse = serde_json::from_str(result_str).unwrap();

        assert_eq!(response.results.len(), 2);
        assert_eq!(response.results[0].metadata.display, "5");
        assert_eq!(response.results[1].metadata.display, "15");

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn ffi_eval_sheet_null_lines() {
    unsafe {
        let result_ptr = calcpad_eval_sheet(ptr::null(), 0);
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        let response: FfiResponse = serde_json::from_str(result_str).unwrap();

        assert_eq!(response.result.result_type(), ResultType::Error);

        calcpad_free_result(result_ptr);
    }
}

// ===== Panic safety tests =====

#[test]
fn ffi_panic_safety_eval_line() {
    // The catch_unwind in eval_line should handle any internal panics.
    // Test with various edge cases that might trigger unexpected behavior.
    let input = CString::new("2 + 3").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        assert!(!result_ptr.is_null());
        calcpad_free_result(result_ptr);
    }
}

#[test]
fn ffi_panic_safety_null_input() {
    // Null input should not crash — should return error JSON
    unsafe {
        let result_ptr = calcpad_eval_line(ptr::null());
        assert!(!result_ptr.is_null());

        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();
        assert!(result_str.contains("error") || result_str.contains("Error"));

        calcpad_free_result(result_ptr);
    }
}

// ===== Memory management tests =====

#[test]
fn ffi_free_result_null() {
    // Freeing null should be a safe no-op
    unsafe {
        calcpad_free_result(ptr::null_mut());
    }
    // If we get here without crashing, the test passes
}

#[test]
fn ffi_free_result_valid() {
    let input = CString::new("42").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        assert!(!result_ptr.is_null());
        // Free the result — should not crash
        calcpad_free_result(result_ptr);
    }
    // If we get here without crashing, the test passes
}

#[test]
fn ffi_multiple_allocations() {
    // Allocate and free multiple results to check for memory leaks
    for _ in 0..100 {
        let input = CString::new("1 + 1").unwrap();
        unsafe {
            let result_ptr = calcpad_eval_line(input.as_ptr());
            assert!(!result_ptr.is_null());
            calcpad_free_result(result_ptr);
        }
    }
}

// ===== JSON schema versioning tests =====

#[test]
fn json_schema_version_present() {
    let input = CString::new("42").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();

        let json: serde_json::Value = serde_json::from_str(result_str).unwrap();
        assert_eq!(json["schema_version"], "1.0");

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn json_schema_contains_required_fields() {
    let input = CString::new("42").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();

        let json: serde_json::Value = serde_json::from_str(result_str).unwrap();

        // Check schema_version
        assert!(json["schema_version"].is_string());

        // Check result structure
        assert!(json["result"]["value"].is_object());
        assert!(json["result"]["metadata"].is_object());

        // Check metadata fields
        let metadata = &json["result"]["metadata"];
        assert!(metadata["result_type"].is_string());
        assert!(metadata["display"].is_string());
        assert!(metadata["span"].is_object());
        assert!(metadata["span"]["start"].is_number());
        assert!(metadata["span"]["end"].is_number());

        // Check raw_value is present (can be number or null)
        assert!(
            metadata["raw_value"].is_number() || metadata["raw_value"].is_null(),
            "raw_value should be a number or null"
        );

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn json_schema_result_type_field() {
    let input = CString::new("42").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();

        let json: serde_json::Value = serde_json::from_str(result_str).unwrap();
        assert_eq!(json["result"]["metadata"]["result_type"], "Number");
        assert_eq!(json["result"]["value"]["kind"], "Number");

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn json_schema_error_result() {
    let input = CString::new("10 / 0").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();

        let json: serde_json::Value = serde_json::from_str(result_str).unwrap();
        assert_eq!(json["result"]["metadata"]["result_type"], "Error");
        assert_eq!(json["result"]["value"]["kind"], "Error");
        assert!(json["result"]["value"]["message"].is_string());

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn json_schema_sheet_version() {
    let line1 = CString::new("1 + 1").unwrap();
    let lines: Vec<*const c_char> = vec![line1.as_ptr()];

    unsafe {
        let result_ptr = calcpad_eval_sheet(lines.as_ptr(), 1);
        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();

        let json: serde_json::Value = serde_json::from_str(result_str).unwrap();
        assert_eq!(json["schema_version"], "1.0");
        assert!(json["results"].is_array());
        assert_eq!(json["results"].as_array().unwrap().len(), 1);

        calcpad_free_result(result_ptr);
    }
}

#[test]
fn json_display_value_and_raw_value() {
    let input = CString::new("2.5 * 4").unwrap();
    unsafe {
        let result_ptr = calcpad_eval_line(input.as_ptr());
        let result_str = CStr::from_ptr(result_ptr).to_str().unwrap();

        let json: serde_json::Value = serde_json::from_str(result_str).unwrap();
        assert_eq!(json["result"]["metadata"]["display"], "10");
        assert_eq!(json["result"]["metadata"]["raw_value"], 10.0);

        calcpad_free_result(result_ptr);
    }
}
