"""Tests for audio codec utilities."""

import math
import struct

import pytest

from dvgateway.audio.codec import (
    calculate_rms,
    detect_voice_activity,
    float32_to_slin16,
    resample,
    slin16_to_float32,
    slin16_to_ulaw,
    ulaw_to_slin16,
)


class TestSlin16ToFloat32:
    def test_silence(self) -> None:
        """All-zero bytes should produce all-zero floats."""
        buf = b"\x00\x00" * 320  # 640 bytes = 20ms
        result = slin16_to_float32(buf)
        assert len(result) == 320
        assert all(s == 0.0 for s in result)

    def test_max_positive(self) -> None:
        """Max positive int16 (32767) should map to ~1.0."""
        buf = struct.pack("<h", 32767)
        result = slin16_to_float32(buf)
        assert len(result) == 1
        assert abs(result[0] - (32767 / 32768.0)) < 1e-6

    def test_max_negative(self) -> None:
        """Min int16 (-32768) should map to -1.0."""
        buf = struct.pack("<h", -32768)
        result = slin16_to_float32(buf)
        assert len(result) == 1
        assert result[0] == -1.0

    def test_roundtrip(self) -> None:
        """Convert slin16 → float32 → slin16 should be nearly lossless."""
        original = struct.pack("<5h", 0, 1000, -1000, 32767, -32768)
        float_samples = slin16_to_float32(original)
        roundtripped = float32_to_slin16(float_samples)
        original_values = struct.unpack("<5h", original)
        roundtripped_values = struct.unpack("<5h", roundtripped)
        for o, r in zip(original_values, roundtripped_values):
            assert abs(o - r) <= 1  # Allow ±1 quantization error


class TestFloat32ToSlin16:
    def test_clamps_values(self) -> None:
        """Values outside [-1.0, 1.0] should be clamped."""
        result = float32_to_slin16([1.5, -1.5])
        values = struct.unpack("<2h", result)
        assert values[0] == 32767
        assert values[1] == -32767

    def test_zero(self) -> None:
        result = float32_to_slin16([0.0])
        assert struct.unpack("<h", result)[0] == 0


class TestUlawConversion:
    def test_roundtrip(self) -> None:
        """slin16 → ulaw → slin16 should preserve signal approximately."""
        original = struct.pack("<4h", 0, 5000, -5000, 20000)
        ulaw = slin16_to_ulaw(original)
        assert len(ulaw) == 4
        restored = ulaw_to_slin16(ulaw)
        orig_values = struct.unpack("<4h", original)
        rest_values = struct.unpack("<4h", restored)
        for o, r in zip(orig_values, rest_values):
            # μ-law is lossy, but should be within ~2% for larger values
            if abs(o) > 100:
                assert abs(o - r) / abs(o) < 0.1


class TestResample:
    def test_same_rate(self) -> None:
        """Same rate should return identical samples."""
        samples = [0.1, 0.2, 0.3]
        result = resample(samples, 16000, 16000)
        assert result == samples

    def test_upsample(self) -> None:
        """Upsampling 16kHz→24kHz should produce more samples."""
        samples = [0.0] * 320  # 20ms at 16kHz
        result = resample(samples, 16000, 24000)
        assert len(result) == 480  # 20ms at 24kHz

    def test_downsample(self) -> None:
        """Downsampling 24kHz→16kHz should produce fewer samples."""
        samples = [0.0] * 480  # 20ms at 24kHz
        result = resample(samples, 24000, 16000)
        assert len(result) == 320  # 20ms at 16kHz


class TestCalculateRms:
    def test_silence(self) -> None:
        assert calculate_rms([0.0] * 100) == 0.0

    def test_constant_signal(self) -> None:
        """RMS of constant signal equals the absolute value."""
        assert abs(calculate_rms([0.5] * 100) - 0.5) < 1e-6

    def test_empty(self) -> None:
        assert calculate_rms([]) == 0.0


class TestDetectVoiceActivity:
    def test_silence_is_not_voice(self) -> None:
        assert detect_voice_activity([0.0] * 100) is False

    def test_loud_signal_is_voice(self) -> None:
        assert detect_voice_activity([0.5] * 100) is True

    def test_custom_threshold(self) -> None:
        samples = [0.005] * 100  # RMS = 0.005
        assert detect_voice_activity(samples, threshold=0.01) is False
        assert detect_voice_activity(samples, threshold=0.001) is True
