"""
AVM ISA v1 — Cross-Validated Test Harness (Python)
"""
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python'))
from avm import Instr, Prim, State, run, push_q16, push_bool, push_q0, prim, pop, dup, swap, store, load, \
    halt, jump, jump_if, make_instr, Q16_SCALE, AVM_CLAMP_MIN, AVM_CLAMP_MAX

AVM_CLAMP_MAX = 2147483647
AVM_MAX_STACK = 1024

def S(nl=0): return State(pc=0, stack=[], locals=[None]*nl, halted=False)

def test_basic_add():
    """5 + 3 = 8"""
    prog = [push_q16(5 * Q16_SCALE), push_q16(3 * Q16_SCALE), prim(Prim.ADD_SAT_Q16), halt()]
    s = run(S(), prog, 100)
    assert s.halted
    assert s.stack[0].val == 8 * Q16_SCALE, f"Got {s.stack[0].val}"
    print("  ✅ basic_add: 5 + 3 = 8")

def test_div_q16():
    """3 / 5 = 0.6 in Q16_16"""
    prog = [push_q16(3 * Q16_SCALE), push_q16(5 * Q16_SCALE), prim(Prim.DIV_SAT_Q16), halt()]
    s = run(S(), prog, 100)
    expected = (3 * Q16_SCALE) // 5  # floor(0.6 * 65536) = 39321
    assert s.stack[0].val == expected, f"Got {s.stack[0].val}, expected {expected}"
    print("  ✅ div_q16: 3/5 = 0.6")

def test_saturation():
    """max-1 + 2 → max"""
    prog = [push_q16(AVM_CLAMP_MAX - 1), push_q16(2), prim(Prim.ADD_SAT_Q16), halt()]
    s = run(S(), prog, 100)
    assert s.stack[0].val == AVM_CLAMP_MAX
    print("  ✅ saturation: max-1 + 2 = max")

def test_v6_comparison():
    cases = [(-5, -3, True), (-3, -5, False), (5, 3, False), (3, 5, True), (-1, 2, True)]
    for a, b, exp in cases:
        prog = [push_q16(a * Q16_SCALE), push_q16(b * Q16_SCALE), prim(Prim.LT_Q16), halt()]
        s = run(S(), prog, 100)
        assert s.stack[0].val == exp, f"ltQ16({a},{b}): exp {exp}, got {s.stack[0].val}"
    print("  ✅ v6_comparison: 5 cases pass")

def test_type_mismatch():
    prog = [push_bool(True), push_q16(Q16_SCALE), prim(Prim.ADD_SAT_Q16)]
    try:
        run(S(), prog, 100)
        return "❌ type_mismatch: should fail"
    except (TypeError, ValueError):
        print("  ✅ type_mismatch: rejected")

def test_stack_overflow():
    prog = [push_q16(0)] * (AVM_MAX_STACK + 1)
    try:
        run(S(), prog, 10000)
        print("  ❌ stack_overflow: should fail")
    except ValueError:
        print("  ✅ stack_overflow: rejected")

def test_division_by_zero():
    prog = [push_q16(Q16_SCALE), push_q16(0), prim(Prim.DIV_SAT_Q16)]
    try:
        run(S(), prog, 100)
        print("  ❌ div_by_zero: should fail")
    except (ValueError, ZeroDivisionError):
        print("  ✅ div_by_zero: rejected")

def test_control_flow():
    prog = [push_bool(True), jump_if(4), push_q16(0), halt(), push_q16(Q16_SCALE), halt()]
    s = run(S(), prog, 100)
    assert s.stack[0].val == Q16_SCALE
    print("  ✅ control_flow: jump_if true")

def test_locals():
    prog = [push_q16(42 * Q16_SCALE), store(0), load(0), halt()]
    s = run(S(1), prog, 100)
    assert s.stack[0].val == 42 * Q16_SCALE
    print("  ✅ locals: store+load")

def test_mul_div_rt():
    prog = [push_q16(5*Q16_SCALE), push_q16(3*Q16_SCALE), prim(Prim.MUL_SAT_Q16),
            push_q16(3*Q16_SCALE), prim(Prim.DIV_SAT_Q16), halt()]
    s = run(S(), prog, 100)
    assert s.stack[0].val == 5 * Q16_SCALE
    print("  ✅ mul_div_roundtrip: 5*3/3 = 5")

if __name__ == "__main__":
    print("AVM Python — Test Harness")
    print("=========================")
    for fn in [test_basic_add, test_div_q16, test_saturation, test_v6_comparison,
               test_type_mismatch, test_stack_overflow, test_division_by_zero,
               test_control_flow, test_locals, test_mul_div_rt]:
        fn()
    print("\nAll Python tests passed.")
