"""Tests for RmsLevelProvider and compute_rms."""

from __future__ import annotations

import math
from typing import TYPE_CHECKING

import numpy as np
import pytest

from noteflow.infrastructure.audio import RmsLevelProvider, compute_rms

if TYPE_CHECKING:
    from numpy.typing import NDArray


class TestComputeRms:
    """Tests for compute_rms function."""

    def test_empty_array_returns_zero(self) -> None:
        """RMS of empty array is zero."""
        frames = np.array([], dtype=np.float32)
        assert compute_rms(frames) == 0.0, "empty array RMS should be zero"

    def test_zeros_returns_zero(self) -> None:
        """RMS of zeros is zero."""
        frames = np.zeros(100, dtype=np.float32)
        assert compute_rms(frames) == 0.0, "zeros RMS should be zero"

    def test_ones_returns_one(self) -> None:
        """RMS of all ones is one."""
        frames = np.ones(100, dtype=np.float32)
        assert compute_rms(frames) == 1.0, "ones RMS should be 1.0"

    def test_half_amplitude_returns_half(self) -> None:
        """RMS of constant 0.5 is 0.5."""
        frames = np.full(100, 0.5, dtype=np.float32)
        assert compute_rms(frames) == 0.5, "half amplitude RMS should be 0.5"

    def test_sine_wave_returns_sqrt_half(self) -> None:
        """RMS of sine wave is approximately 1/sqrt(2)."""
        t = np.linspace(0, 2 * np.pi, 1000, dtype=np.float32)
        frames = np.sin(t).astype(np.float32)
        result = compute_rms(frames)
        assert 0.7 < result < 0.72, "sine wave RMS should be ~0.707"


class TestRmsLevelProvider:
    """Tests for RmsLevelProvider class."""

    @pytest.fixture
    def provider(self) -> RmsLevelProvider:
        """Create RmsLevelProvider instance."""
        return RmsLevelProvider()

    def test_get_rms_empty_array_returns_zero(self, provider: RmsLevelProvider) -> None:
        """Test RMS of empty array is zero."""
        frames = np.array([], dtype=np.float32)
        assert provider.get_rms(frames) == 0.0, "empty array RMS should be zero"

    def test_get_rms_silence_returns_zero(
        self, provider: RmsLevelProvider, silence_audio: NDArray[np.float32]
    ) -> None:
        """Test RMS of silence is zero."""
        assert provider.get_rms(silence_audio) == 0.0, "silence RMS should be zero"

    def test_get_rms_full_scale_returns_one(
        self, provider: RmsLevelProvider, full_scale_audio: NDArray[np.float32]
    ) -> None:
        """Test RMS of full scale signal is one."""
        assert provider.get_rms(full_scale_audio) == 1.0, "full scale RMS should be 1.0"

    def test_get_rms_half_scale_returns_half(
        self, provider: RmsLevelProvider, half_scale_audio: NDArray[np.float32]
    ) -> None:
        """Test RMS of half scale signal is 0.5."""
        assert provider.get_rms(half_scale_audio) == 0.5, "half scale RMS should be 0.5"

    def test_get_rms_normalized_range(self, provider: RmsLevelProvider) -> None:
        """Test RMS is always in 0.0-1.0 range."""
        # Test with values > 1.0 (should clamp)
        frames = np.full(100, 2.0, dtype=np.float32)
        rms = provider.get_rms(frames)
        assert 0.0 <= rms <= 1.0, "RMS should be clamped to 0.0-1.0"

    def test_get_db_silence_returns_min_db(
        self, provider: RmsLevelProvider, silence_audio: NDArray[np.float32]
    ) -> None:
        """Test dB of silence returns MIN_DB."""
        assert provider.get_db(silence_audio) == provider.MIN_DB, "silence dB should be MIN_DB"

    def test_get_db_full_scale_returns_zero(
        self, provider: RmsLevelProvider, full_scale_audio: NDArray[np.float32]
    ) -> None:
        """Test dB of full scale signal is 0 dB."""
        assert provider.get_db(full_scale_audio) == 0.0, "full scale dB should be 0"

    def test_get_db_half_scale_is_negative_six(
        self, provider: RmsLevelProvider, half_scale_audio: NDArray[np.float32]
    ) -> None:
        """Test dB of half scale is approximately -6 dB."""
        db = provider.get_db(half_scale_audio)
        # -6.02 dB for half amplitude
        assert -7.0 < db < -5.0, "half scale dB should be ~-6"

    def test_rms_to_db_zero_returns_min_db(self, provider: RmsLevelProvider) -> None:
        """Test rms_to_db(0) returns MIN_DB."""
        assert provider.rms_to_db(0.0) == provider.MIN_DB, "zero RMS dB should be MIN_DB"

    def test_rms_to_db_one_returns_zero(self, provider: RmsLevelProvider) -> None:
        """Test rms_to_db(1.0) returns 0 dB."""
        assert provider.rms_to_db(1.0) == 0.0, "1.0 RMS dB should be 0"

    def test_db_to_rms_min_db_returns_zero(self, provider: RmsLevelProvider) -> None:
        """Test db_to_rms(MIN_DB) returns 0."""
        assert provider.db_to_rms(provider.MIN_DB) == 0.0, "MIN_DB to RMS should be 0"

    def test_db_to_rms_zero_returns_one(self, provider: RmsLevelProvider) -> None:
        """Test db_to_rms(0) returns 1.0."""
        assert provider.db_to_rms(0.0) == 1.0, "0 dB to RMS should be 1.0"

    @pytest.mark.parametrize("rms", [0.1, 0.25, 0.5, 0.75, 1.0])
    def test_rms_db_roundtrip(self, provider: RmsLevelProvider, rms: float) -> None:
        """Test RMS -> dB -> RMS roundtrip preserves value."""
        db = provider.rms_to_db(rms)
        recovered = provider.db_to_rms(db)
        assert math.isclose(recovered, rms, rel_tol=1e-9), "RMS -> dB -> RMS roundtrip should preserve value"
