"""Phase 1 spec tests for ADR-0065 P1 — flat-ops ``CompositeCmd`` refactor.

P1 restructures ``CompositeCmd`` from the legacy
``(op, a, b, out_addr, out_nbytes, math_op, ops)`` shape into a flat ordered
op list ``(completion, ops, rw_handles, data_op)`` (ADR-0065 D1/D2). The
user-facing ``tl.composite(op=..., a, b, epilogue=[...])`` API is preserved
(D6.4) and lowers internally to the flat shape.

This is a *meaning-preserving* refactor: every existing bench's op_log must
stay byte-equal (the existing integration suite is the regression net). The
tests here pin the new lowering *shape* only.

Phase 1 (this commit): tests only. All FAIL until the P2 production refactor:
  - current ``CompositeCmd`` requires ``op/a/b/out_addr`` (no flat-only ctor),
  - current ``OpSpec.operands`` is a tuple and there is no ``out`` field.
"""
from __future__ import annotations

from kernbench.common.pe_commands import CompositeCmd, OpSpec, Scope, TensorHandle
from kernbench.triton_emu.tl_context import TLContext

_LEGACY_FIELDS = ("op", "a", "b", "out_addr", "out_nbytes", "math_op")


def _tl() -> TLContext:
    return TLContext(pe_id=0, num_programs=1, dispatch_cycles=0)


def _composites(tl: TLContext) -> list[CompositeCmd]:
    return [c for c in tl.commands if isinstance(c, CompositeCmd)]


# ── plain GEMM composite (no epilogue) ───────────────────────────────


def test_plain_gemm_composite_lowers_to_single_head_op():
    """A composite with no epilogue still emits exactly one flat head op."""
    tl = _tl()
    a = tl.ref(0x1000, shape=(8, 16), dtype="f16")
    b = tl.ref(0x2000, shape=(16, 8), dtype="f16")
    tl.composite("gemm", a, b, out_ptr=0x3000)

    comps = _composites(tl)
    assert len(comps) == 1, f"expected 1 composite; got {len(comps)}"
    cmd = comps[0]

    assert len(cmd.ops) == 1, f"plain gemm must have 1 head op; got {len(cmd.ops)}"
    head = cmd.ops[0]
    assert head.kind == "gemm"
    assert head.operands == {"a": a, "b": b}, head.operands
    assert head.out is not None and head.out.addr == 0x3000, head.out
    assert cmd.rw_handles == ()


def test_composite_has_no_legacy_fields():
    """Legacy ``op/a/b/out_addr/out_nbytes/math_op`` are gone from the cmd."""
    tl = _tl()
    a = tl.ref(0x1000, shape=(8, 16), dtype="f16")
    b = tl.ref(0x2000, shape=(16, 8), dtype="f16")
    tl.composite("gemm", a, b, out_ptr=0x3000)
    cmd = _composites(tl)[0]
    for f in _LEGACY_FIELDS:
        assert not hasattr(cmd, f), f"legacy field {f!r} must be removed"


# ── GEMM + epilogue ──────────────────────────────────────────────────


def test_gemm_composite_with_epilogue_lowers_to_flat_ops():
    """``epilogue=[relu, bias]`` lowers to flat ops after the head, with the
    epilogue operand migrated from positional tuple to a named dict."""
    tl = _tl()
    a = tl.ref(0x1000, shape=(8, 16), dtype="f16")
    b = tl.ref(0x2000, shape=(16, 8), dtype="f16")
    bias = tl.ref(0x4000, shape=(8, 8), dtype="f16")
    tl.composite(
        "gemm", a, b, out_ptr=0x3000,
        epilogue=[{"op": "relu"}, {"op": "bias", "bias": bias}],
    )
    cmd = _composites(tl)[0]

    assert [o.kind for o in cmd.ops] == ["gemm", "relu", "bias"]
    assert cmd.ops[0].scope == Scope.OUTPUT_TILE          # head scope preserved
    assert cmd.ops[1].kind == "relu"
    assert cmd.ops[1].scope == Scope.OUTPUT_TILE
    # bias handle migrated to a named dict keyed by the EPILOGUE_OPS field.
    assert cmd.ops[2].operands == {"bias": bias}, cmd.ops[2].operands


# ── MATH composite ───────────────────────────────────────────────────


def test_math_composite_lowers_to_flat_ops():
    """``op="math", math_op="exp"`` lowers to a single head op carrying the
    math op kind in ``extra`` and its input as a named operand."""
    tl = _tl()
    a = tl.ref(0x1000, shape=(8, 16), dtype="f16")
    tl.composite("math", a, math_op="exp", out_ptr=0x3000)
    cmd = _composites(tl)[0]

    assert len(cmd.ops) == 1
    head = cmd.ops[0]
    assert head.kind == "math"
    assert head.extra.get("math_op") == "exp", head.extra
    assert head.operands == {"a": a}, head.operands
    assert head.out is not None and head.out.addr == 0x3000


# ── dataclass shape (unit) ───────────────────────────────────────────


def test_opspec_accepts_dict_operands_and_out_handle():
    h = TensorHandle(id="t1", addr=0x10, shape=(4, 4), dtype="f16", nbytes=32)
    op = OpSpec(kind="gemm", scope=Scope.OUTPUT_TILE, operands={"a": h}, out=h)
    assert op.operands == {"a": h}
    assert op.out is h


def test_compositecmd_flat_ctor_and_rw_handles_default():
    from kernbench.common.pe_commands import CompletionHandle

    h = TensorHandle(id="t1", addr=0x10, shape=(4, 4), dtype="f16", nbytes=32)
    head = OpSpec(kind="math", scope=Scope.KERNEL, operands={"a": h}, out=h)
    cmd = CompositeCmd(completion=CompletionHandle(id="c1"), ops=(head,))
    assert cmd.ops == (head,)
    assert cmd.rw_handles == ()
    assert cmd.data_op is True
