"""Tests for system/audio.py - AudioSystem, AudioConfig, and helpers."""

import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from system.audio import (
    AudioConfig,
    AudioSystem,
    LATENCY_PROFILES,
    I2S_CONFIGS,
    FOCUSRITE_PROFILES,
    CONFIG_TXT,
    JACK_SERVICE_PATH,
    LIMITS_CONF,
)


# ── AudioConfig tests ─────────────────────────────────────────────


def test_audio_config_defaults():
    cfg = AudioConfig()
    assert cfg.hat_type == "audioinjector"
    assert cfg.profile == "standard"
    assert cfg.input_device == "hw:0,0"
    assert cfg.output_device == "hw:0,0"
    assert cfg.jack_enabled is True
    assert cfg.auto_connect is True
    assert cfg.xrun_warn_only is True


def test_audio_config_overlay_line():
    for key, expected_line in [
        ("audioinjector", "dtoverlay=audioinjector-wm8731"),
        ("pcm1808_pcm5102", "dtoverlay=audiosense-pi"),
        ("iqaudio_codec", "dtoverlay=iqaudio-codec"),
        ("justboom", "dtoverlay=justboom-dac"),
        ("wm8731", "dtoverlay=wm8731"),
    ]:
        cfg = AudioConfig(hat_type=key)
        assert cfg.overlay_line == expected_line, f"{key}: {cfg.overlay_line}"


def test_audio_config_unknown_hat():
    cfg = AudioConfig(hat_type="nonexistent-hat")
    assert cfg.overlay_line is None


def test_audio_config_latency_profile():
    cfg = AudioConfig(profile="standard")
    assert cfg.latency_profile["period"] == 128
    assert cfg.latency_profile["nperiods"] == 2
    assert cfg.latency_profile["rate"] == 48000
    assert cfg.latency_profile["rt_priority"] == 70

    cfg2 = AudioConfig(profile="low")
    assert cfg2.latency_profile["period"] == 64
    assert cfg2.latency_profile["rt_priority"] == 80


def test_audio_config_unknown_profile():
    cfg = AudioConfig(profile="bogus")
    # Should fall back to standard
    assert cfg.latency_profile["period"] == 128


def test_audio_config_jack_device_arg():
    cfg = AudioConfig(output_device="hw:1,0")
    assert cfg.jack_device_arg == "dhw:1,0"


# ── Latency buffer time computation ───────────────────────────────


def test_latency_profiles_buffer_times():
    for name, profile in LATENCY_PROFILES.items():
        period_ms = (profile["period"] / profile["rate"]) * 1000
        buffer_ms = period_ms * profile["nperiods"]
        # Standard: 128/48000 * 2 = 5.33ms; Low: 64/48000 * 2 = 2.67ms
        assert buffer_ms < 10.0, f"{name}: {buffer_ms:.2f} ms >= 10 ms"
        assert buffer_ms > 0


# ── I2S config entries ────────────────────────────────────────────


def test_i2s_configs_have_all_fields():
    for key, (line, card, desc) in I2S_CONFIGS.items():
        assert line.startswith("dtoverlay="), f"{key}: bad overlay line"
        assert isinstance(card, int) and card >= 0, f"{key}: bad card index"
        assert len(desc) > 10, f"{key}: missing description"


# ── AudioSystem tests ─────────────────────────────────────────────


def test_audio_system_init():
    sys = AudioSystem()
    assert sys.config.jack_enabled is True
    assert isinstance(sys.config, AudioConfig)


def test_audio_system_custom_config():
    cfg = AudioConfig(hat_type="justboom", profile="low")
    sys = AudioSystem(config=cfg)
    assert sys.config.hat_type == "justboom"
    assert sys.config.profile == "low"


def test_audio_system_list_devices_no_alsa():
    # On a non-RPi / without ALSA tools, list_devices returns []
    sys = AudioSystem()
    devices = sys.list_devices()
    assert isinstance(devices, list)


def test_audio_system_stop_jack_no_crash():
    """stop_jack should not crash even if jackd is not running."""
    sys = AudioSystem()
    # Should finish without exception
    sys.stop_jack()


def test_audio_system_read_xrun_count_no_jack():
    """read_xrun_count returns None when JACK isn't running."""
    result = AudioSystem.read_xrun_count()
    assert result is None or isinstance(result, int)


def test_audio_system_get_active_overlay():
    """get_active_overlay should not crash on a non-RPi."""
    result = AudioSystem.get_active_overlay()
    # May be None or a string — just shouldn't crash
    assert result is None or isinstance(result, str)


def test_audio_system_monitor_xruns_no_jack():
    """monitor_xruns should not crash when JACK isn't running."""
    sys = AudioSystem()
    result = sys.monitor_xruns(duration=1, interval=1)
    assert isinstance(result, dict)
    assert "xrun_total" in result
    assert "stable" in result


def test_audio_system_measure_roundtrip_latency():
    """measure_roundtrip_latency returns None when jack_iodelay is missing."""
    result = AudioSystem.measure_roundtrip_latency(samples=1, timeout=2)
    assert result is None  # jack_iodelay not installed on dev machine


# ── Systemd service content tests ─────────────────────────────────


def test_systemd_service_content_default():
    cfg = AudioConfig()
    content = AudioSystem.systemd_service_content(cfg)
    assert "[Unit]" in content
    assert "Description=JACK Audio Server" in content
    assert "ExecStart=/usr/bin/jackd" in content
    assert "-P70" in content
    assert "-p128" in content
    assert "-n2" in content
    assert "-r48000" in content
    assert "LimitRTPRIO=99" in content
    assert "LimitMEMLOCK=infinity" in content
    assert "WantedBy=multi-user.target" in content


def test_systemd_service_content_low_latency():
    cfg = AudioConfig(profile="low")
    content = AudioSystem.systemd_service_content(cfg)
    assert "-P80" in content  # higher RT priority for low profile
    assert "-p64" in content  # smaller period


def test_systemd_service_content_different_hat():
    cfg = AudioConfig(hat_type="justboom")
    content = AudioSystem.systemd_service_content(cfg)
    assert "-dhw:0" in content  # same device default


def test_systemd_service_content_custom_device():
    cfg = AudioConfig(output_device="hw:2,0")
    content = AudioSystem.systemd_service_content(cfg)
    assert "-dhw:2,0" in content


# ── Mode / stereo_4cm tests ───────────────────────────────────────


def test_audio_config_mode_default():
    cfg = AudioConfig()
    assert cfg.mode == "mono"


def test_audio_config_mode_stereo_4cm():
    cfg = AudioConfig(mode="stereo_4cm")
    assert cfg.mode == "stereo_4cm"


def test_audio_config_channel_counts_mono():
    cfg = AudioConfig(mode="mono")
    assert cfg.capture_channels == 1
    assert cfg.playback_channels == 1


def test_audio_config_channel_counts_stereo_4cm():
    cfg = AudioConfig(mode="stereo_4cm")
    assert cfg.capture_channels == 2
    assert cfg.playback_channels == 2


def test_audio_config_focusrite_profile_hit():
    cfg = AudioConfig(hat_type="focusrite_2i2_3gen")
    prof = cfg.focusrite_profile
    assert prof is not None
    desc, cap, play = prof
    assert "Focusrite" in desc
    assert cap == 2
    assert play == 2


def test_audio_config_focusrite_profile_miss():
    cfg = AudioConfig()
    assert cfg.focusrite_profile is None


def test_focusrite_profiles_entries():
    for key, (desc, cap, play) in FOCUSRITE_PROFILES.items():
        assert len(desc) > 10
        assert isinstance(cap, int) and cap > 0
        assert isinstance(play, int) and play > 0


def test_channel_mapping_help_mono():
    mapping = AudioSystem.channel_mapping_help("mono")
    assert len(mapping) == 1
    assert mapping[0]["capture_port"] == "system:capture_1"
    assert mapping[0]["playback_port"] == "system:playback_1"


def test_channel_mapping_help_stereo_4cm():
    mapping = AudioSystem.channel_mapping_help("stereo_4cm")
    assert len(mapping) == 2
    assert "Guitar" in mapping[0]["description"]
    assert "FX Send" in mapping[1]["description"]


def test_channel_mapping_help_unknown():
    import pytest
    with pytest.raises(ValueError, match="Unknown mode"):
        AudioSystem.channel_mapping_help("bogus")


# ── Module exports ────────────────────────────────────────────────


def test_module_exports():
    from system import AudioConfig as AC, AudioSystem as AS
    assert AC is AudioConfig
    assert AS is AudioSystem