#!/usr/bin/env python3
"""
Tests for LDPC code analysis module (ldpc_analysis.py).

Covers IRA staircase code construction and rate sweep functionality.

Run:
    python3 -m pytest model/test_ldpc_analysis.py -v
"""

import numpy as np
import pytest

from ldpc_analysis import (
    build_ira_staircase,
    ira_encode,
    generic_decode,
    rate_sweep_single,
)
from ldpc_sim import poisson_channel, quantize_llr


# =============================================================================
# TestIRAStaircase
# =============================================================================

class TestIRAStaircase:
    """Validate generic IRA staircase code construction for various rates."""

    def test_rate_1_2(self):
        """m_base=1 -> rate 1/2: (1,2) base matrix, (32,64) full matrix, rank=32."""
        H_base, H_full = build_ira_staircase(m_base=1, z=32)
        assert H_base.shape == (1, 2), f"Base shape: {H_base.shape}"
        assert H_full.shape == (32, 64), f"Full shape: {H_full.shape}"
        rank = np.linalg.matrix_rank(H_full.astype(float))
        assert rank == 32, f"Rank: {rank}, expected 32"

    def test_rate_1_4(self):
        """m_base=3 -> rate 1/4: (3,4) base matrix, (96,128) full matrix, rank=96."""
        H_base, H_full = build_ira_staircase(m_base=3, z=32)
        assert H_base.shape == (3, 4), f"Base shape: {H_base.shape}"
        assert H_full.shape == (96, 128), f"Full shape: {H_full.shape}"
        rank = np.linalg.matrix_rank(H_full.astype(float))
        assert rank == 96, f"Rank: {rank}, expected 96"

    def test_rate_1_8_matches_existing(self):
        """m_base=7 -> rate 1/8: (7,8) base matrix, (224,256) full matrix."""
        H_base, H_full = build_ira_staircase(m_base=7, z=32)
        assert H_base.shape == (7, 8), f"Base shape: {H_base.shape}"
        assert H_full.shape == (224, 256), f"Full shape: {H_full.shape}"

    def test_encode_decode_roundtrip(self):
        """For m_base in [1,2,3,5], encode random info, verify syndrome=0."""
        np.random.seed(42)
        for m_base in [1, 2, 3, 5]:
            H_base, H_full = build_ira_staircase(m_base=m_base, z=32)
            z = 32
            k = z  # info bits = Z
            info = np.random.randint(0, 2, k).astype(np.int8)
            codeword = ira_encode(info, H_base, H_full, z=z)
            syndrome = H_full @ codeword % 2
            assert np.all(syndrome == 0), (
                f"m_base={m_base}: syndrome weight = {syndrome.sum()}"
            )


# =============================================================================
# TestRateSweep
# =============================================================================

class TestRateSweep:
    """Validate rate sweep simulation."""

    def test_high_snr_all_rates_decode(self):
        """At lam_s=20, all rates should achieve FER=0 with 10 frames."""
        np.random.seed(42)
        for m_base in [1, 2, 3, 5, 7]:
            result = rate_sweep_single(
                m_base=m_base, z=32, lam_s=20.0, lam_b=0.1,
                n_frames=10, max_iter=30, q_bits=6,
            )
            assert result['fer'] == 0.0, (
                f"m_base={m_base} (rate 1/{m_base+1}): FER={result['fer']} at lam_s=20, "
                f"expected 0.0"
            )
