#!/usr/bin/env python3
"""
CWC COPY v1 - Surgical edge case coverage (2025-11-01)

Covers:
1. Anchor semantics (CURRENT anchor behavior)
2. XOR × COPY strictness (iterator discipline)
3. Deep COPY chains (O(1) memory, O(n_ops) time)
4. Offset & bounds hardening (edge cases, u64 wrap)
5. Float determinism (gain roundtrip, numeric stability)
6. EMIT_GUARD bypass protection
7. Linearity proof (CWC math validity)
"""

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, COPY, XOR8, ANCHOR, END, CodeIter
import sdk.code_iter as CI

# ────────────────────────────────────────────────────────────────────────────────
# Helper: build programs from segments
# ────────────────────────────────────────────────────────────────────────────────

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 == "COPY":
            _, src_off, ln = seg
            b.append(COPY)
            b += struct.pack("<Q", int(src_off))
            b += struct.pack("<Q", int(ln))
        elif op == "XOR8":
            _, ln, key = seg
            b.append(XOR8)
            b += struct.pack("<Q", int(ln))
            b.append(int(key) & 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)

# Simple v_accessor for tests
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 v_gain(anchor_id: int, anchor_gain: float, virt_off: int, length: int) -> float:
    """Return contribution when every element = anchor_gain."""
    return float(length) * float(anchor_gain)

# ────────────────────────────────────────────────────────────────────────────────
# 1. Anchor semantics corner cases
# ────────────────────────────────────────────────────────────────────────────────

def test_copy_same_anchor_equals_fill():
    """COPY under same anchor should equal double FILL8."""
    prog = build_bytes([
        ("ANCHOR", 1, 2.0),
        ("FILL8", 100, 7),
        ("COPY", 0, 100),
        ("END",),
    ])
    res = dot_product_code(prog, v_ones)
    # 100 (FILL8) + 100 (COPY) = 200
    assert res == 200.0

def test_copy_cross_anchor_differs():
    """COPY across anchors uses CURRENT anchor gain."""
    prog = build_bytes([
        ("ANCHOR", 1, 2.0),
        ("FILL8", 100, 7),
        ("ANCHOR", 2, 5.0),
        ("COPY", 0, 100),
        ("END",),
    ])
    res = dot_product_code(prog, v_gain)
    # FILL8: 100 × 2.0 = 200
    # COPY:  100 × 5.0 = 500 (CURRENT anchor g=5.0)
    # Total: 700
    assert res == 700.0

def test_anchor_after_copy_no_backprop():
    """ANCHOR after COPY doesn't affect prior COPY contribution."""
    prog = build_bytes([
        ("FILL8", 50, 3),
        ("COPY", 0, 50),
        ("ANCHOR", 3, 99.0),  # Non-generating, doesn't consume anything
        ("END",),
    ])
    res = dot_product_code(prog, v_ones)
    # FILL8: 50, COPY: 50, ANCHOR: 0 (non-generating)
    assert res == 100.0

# ────────────────────────────────────────────────────────────────────────────────
# 2. XOR × COPY strictness
# ────────────────────────────────────────────────────────────────────────────────

def test_xor_len0_copy_len0_ok():
    """XOR8(0) → COPY(0) should consume and pass (zero-length edge)."""
    prog = build_bytes([
        ("FILL8", 10, 9),
        ("XOR8", 0, 0x2A),
        ("COPY", 0, 0),
        ("END",),
    ])
    _ = list(CodeIter(prog))  # Should not raise
    res = dot_product_code(prog, v_ones)
    assert res == 10.0  # Only FILL8 contributes

def test_xor_pending_then_anchor_then_copy_ok():
    """XOR → ANCHOR → COPY with matching length should succeed."""
    prog = build_bytes([
        ("FILL8", 50, 7),
        ("XOR8", 50, 0x3B),
        ("ANCHOR", 2, 3.0),  # Non-generating, doesn't consume XOR
        ("COPY", 0, 50),      # Consumes XOR (length matches)
        ("END",),
    ])
    _ = list(CodeIter(prog))
    res = dot_product_code(prog, v_gain)
    # FILL8: 50 × 1.0 = 50 (default anchor g=1.0)
    # COPY:  50 × 3.0 = 150 (CURRENT anchor g=3.0)
    assert res == 200.0

def test_xor_pending_copy_wrong_len_rejects():
    """XOR(100) → COPY(50) must reject (length mismatch)."""
    prog = build_bytes([
        ("FILL8", 100, 7),
        ("XOR8", 100, 0x2A),
        ("COPY", 0, 50),  # Mismatch!
        ("END",),
    ])
    try:
        _ = list(CodeIter(prog))
        assert False, "Should have raised ValueError"
    except ValueError as e:
        assert "xor" in str(e).lower() or "!=" in str(e)

# ────────────────────────────────────────────────────────────────────────────────
# 3. Deep COPY chains (O(1) memory, O(n_ops) time)
# ────────────────────────────────────────────────────────────────────────────────

def test_deep_copy_chain_no_materialization():
    """Chain of 100 COPYs should complete without materialization."""
    import resource

    # Build program: FILL8(100) + 99 consecutive COPYs
    segs = [("FILL8", 100, 7)]
    for i in range(99):
        segs.append(("COPY", i * 100, 100))
    segs.append(("END",))

    prog = build_bytes(segs)

    rss_before = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    res = dot_product_code(prog, v_ones)
    rss_after = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

    # Expected: 100 ops × 100 bytes = 10,000 contribution
    assert res == 10_000.0

    # Memory drift should be < 5 MB
    rss_delta_kb = abs(rss_after - rss_before)
    assert rss_delta_kb < 5_000  # 5 MB threshold

# Property test with Hypothesis (if installed)
try:
    from hypothesis import given, strategies as st, settings

    @given(
        n_copies=st.integers(min_value=1, max_value=1000),
        base_len=st.integers(min_value=1, max_value=100)
    )
    @settings(max_examples=50, deadline=5000)
    def test_copy_chain_property(n_copies, base_len):
        """Property: N COPYs of length L → (N+1) × L contribution."""
        segs = [("FILL8", base_len, 3)]
        for i in range(n_copies):
            segs.append(("COPY", i * base_len, base_len))
        segs.append(("END",))

        prog = build_bytes(segs)
        res = dot_product_code(prog, v_ones)

        expected = (n_copies + 1) * base_len
        assert res == float(expected)

except ImportError:
    # Hypothesis not installed, skip property test
    pass

# ────────────────────────────────────────────────────────────────────────────────
# 4. Offset & bounds hardening
# ────────────────────────────────────────────────────────────────────────────────

def test_copy_src_off_exact_tail_ok():
    """src_off + len == virt_off at encounter time (edge of window)."""
    prog = build_bytes([
        ("FILL8", 64, 1),
        ("COPY", 32, 32),  # 32 + 32 == 64 (exact tail)
        ("END",),
    ])
    _ = list(CodeIter(prog))
    res = dot_product_code(prog, v_ones)
    assert res == 96.0  # 64 + 32

def test_copy_src_off_u64_max_rejected():
    """u64 near overflow must reject via forward-ref guard."""
    # Manually craft bytecode with src_off = 2^64-1
    b = bytearray()
    b.append(FILL8)
    b += struct.pack("<Q", 100)
    b.append(7)
    b.append(COPY)
    b += struct.pack("<Q", (2**64 - 1))  # src_off = u64 max
    b += struct.pack("<Q", 100)           # length
    b.append(END)

    prog = bytes(b)

    try:
        dot_product_code(prog, v_ones)
        assert False, "Should have raised ValueError for forward reference"
    except ValueError as e:
        assert "forward" in str(e).lower()

def test_copy_negative_offset_rejected():
    """Negative src_off (serialized as large u64) must reject."""
    # Serialize -1 as u64 (wraps to 2^64-1)
    b = bytearray()
    b.append(FILL8)
    b += struct.pack("<Q", 50)
    b.append(3)
    b.append(COPY)
    b += struct.pack("<q", -1)  # Signed -1 (but read as u64)
    b += struct.pack("<Q", 10)
    b.append(END)

    prog = bytes(b)

    try:
        dot_product_code(prog, v_ones)
        assert False, "Should have raised ValueError"
    except ValueError:
        pass  # Expected

# ────────────────────────────────────────────────────────────────────────────────
# 5. Float determinism & numeric stability
# ────────────────────────────────────────────────────────────────────────────────

def test_gain_roundtrip_exact():
    """Serialize→deserialize gain matches kernel use."""
    gain = 3.14159
    gain_f32 = struct.unpack('<f', struct.pack('<f', gain))[0]

    prog = build_bytes([
        ("ANCHOR", 1, gain),
        ("FILL8", 1000, 5),
        ("END",),
    ])

    res = dot_product_code(prog, v_gain)
    expected = 1000.0 * gain_f32
    assert abs(res - expected) < 1e-6

def test_large_len_small_gain():
    """Large length × small gain should be exact (no catastrophic cancellation)."""
    prog = build_bytes([
        ("ANCHOR", 1, 1e-7),
        ("FILL8", 10_000_000, 9),
        ("END",),
    ])

    res = dot_product_code(prog, v_gain)
    # 1e7 × 1e-7 = 1.0 (but in f32 precision)
    gain_f32 = struct.unpack('<f', struct.pack('<f', 1e-7))[0]
    expected = 10_000_000.0 * gain_f32

    # Allow small relative error for f32
    assert abs(res - expected) / expected < 1e-5

def test_gain_zero_ok():
    """gain=0.0 should produce 0.0 contribution (edge case)."""
    prog = build_bytes([
        ("ANCHOR", 1, 0.0),
        ("FILL8", 1000, 7),
        ("END",),
    ])

    res = dot_product_code(prog, v_gain)
    assert res == 0.0

# ────────────────────────────────────────────────────────────────────────────────
# 6. EMIT_GUARD bypass protection
# ────────────────────────────────────────────────────────────────────────────────

def test_emit_guard_false_raises():
    """Setting EMIT_GUARD_ENABLED=False must raise RuntimeError."""
    prog = build_bytes([("FILL8", 10, 3), ("END",)])

    # Save original state
    original_guard = getattr(CI, "EMIT_GUARD_ENABLED", True)

    try:
        # Disable guard
        CI.EMIT_GUARD_ENABLED = False

        # Should raise before walking ops
        try:
            dot_product_code(prog, v_ones)
            assert False, "Should have raised RuntimeError"
        except RuntimeError as e:
            assert "EMIT_GUARD_ENABLED" in str(e) or "security" in str(e).lower()
    finally:
        # Restore guard
        CI.EMIT_GUARD_ENABLED = original_guard

# ────────────────────────────────────────────────────────────────────────────────
# 7. Linearity proof (CWC math validity)
# ────────────────────────────────────────────────────────────────────────────────

def test_dot_linearity_additive():
    """Prove dot(P ⊕ Q) == dot(P) + dot(Q)."""
    # Program P: FILL8(100)
    p_bytes = build_bytes([("FILL8", 100, 7), ("END",)])

    # Program Q: ANCHOR(g=2.0) + FILL8(50)
    q_bytes = build_bytes([("ANCHOR", 1, 2.0), ("FILL8", 50, 3), ("END",)])

    # Concatenated program P ⊕ Q (strip trailing END from P)
    pq_bytes = p_bytes[:-1] + q_bytes[1:]  # Remove P's END, Q's first byte (reuse P's END)

    # Actually, need to rebuild properly
    pq_bytes = build_bytes([
        ("FILL8", 100, 7),
        ("ANCHOR", 1, 2.0),
        ("FILL8", 50, 3),
        ("END",),
    ])

    res_p = dot_product_code(p_bytes, v_gain)
    res_q = dot_product_code(q_bytes, v_gain)
    res_pq = dot_product_code(pq_bytes, v_gain)

    # Linearity: dot(P ⊕ Q) == dot(P) + dot(Q)
    assert abs(res_pq - (res_p + res_q)) < 1e-6

def test_copy_preserves_linearity():
    """Prove dot(COPY(P) ⊕ COPY(Q)) == dot(COPY(P)) + dot(COPY(Q))."""
    # P: FILL8(50)
    # COPY(P): COPY(0, 50)
    # Q: FILL8(30)
    # COPY(Q): COPY(50, 30)

    prog_p_copyp = build_bytes([
        ("FILL8", 50, 1),
        ("COPY", 0, 50),
        ("END",),
    ])

    prog_q_copyq = build_bytes([
        ("FILL8", 30, 2),
        ("COPY", 0, 30),
        ("END",),
    ])

    prog_pq_all = build_bytes([
        ("FILL8", 50, 1),
        ("FILL8", 30, 2),
        ("COPY", 0, 50),
        ("COPY", 50, 30),
        ("END",),
    ])

    res_p_copyp = dot_product_code(prog_p_copyp, v_ones)
    res_q_copyq = dot_product_code(prog_q_copyq, v_ones)
    res_pq_all = dot_product_code(prog_pq_all, v_ones)

    # Expected: (50+50) + (30+30) = 160
    assert res_p_copyp == 100.0
    assert res_q_copyq == 60.0
    assert res_pq_all == 160.0
    assert abs(res_pq_all - (res_p_copyp + res_q_copyq)) < 1e-6
