//! Unabhängige Reviewproben, 06.09.2026. Als Rust-Testbinary gegen tb-vm/tb-runtime.
use std::{path::PathBuf, rc::Rc};
use tb_runtime::{
    builtins::{builtin_table, ids, RtState},
    datetime::Zeitzone,
    host::{CaptureHost, Ereignis},
    value::Value,
};
use tb_vm::{
    bytecode::CompiledModule,
    interp::{RunEvent, Vm},
};

struct Temp(PathBuf);
impl Temp {
    fn new(name: &str) -> Self {
        let p =
            std::env::temp_dir().join(format!("tb_runtime_review_{name}_{}", std::process::id()));
        std::fs::create_dir_all(&p).unwrap();
        Self(p)
    }
    fn file(&self) -> String {
        self.0.join("data.txt").to_str().unwrap().to_owned()
    }
}
impl Drop for Temp {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}
fn text(vm: &Vm, name: &str) -> String {
    match vm.inspect(name).unwrap() {
        Value::Str(s) => s.to_string(),
        other => panic!("{other:?}"),
    }
}
fn expr(s: &str) -> String {
    let escaped = s
        .replace('"', "\"\"")
        .replace('\r', "\" + CHR$(13) + \"")
        .replace('\n', "\" + CHR$(10) + \"")
        .replace('\0', "\" + CHR$(0) + \"");
    format!("\"{escaped}\"")
}

#[test]
fn write_input_roundtrip_string_matrix() {
    let tmp = Temp::new("roundtrip");
    let p = tmp.file();
    let parts = ["", "a", " ", "\t", "\"", ",", "\r\n", "ä😀", "\0", "\u{a0}"];
    for a in parts {
        for b in parts {
            let expected = format!("{a}{b}");
            let src = format!("OPEN \"{p}\" FOR OUTPUT AS #1\nWRITE #1, {}, \"next\"\nCLOSE\nOPEN \"{p}\" FOR INPUT AS #1\nINPUT #1, got$\nINPUT #1, next$\nEND", expr(&expected));
            let mut vm = Vm::new(tb_vm::compile_source("REVIEW", &src).unwrap());
            assert_eq!(
                vm.run(&mut CaptureHost::default()),
                RunEvent::Ended,
                "{expected:?}"
            );
            assert_eq!(text(&vm, "got"), expected);
            assert_eq!(text(&vm, "next"), "next");
        }
    }
}

#[test]
fn all_nine_file_read_transitions() {
    let tmp = Temp::new("transitions");
    let p = tmp.file();
    for first in 0..3 {
        for second in 0..3 {
            std::fs::write(&p, "ä,😀\r\nZ").unwrap();
            let statement = |mode, variable| match mode {
                0 => format!("INPUT #1, {variable}$"),
                1 => format!("{variable}$ = INPUT$(1, #1)"),
                _ => format!("LINE INPUT #1, {variable}$"),
            };
            let src = format!(
                "OPEN \"{p}\" FOR INPUT AS #1\n{}\n{}\nEND",
                statement(first, "a"),
                statement(second, "b")
            );
            let mut vm = Vm::new(tb_vm::compile_source("REVIEW", &src).unwrap());
            assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
            assert_eq!(text(&vm, "a"), if first == 2 { "ä,😀" } else { "ä" });
            let expected = match (first, second) {
                (0, _) => "😀",
                (1, 0) => "",
                (1, 1) => ",",
                (1, 2) => ",😀",
                _ => "Z",
            };
            assert_eq!(text(&vm, "b"), expected, "transition {first}/{second}");
        }
    }
}

#[test]
fn every_mk_cv_pair_preserves_target_values_and_width() {
    let mut st = RtState::default();
    let mut host = CaptureHost::default();
    let cases = [
        (0, Value::Int(i16::MIN)),
        (0, Value::Int(i16::MAX)),
        (1, Value::Lng(i32::MIN)),
        (1, Value::Lng(i32::MAX)),
        (2, Value::Sng(-0.0)),
        (2, Value::Sng(f32::MAX)),
        (3, Value::Dbl(-0.0)),
        (3, Value::Dbl(f64::MIN_POSITIVE)),
        (4, Value::Cur(i64::MIN)),
        (4, Value::Cur(i64::MAX)),
    ];
    for (kind, value) in cases {
        let encoded = builtin_table()[ids::MK_S as usize](
            &mut st,
            &mut host,
            &mut [value.clone(), Value::Lng(kind)],
        )
        .unwrap()
        .unwrap();
        let decoded = builtin_table()[ids::CV_F as usize](
            &mut st,
            &mut host,
            &mut [encoded.clone(), Value::Lng(kind)],
        )
        .unwrap()
        .unwrap();
        match (&value, &decoded) {
            (Value::Int(a), Value::Int(b)) => assert_eq!(a, b),
            (Value::Lng(a), Value::Lng(b)) => assert_eq!(a, b),
            (Value::Cur(a), Value::Cur(b)) => assert_eq!(a, b),
            (Value::Sng(a), Value::Sng(b)) => assert_eq!(a.to_bits(), b.to_bits()),
            (Value::Dbl(a), Value::Dbl(b)) => assert_eq!(a.to_bits(), b.to_bits()),
            _ => panic!("{value:?} -> {decoded:?}"),
        }
        let Value::Str(encoded) = encoded else {
            panic!()
        };
        let len = encoded.chars().count();
        for wrong in [
            encoded.chars().take(len - 1).collect::<String>(),
            format!("{encoded}\0"),
        ] {
            let err = builtin_table()[ids::CV_F as usize](
                &mut st,
                &mut host,
                &mut [Value::Str(Rc::from(wrong)), Value::Lng(kind)],
            )
            .unwrap_err();
            assert_eq!(err.0, 5);
        }
    }
}

#[test]
fn timezoneknown_survives_tbc_and_source_failure_recovery() {
    thread_local! { static ZONE: std::cell::Cell<Option<i32>> = const { std::cell::Cell::new(None) }; }
    let module = tb_vm::compile_source(
        "REVIEW",
        "a% = TIMEZONEKNOWN\nSTOP\nb% = TIMEZONEKNOWN\nSTOP\nc% = TIMEZONEKNOWN\nEND",
    )
    .unwrap();
    let mut vm = Vm::new(CompiledModule::from_tbc(&module.to_tbc()).unwrap());
    vm.rt.zonen_ermittler = |_| ZONE.get();
    let mut host = CaptureHost::default();
    for (name, zone, expected) in [("a", Some(3600), -1), ("b", None, 0), ("c", Some(0), -1)] {
        ZONE.set(zone);
        let ev = vm.run(&mut host);
        assert!(matches!(ev, RunEvent::Stopped { .. } | RunEvent::Ended));
        assert!(matches!(vm.inspect(name), Some(Value::Int(n)) if n == expected));
        assert_eq!(vm.rt.zeitzone, Zeitzone::System);
        assert_eq!(vm.rt.uhr_offset, 0);
    }
}

#[test]
fn successive_function_keys_are_split_in_order() {
    let module = tb_vm::compile_source(
        "REVIEW",
        "a$ = INPUT$(3)\nb$ = INPUT$(1)\nc$ = INPUT$(1)\nEND",
    )
    .unwrap();
    let mut vm = Vm::new(module);
    let mut host = CaptureHost::default();
    for s in ["\0;", "\0<", "😀"] {
        host.ereignis(Ereignis::Taste(s.into(), 0));
    }
    assert_eq!(vm.run(&mut host), RunEvent::Ended);
    assert_eq!(text(&vm, "a"), "\0;\0");
    assert_eq!(text(&vm, "b"), "<");
    assert_eq!(text(&vm, "c"), "😀");
}
