"""Unit tests for MpvMediaPlayer."""

from unittest.mock import MagicMock, patch

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def make_player(device=None):
    """Return an MpvMediaPlayer with the internal LibMpvPlayer mocked out."""
    from linux_voice_assistant.player.state import PlayerState

    mock_lib_player = MagicMock()
    mock_lib_player.state.return_value = PlayerState.IDLE

    with patch("linux_voice_assistant.mpv_player.LibMpvPlayer", return_value=mock_lib_player):
        from linux_voice_assistant.mpv_player import MpvMediaPlayer

        player = MpvMediaPlayer(device=device)
        player._mock = mock_lib_player
        return player


# ---------------------------------------------------------------------------
# Initialization
# ---------------------------------------------------------------------------


class TestInit:
    def test_player_initialized_with_device(self):
        with patch("linux_voice_assistant.mpv_player.LibMpvPlayer") as mock_cls:
            mock_cls.return_value = MagicMock()
            from linux_voice_assistant.mpv_player import MpvMediaPlayer

            MpvMediaPlayer(device="hw:1,0")
            mock_cls.assert_called_once_with(device="hw:1,0")

    def test_player_initialized_with_none_device(self):
        with patch("linux_voice_assistant.mpv_player.LibMpvPlayer") as mock_cls:
            mock_cls.return_value = MagicMock()
            from linux_voice_assistant.mpv_player import MpvMediaPlayer

            MpvMediaPlayer(device=None)
            mock_cls.assert_called_once_with(device=None)

    def test_done_callback_starts_none(self):
        player = make_player()
        assert player._done_callback is None

    def test_playlist_starts_empty(self):
        player = make_player()
        assert player._playlist == []


# ---------------------------------------------------------------------------
# play()
# ---------------------------------------------------------------------------


class TestPlay:
    def test_play_single_url_calls_lib_player(self):
        player = make_player()
        player.play("http://example.com/audio.mp3")
        player._mock.play.assert_called_once()

    def test_play_single_url_passes_correct_url(self):
        player = make_player()
        player.play("http://example.com/audio.mp3")
        args, kwargs = player._mock.play.call_args
        assert args[0] == "http://example.com/audio.mp3"

    def test_play_list_plays_first_url(self):
        player = make_player()
        player.play(["http://a.com/1.mp3", "http://a.com/2.mp3"])
        args, kwargs = player._mock.play.call_args
        assert args[0] == "http://a.com/1.mp3"

    def test_play_list_stores_remaining_in_playlist(self):
        player = make_player()
        player.play(["http://a.com/1.mp3", "http://a.com/2.mp3", "http://a.com/3.mp3"])
        assert player._playlist == ["http://a.com/2.mp3", "http://a.com/3.mp3"]

    def test_play_single_url_playlist_is_empty(self):
        player = make_player()
        player.play("http://example.com/audio.mp3")
        assert player._playlist == []

    def test_play_stores_done_callback(self):
        player = make_player()
        cb = MagicMock()
        player.play("http://example.com/audio.mp3", done_callback=cb)
        assert player._done_callback is cb

    def test_play_empty_list_does_not_call_lib_player(self):
        player = make_player()
        player.play([])
        player._mock.play.assert_not_called()

    def test_play_while_active_stops_previous(self):
        from linux_voice_assistant.player.state import PlayerState

        player = make_player()
        player._done_callback = MagicMock()  # simulate active playback
        player._mock.state.return_value = PlayerState.PLAYING

        player.play("http://example.com/new.mp3")
        player._mock.stop.assert_called_once()

    def test_play_while_idle_does_not_stop(self):
        from linux_voice_assistant.player.state import PlayerState

        player = make_player()
        player._mock.state.return_value = PlayerState.IDLE
        player._done_callback = None

        player.play("http://example.com/new.mp3")
        player._mock.stop.assert_not_called()


# ---------------------------------------------------------------------------
# _on_track_finished()
# ---------------------------------------------------------------------------


class TestOnTrackFinished:
    def test_plays_next_url_when_playlist_has_items(self):
        player = make_player()
        player._playlist = ["http://a.com/2.mp3"]
        player._on_track_finished()
        args, _ = player._mock.play.call_args
        assert args[0] == "http://a.com/2.mp3"

    def test_invokes_done_callback_when_playlist_empty(self):
        player = make_player()
        cb = MagicMock()
        player._done_callback = cb
        player._playlist = []
        player._on_track_finished()
        cb.assert_called_once()

    def test_clears_done_callback_after_invoking(self):
        player = make_player()
        player._done_callback = MagicMock()
        player._playlist = []
        player._on_track_finished()
        assert player._done_callback is None

    def test_no_error_when_done_callback_is_none(self):
        player = make_player()
        player._done_callback = None
        player._playlist = []
        player._on_track_finished()  # should not raise


# ---------------------------------------------------------------------------
# pause() / resume() / stop()
# ---------------------------------------------------------------------------


class TestPauseResumeStop:
    def test_pause_delegates_to_lib_player(self):
        player = make_player()
        player.pause()
        player._mock.pause.assert_called_once()

    def test_resume_delegates_to_lib_player(self):
        player = make_player()
        player.resume()
        player._mock.resume.assert_called_once()

    def test_stop_delegates_to_lib_player(self):
        player = make_player()
        player.stop()
        player._mock.stop.assert_called_once()

    def test_stop_invokes_done_callback(self):
        player = make_player()
        cb = MagicMock()
        player._done_callback = cb
        player.stop()
        cb.assert_called_once()

    def test_stop_clears_done_callback(self):
        player = make_player()
        player._done_callback = MagicMock()
        player.stop()
        assert player._done_callback is None

    def test_stop_no_error_when_no_callback(self):
        player = make_player()
        player._done_callback = None
        player.stop()  # should not raise


# ---------------------------------------------------------------------------
# is_playing
# ---------------------------------------------------------------------------


class TestIsPlaying:
    def test_true_when_playing(self):
        from linux_voice_assistant.player.state import PlayerState

        player = make_player()
        player._mock.state.return_value = PlayerState.PLAYING
        assert player.is_playing is True

    def test_true_when_paused(self):
        from linux_voice_assistant.player.state import PlayerState

        player = make_player()
        player._mock.state.return_value = PlayerState.PAUSED
        assert player.is_playing is True

    def test_true_when_loading(self):
        from linux_voice_assistant.player.state import PlayerState

        player = make_player()
        player._mock.state.return_value = PlayerState.LOADING
        assert player.is_playing is True

    def test_false_when_idle(self):
        from linux_voice_assistant.player.state import PlayerState

        player = make_player()
        player._mock.state.return_value = PlayerState.IDLE
        assert player.is_playing is False


# ---------------------------------------------------------------------------
# set_volume() / duck() / unduck()
# ---------------------------------------------------------------------------


class TestVolume:
    def test_set_volume_delegates_to_lib_player(self):
        player = make_player()
        player.set_volume(75.0)
        player._mock.set_volume.assert_called_once_with(75.0)

    def test_duck_delegates_to_lib_player(self):
        player = make_player()
        player.duck(factor=0.3)
        player._mock.duck.assert_called_once_with(0.3)

    def test_duck_default_factor(self):
        player = make_player()
        player.duck()
        player._mock.duck.assert_called_once_with(0.5)

    def test_unduck_delegates_to_lib_player(self):
        player = make_player()
        player.unduck()
        player._mock.unduck.assert_called_once()
