#!/usr/bin/env python3
"""
Tests for the frame synchronization prototype (frame_sync.py).

Tests cover stream generation (length, offsets) and syndrome screening
(correct offset identification, wrong offset rejection).

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

import numpy as np
import pytest

from ldpc_sim import N, build_full_h_matrix
from frame_sync import generate_stream, syndrome_screen


# =============================================================================
# Fixtures
# =============================================================================

@pytest.fixture(scope="module")
def H():
    """Full expanded H matrix, built once per module."""
    return build_full_h_matrix()


# =============================================================================
# TestStreamGeneration
# =============================================================================

class TestStreamGeneration:
    """Validate continuous LLR stream generation."""

    def test_stream_length(self, H):
        """Stream should be n_frames * N + offset long."""
        np.random.seed(100)
        n_frames = 5
        offset = 37
        stream_llr, true_offset, info_list = generate_stream(
            H, n_frames=n_frames, lam_s=5.0, lam_b=0.1, offset=offset
        )
        assert len(stream_llr) == n_frames * N + offset
        assert true_offset == offset
        assert len(info_list) == n_frames

    def test_stream_zero_offset(self, H):
        """offset=0 should work, producing stream of length n_frames * N."""
        np.random.seed(101)
        n_frames = 3
        stream_llr, true_offset, info_list = generate_stream(
            H, n_frames=n_frames, lam_s=5.0, lam_b=0.1, offset=0
        )
        assert len(stream_llr) == n_frames * N
        assert true_offset == 0
        assert len(info_list) == n_frames

    def test_stream_random_offset(self, H):
        """offset=None should pick a random offset in [0, N-1]."""
        np.random.seed(102)
        stream_llr, true_offset, info_list = generate_stream(
            H, n_frames=4, lam_s=5.0, lam_b=0.1, offset=None
        )
        assert 0 <= true_offset < N
        assert len(stream_llr) == 4 * N + true_offset


# =============================================================================
# TestSyndromeScreen
# =============================================================================

class TestSyndromeScreen:
    """Validate syndrome-based offset screening."""

    def test_correct_offset_low_syndrome(self, H):
        """At high SNR (lam_s=10), the best offset should match the true offset."""
        np.random.seed(200)
        n_frames = 3
        offset = 42
        stream_llr, true_offset, _ = generate_stream(
            H, n_frames=n_frames, lam_s=10.0, lam_b=0.1, offset=offset
        )
        scores = syndrome_screen(stream_llr, n_offsets=N)
        # The true offset should have the lowest (or near-lowest) syndrome weight
        best_offset = min(scores, key=scores.get)
        assert best_offset == true_offset, (
            f"Best offset {best_offset} (sw={scores[best_offset]}) "
            f"!= true offset {true_offset} (sw={scores[true_offset]})"
        )

    def test_wrong_offsets_high_syndrome(self, H):
        """Wrong offsets should have average syndrome weight > 80."""
        np.random.seed(201)
        n_frames = 3
        offset = 10
        stream_llr, true_offset, _ = generate_stream(
            H, n_frames=n_frames, lam_s=10.0, lam_b=0.1, offset=offset
        )
        scores = syndrome_screen(stream_llr, n_offsets=N)
        # Collect syndrome weights for wrong offsets
        wrong_weights = [
            scores[o] for o in scores if o != true_offset
        ]
        avg_wrong = np.mean(wrong_weights)
        assert avg_wrong > 80, (
            f"Average wrong-offset syndrome weight {avg_wrong:.1f} is not > 80"
        )
