"""Tests for audio DTOs."""

from __future__ import annotations

from dataclasses import FrozenInstanceError

import numpy as np
import pytest

from noteflow.config.constants import DEFAULT_SAMPLE_RATE
from noteflow.infrastructure.audio import AudioDeviceInfo, TimestampedAudio

# Test constants for audio frame sizes
AUDIO_FRAME_SIZE_SAMPLES = 1600
"""Standard audio frame size in samples (0.1 seconds at 16kHz)."""


class TestAudioDeviceInfo:
    """Tests for AudioDeviceInfo dataclass."""

    @pytest.mark.parametrize(
        "attr,expected",
        [
            ("device_id", 0),
            ("name", "Test Microphone"),
            ("channels", 2),
            ("sample_rate", 48000),
            ("is_default", True),
        ],
    )
    def test_audio_device_info_attributes(self, attr: str, expected: object) -> None:
        """Test AudioDeviceInfo stores attributes correctly."""
        device = AudioDeviceInfo(
            device_id=0, name="Test Microphone", channels=2, sample_rate=48000, is_default=True
        )
        assert getattr(device, attr) == expected, f"attribute {attr} should equal {expected}"

    def test_audio_device_info_frozen(self) -> None:
        """Test AudioDeviceInfo is immutable (frozen)."""
        device = AudioDeviceInfo(
            device_id=0,
            name="Test",
            channels=1,
            sample_rate=DEFAULT_SAMPLE_RATE,
            is_default=False,
        )
        with pytest.raises(FrozenInstanceError, match="cannot assign"):
            # Intentionally assign to frozen field to verify immutability
            device.name = "Modified"


class TestTimestampedAudio:
    """Tests for TimestampedAudio dataclass."""

    def test_timestamped_audio_creation(self) -> None:
        """Test TimestampedAudio can be created with valid values."""
        frames = np.zeros(AUDIO_FRAME_SIZE_SAMPLES, dtype=np.float32)
        audio = TimestampedAudio(
            frames=frames,
            timestamp=1.0,
            duration=0.1,
        )
        assert len(audio.frames) == AUDIO_FRAME_SIZE_SAMPLES, "frames length should match input"
        assert audio.timestamp == 1.0, "timestamp should match input"
        assert audio.duration == 0.1, "duration should match input"

    def test_timestamped_audio_negative_duration_raises(self) -> None:
        """Test TimestampedAudio raises on negative duration."""
        frames = np.zeros(AUDIO_FRAME_SIZE_SAMPLES, dtype=np.float32)
        with pytest.raises(ValueError, match="Duration must be non-negative"):
            TimestampedAudio(
                frames=frames,
                timestamp=0.0,
                duration=-0.1,
            )

    def test_timestamped_audio_negative_timestamp_raises(self) -> None:
        """Test TimestampedAudio raises on negative timestamp."""
        frames = np.zeros(AUDIO_FRAME_SIZE_SAMPLES, dtype=np.float32)
        with pytest.raises(ValueError, match="Timestamp must be non-negative"):
            TimestampedAudio(
                frames=frames,
                timestamp=-1.0,
                duration=0.1,
            )

    def test_timestamped_audio_zero_duration_valid(self) -> None:
        """Test TimestampedAudio accepts zero duration."""
        frames = np.zeros(0, dtype=np.float32)
        audio = TimestampedAudio(
            frames=frames,
            timestamp=0.0,
            duration=0.0,
        )
        assert audio.duration == 0.0, "zero duration should be accepted"

    def test_timestamped_audio_zero_timestamp_valid(self) -> None:
        """Test TimestampedAudio accepts zero timestamp."""
        frames = np.zeros(AUDIO_FRAME_SIZE_SAMPLES, dtype=np.float32)
        audio = TimestampedAudio(
            frames=frames,
            timestamp=0.0,
            duration=0.1,
        )
        assert audio.timestamp == 0.0, "zero timestamp should be accepted"
