"""gen_vectors.py - Test vector generator for sample_ntt module.

Generates random rho seeds, computes expected coefficients with a standard
FIPS 202 SHAKE-128 rejection sampler (hashlib), and writes hex files for
Verilator simulation.

The expected coefficients are FIPS-203-conformant (Algorithm 7 SampleNTT):
SHAKE-128(rho || j || i), consume the full rate, 12-bit rejection sampling.
"""

import os
import random
import sys
import hashlib

# Add test_framework/lib to path for VectorGenerator base class
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "lib"))
from vector_gen import VectorGenerator


def _sample_ntt_ref(seed: bytes) -> list[int]:
    """FIPS 203 Algorithm 7 SampleNTT using standard SHAKE-128.

    Args:
        seed: 34-byte input = rho(32) || j(1) || i(1).

    Returns:
        List of 256 coefficients in [0, 3329).
    """
    # 1344 bytes = 8 full SHAKE-128 rate blocks; ample for 256 accepts.
    stream = hashlib.shake_128(seed).digest(1344)
    coeffs = []
    off = 0
    while len(coeffs) < 256:
        c0, c1, c2 = stream[off], stream[off + 1], stream[off + 2]
        off += 3
        d1 = c0 | ((c1 & 0x0F) << 8)
        d2 = (c1 >> 4) | (c2 << 4)
        if d1 < 3329:
            coeffs.append(d1)
        if d2 < 3329 and len(coeffs) < 256:
            coeffs.append(d2)
    return coeffs


class SampleNTTVectorGenerator(VectorGenerator):
    """Generates test vectors for the sample_ntt_sync module."""

    def generate_one(self, params: dict) -> dict:
        """Generate a single test vector.

        Args:
            params: dict with 'k' key (ML-KEM parameter, 2/3/4).

        Returns:
            dict with 'input' and 'expected' keys.
        """
        k = params.get("k", 2)

        # Random 32-byte rho
        rho = bytes(random.randint(0, 255) for _ in range(32))

        # Choose random (i, j) indices within [0, k-1]
        i = random.randint(0, k - 1)
        j = random.randint(0, k - 1)

        # 34-byte SampleNTT input: rho || j || i (FIPS 203 Alg 13/7)
        seed = rho + bytes([j]) + bytes([i])

        # Expected coefficients from a standard SHAKE-128 sampler
        coeffs = _sample_ntt_ref(seed)

        # rho hex: byte 0 first (matches TB parse_rho byte ordering)
        rho_hex = rho.hex().upper()

        return {
            "input": {
                "rho_hex": rho_hex,
                "k": k,
                "i": i,
                "j": j,
            },
            "expected": {
                "coeffs": list(coeffs),  # 256 coefficients (0 <= c < 3329)
            },
        }

    def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
        """Write input vectors as "RHO_HEX K_HEX I_HEX J_HEX" per line.

        Args:
            vectors: List of vector dicts from generate_one().
            filepath: Path to write the hex file.
        """
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        with open(filepath, "w") as f:
            for v in vectors:
                inp = v["input"]
                rho_hex = inp["rho_hex"]
                k_hex = format(inp["k"], "X")
                i_hex = format(inp["i"], "X")
                j_hex = format(inp["j"], "X")
                f.write(f"{rho_hex} {k_hex} {i_hex} {j_hex}\n")

    def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
        """Write expected outputs: one coefficient per line (12-bit hex).

        Args:
            vectors: List of vector dicts from generate_one().
            filepath: Path to write the expected hex file.
        """
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        with open(filepath, "w") as f:
            for v in vectors:
                coeffs = v["expected"]["coeffs"]
                for c in coeffs:
                    f.write(f"{c:03X}\n")

    def compare_results(self, got: list[str], expected_file: str) -> bool:
        """Compare RTL output against expected values.

        Args:
            got: List of hex result strings from simulation ("RESULT: XXX").
            expected_file: Path to expected hex file.

        Returns:
            bool: True if all results match.
        """
        with open(expected_file, "r") as f:
            expected = [
                line.strip()
                for line in f
                if line.strip() and not line.startswith("#")
            ]

        if len(got) != len(expected):
            print(
                f"  MISMATCH: got {len(got)} results, expected {len(expected)}"
            )
            return False

        all_ok = True
        for i, (g, e) in enumerate(zip(got, expected)):
            if g.upper() != e.upper():
                print(f"  MISMATCH[{i}]: got={g}, expected={e}")
                all_ok = False
                if i >= 9:  # Stop after 10 mismatches
                    print(f"  ... too many mismatches, stopping comparison")
                    break

        return all_ok


if __name__ == "__main__":
    # Quick smoke test when run directly
    gen = SampleNTTVectorGenerator()
    for k_val in [2, 4]:
        vec = gen.generate_one({"k": k_val})
        rho = vec["input"]["rho_hex"]
        i = vec["input"]["i"]
        j = vec["input"]["j"]
        coeffs = vec["expected"]["coeffs"]
        print(f"k={k_val}, i={i}, j={j}: {len(coeffs)} coefficients")
        print(f"  rho[0:8]={rho[:8]}...")
        print(f"  coeffs[0:4]={coeffs[:4]}")
        # Verify all coefficients < Q
        assert all(0 <= c < 3329 for c in coeffs), "Coefficient out of range!"
    print("Smoke test PASSED")
