#!/usr/bin/env python3
"""
Float safety tests (NaN/Inf/denormal) - 2025-11-01

Ensures ANCHOR gain guards against non-finite values (NaN/Inf) that would
poison dot-product sums.
"""

import math
import struct
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from kernels.dot_product_code import dot_product_code
from sdk.code_iter import FILL8, ANCHOR, END, CodeIter

def build_bytes(segments):
    """Build bytecode from segment list."""
    b = bytearray()
    for seg in segments:
        op = seg[0].upper()
        if op == "FILL8":
            _, ln, val = seg
            b.append(FILL8)
            b += struct.pack("<Q", int(ln))
            b.append(int(val) & 0xFF)
        elif op == "ANCHOR":
            _, aid, gain = seg
            b.append(ANCHOR)
            b += struct.pack("<I", int(aid))
            b += struct.pack("<f", float(gain))
        elif op == "END":
            b.append(END)
        else:
            raise ValueError(f"unknown op {op}")
    if not b or b[-1] != END:
        b.append(END)
    return bytes(b)

def v_ones(anchor_id: int, anchor_gain: float, virt_off: int, length: int) -> float:
    """Return contribution when every element = 1.0."""
    return float(length)

def test_anchor_gain_nan_rejected():
    """ANCHOR with NaN gain must raise ValueError with ERR_GAIN_NAN_INF tag."""
    prog = build_bytes([
        ("ANCHOR", 1, float('nan')),
        ("FILL8", 100, 7),
        ("END",),
    ])

    try:
        _ = list(CodeIter(prog))
        assert False, "Should have raised ValueError for NaN gain"
    except ValueError as e:
        assert "ERR_GAIN_NAN_INF" in str(e), f"Wrong error: {e}"
        assert "nan" in str(e).lower() or "finite" in str(e).lower()

def test_anchor_gain_inf_rejected():
    """ANCHOR with +Inf gain must raise ValueError with ERR_GAIN_NAN_INF tag."""
    prog = build_bytes([
        ("ANCHOR", 1, float('inf')),
        ("FILL8", 100, 7),
        ("END",),
    ])

    try:
        _ = list(CodeIter(prog))
        assert False, "Should have raised ValueError for +Inf gain"
    except ValueError as e:
        assert "ERR_GAIN_NAN_INF" in str(e), f"Wrong error: {e}"

def test_anchor_gain_neginf_rejected():
    """ANCHOR with -Inf gain must raise ValueError with ERR_GAIN_NAN_INF tag."""
    prog = build_bytes([
        ("ANCHOR", 1, float('-inf')),
        ("FILL8", 100, 7),
        ("END",),
    ])

    try:
        _ = list(CodeIter(prog))
        assert False, "Should have raised ValueError for -Inf gain"
    except ValueError as e:
        assert "ERR_GAIN_NAN_INF" in str(e), f"Wrong error: {e}"

def test_anchor_gain_zero_ok():
    """ANCHOR with gain=0.0 is finite and should succeed."""
    prog = build_bytes([
        ("ANCHOR", 1, 0.0),
        ("FILL8", 100, 7),
        ("END",),
    ])

    # Should not raise
    _ = list(CodeIter(prog))
    res = dot_product_code(prog, lambda aid, gain, off, ln: float(ln) * gain)
    assert res == 0.0  # 100 × 0.0 = 0.0

def test_anchor_gain_denormal_ok():
    """ANCHOR with subnormal (denormal) gain should be allowed (finite)."""
    # Smallest positive f32 subnormal: ~1.4e-45
    denormal = 1.0e-40  # Well into subnormal range for f32

    prog = build_bytes([
        ("ANCHOR", 1, denormal),
        ("FILL8", 10, 3),
        ("END",),
    ])

    # Should not raise (denormal is finite)
    _ = list(CodeIter(prog))
    res = dot_product_code(prog, lambda aid, gain, off, ln: float(ln) * gain)

    # Result should be very small but finite
    assert math.isfinite(res)
    assert res > 0.0

def test_anchor_gain_large_finite_ok():
    """ANCHOR with large but finite gain should succeed."""
    large_gain = 1.0e30  # Large but well below f32 max (~3.4e38)

    prog = build_bytes([
        ("ANCHOR", 1, large_gain),
        ("FILL8", 10, 5),
        ("END",),
    ])

    # Should not raise
    _ = list(CodeIter(prog))
    res = dot_product_code(prog, lambda aid, gain, off, ln: float(ln) * gain)

    # Result should be finite (10 × 1e30 = 1e31)
    assert math.isfinite(res)
    assert res > 0.0

def test_anchor_gain_negative_finite_ok():
    """ANCHOR with negative finite gain should succeed."""
    prog = build_bytes([
        ("ANCHOR", 1, -3.5),
        ("FILL8", 10, 7),
        ("END",),
    ])

    # Should not raise
    _ = list(CodeIter(prog))
    res = dot_product_code(prog, lambda aid, gain, off, ln: float(ln) * gain)

    # Result should be negative and finite
    assert math.isfinite(res)
    assert res < 0.0
