"""Test fixtures for bang_olufsen."""

from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch

from mozart_api.models import (
    Action,
    BatteryState,
    BeolinkPeer,
    BeolinkSelf,
    ContentItem,
    ListeningMode,
    ListeningModeFeatures,
    ListeningModeRef,
    ListeningModeTrigger,
    PairedRemote,
    PairedRemoteResponse,
    PlaybackContentMetadata,
    PlaybackProgress,
    PlaybackState,
    PlayQueueSettings,
    ProductState,
    RemoteMenuItem,
    RenderingState,
    SoftwareUpdateState,
    SoftwareUpdateStatus,
    Source,
    SourceArray,
    SourceTypeEnum,
    VolumeState,
)
import pytest

from homeassistant.components.bang_olufsen.const import DOMAIN
from homeassistant.core import HomeAssistant

from .const import (
    TEST_BATTERY,
    TEST_DATA_CREATE_ENTRY,
    TEST_DATA_CREATE_ENTRY_2,
    TEST_DATA_CREATE_ENTRY_3,
    TEST_DATA_CREATE_ENTRY_4,
    TEST_FRIENDLY_NAME,
    TEST_FRIENDLY_NAME_2,
    TEST_FRIENDLY_NAME_3,
    TEST_FRIENDLY_NAME_4,
    TEST_HOST_3,
    TEST_HOST_4,
    TEST_JID_1,
    TEST_JID_3,
    TEST_JID_4,
    TEST_REMOTE_SERIAL,
    TEST_SERIAL_NUMBER,
    TEST_SERIAL_NUMBER_2,
    TEST_SERIAL_NUMBER_3,
    TEST_SERIAL_NUMBER_4,
    TEST_SOUND_MODE,
    TEST_SOUND_MODE_2,
    TEST_SOUND_MODE_NAME,
)

from tests.common import MockConfigEntry


@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
    """Mock config entry for Beosound Balance."""
    return MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_SERIAL_NUMBER,
        data=TEST_DATA_CREATE_ENTRY,
        title=TEST_FRIENDLY_NAME,
    )


@pytest.fixture
def mock_config_entry_core() -> MockConfigEntry:
    """Mock config entry for Beoconnect Core."""
    return MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_SERIAL_NUMBER_2,
        data=TEST_DATA_CREATE_ENTRY_2,
        title=TEST_FRIENDLY_NAME_2,
    )


@pytest.fixture
def mock_config_entry_premiere() -> MockConfigEntry:
    """Mock config entry for Beosound Premiere."""
    return MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_SERIAL_NUMBER_3,
        data=TEST_DATA_CREATE_ENTRY_3,
        title=TEST_FRIENDLY_NAME_3,
    )


@pytest.fixture
def mock_config_entry_a5() -> MockConfigEntry:
    """Mock config entry for Beosound A5."""
    return MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_SERIAL_NUMBER_4,
        data=TEST_DATA_CREATE_ENTRY_4,
        title=TEST_FRIENDLY_NAME_4,
    )


async def mock_websocket_connection(
    hass: HomeAssistant, mock_mozart_client: AsyncMock
) -> None:
    """Register and receive initial WebSocket notifications."""

    # Currently only add notifications that are used.

    # Register callbacks.
    volume_callback = mock_mozart_client.get_volume_notifications.call_args[0][0]
    source_change_callback = (
        mock_mozart_client.get_source_change_notifications.call_args[0][0]
    )
    playback_state_callback = (
        mock_mozart_client.get_playback_state_notifications.call_args[0][0]
    )
    playback_metadata_callback = (
        mock_mozart_client.get_playback_metadata_notifications.call_args[0][0]
    )
    battery_callback = mock_mozart_client.get_battery_notifications.call_args[0][0]

    # Trigger callbacks. Try to use existing data
    volume_callback(mock_mozart_client.get_product_state.return_value.volume)
    source_change_callback(
        mock_mozart_client.get_product_state.return_value.playback.source
    )
    playback_state_callback(
        mock_mozart_client.get_product_state.return_value.playback.state
    )
    playback_metadata_callback(
        mock_mozart_client.get_product_state.return_value.playback.metadata
    )

    # This should not affect non-battery devices.
    battery_callback(TEST_BATTERY)

    await hass.async_block_till_done()


@pytest.fixture(name="integration")
async def integration_fixture(
    hass: HomeAssistant,
    mock_mozart_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Set up the Bang & Olufsen integration."""

    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    await mock_websocket_connection(hass, mock_mozart_client)


@pytest.fixture
def mock_mozart_client() -> Generator[AsyncMock]:
    """Mock MozartClient."""
    with (
        patch(
            "homeassistant.components.bang_olufsen.MozartClient", autospec=True
        ) as mock_client,
        patch(
            "homeassistant.components.bang_olufsen.config_flow.MozartClient",
            new=mock_client,
        ),
    ):
        client = mock_client.return_value

        # REST API client methods
        client.get_beolink_self = AsyncMock()
        client.get_beolink_self.return_value = BeolinkSelf(
            friendly_name=TEST_FRIENDLY_NAME, jid=TEST_JID_1
        )
        client.get_softwareupdate_status = AsyncMock()
        client.get_softwareupdate_status.return_value = SoftwareUpdateStatus(
            software_version="1.0.0", state=SoftwareUpdateState()
        )
        client.get_product_state = AsyncMock()
        client.get_product_state.return_value = ProductState(
            volume=VolumeState(),
            playback=PlaybackState(
                metadata=PlaybackContentMetadata(),
                progress=PlaybackProgress(),
                source=Source(),
                state=RenderingState(value="started"),
            ),
        )
        client.get_available_sources = AsyncMock()
        client.get_available_sources.return_value = SourceArray(
            items=[
                # Is not playable, so should not be user selectable
                Source(
                    name="AirPlay",
                    id="airPlay",
                    is_enabled=True,
                    is_multiroom_available=False,
                ),
                # The only available beolink source
                Source(
                    name="Tidal",
                    id="tidal",
                    is_enabled=True,
                    is_multiroom_available=True,
                    is_playable=True,
                ),
                Source(
                    name="Line-In",
                    id="lineIn",
                    is_enabled=True,
                    is_multiroom_available=False,
                    is_playable=True,
                ),
                # Is disabled and not playable, so should not be user selectable
                Source(
                    name="Powerlink",
                    id="pl",
                    is_enabled=False,
                ),
            ]
        )
        client.get_remote_menu = AsyncMock()
        client.get_remote_menu.return_value = {
            # Music category, so shouldn't be included in video sources
            "b355888b-2cde-5f94-8592-d47b71d52a27": RemoteMenuItem(
                action_list=[
                    Action(
                        button_name=None,
                        content_id="netRadio://6629967157728971",
                        deezer_user_id=None,
                        gain_db=None,
                        listening_mode_id=None,
                        preset_key=None,
                        queue_item=None,
                        queue_settings=None,
                        radio_station_id=None,
                        source=None,
                        speaker_group_id=None,
                        stand_position=None,
                        stop_duration=None,
                        tone_name=None,
                        type="triggerContent",
                        volume_level=None,
                    )
                ],
                scene_list=None,
                disabled=None,
                dynamic_list=None,
                first_child_menu_item_id=None,
                label="Yle Radio Suomi Helsinki",
                next_sibling_menu_item_id="0b4552f8-7ac6-5046-9d44-5410a815b8d6",
                parent_menu_item_id="eee0c2d0-2b3a-4899-a708-658475c38926",
                available=None,
                content=ContentItem(
                    categories=["music"],
                    content_uri="netRadio://6629967157728971",
                    label="Yle Radio Suomi Helsinki",
                    source=SourceTypeEnum(value="netRadio"),
                ),
                fixed=True,
                id="b355888b-2cde-5f94-8592-d47b71d52a27",
            ),
            # Has "hdmi" as category, so should be included in video sources
            "b6591565-80f4-4356-bcd9-c92ca247f0a9": RemoteMenuItem(
                action_list=[
                    Action(
                        button_name=None,
                        content_id="tv://hdmi_1",
                        deezer_user_id=None,
                        gain_db=None,
                        listening_mode_id=None,
                        preset_key=None,
                        queue_item=None,
                        queue_settings=None,
                        radio_station_id=None,
                        source=None,
                        speaker_group_id=None,
                        stand_position=None,
                        stop_duration=None,
                        tone_name=None,
                        type="triggerContent",
                        volume_level=None,
                    )
                ],
                scene_list=None,
                disabled=False,
                dynamic_list="none",
                first_child_menu_item_id=None,
                label="HDMI A",
                next_sibling_menu_item_id="0ba98974-7b1f-40dc-bc48-fbacbb0f1793",
                parent_menu_item_id="b66c835b-6b98-4400-8f84-6348043792c7",
                available=True,
                content=ContentItem(
                    categories=["hdmi"],
                    content_uri="tv://hdmi_1",
                    label="HDMI A",
                    source=SourceTypeEnum(value="tv"),
                ),
                fixed=False,
                id="b6591565-80f4-4356-bcd9-c92ca247f0a9",
            ),
            # The parent remote menu item. Has the TV label and
            # should therefore not be included in video sources
            "b66c835b-6b98-4400-8f84-6348043792c7": RemoteMenuItem(
                action_list=[],
                scene_list=None,
                disabled=False,
                dynamic_list="none",
                first_child_menu_item_id="b6591565-80f4-4356-bcd9-c92ca247f0a9",
                label="TV",
                next_sibling_menu_item_id="0c4547fe-d3cc-4348-a425-473595b8c9fb",
                parent_menu_item_id=None,
                available=True,
                content=None,
                fixed=True,
                id="b66c835b-6b98-4400-8f84-6348043792c7",
            ),
            # Has an empty content, so should not be included
            "64c9da45-3682-44a4-8030-09ed3ef44160": RemoteMenuItem(
                action_list=[],
                scene_list=None,
                disabled=False,
                dynamic_list="none",
                first_child_menu_item_id=None,
                label="ListeningPosition",
                next_sibling_menu_item_id=None,
                parent_menu_item_id="0c4547fe-d3cc-4348-a425-473595b8c9fb",
                available=True,
                content=None,
                fixed=True,
                id="64c9da45-3682-44a4-8030-09ed3ef44160",
            ),
        }
        client.get_beolink_peers = AsyncMock()
        client.get_beolink_peers.return_value = [
            BeolinkPeer(
                friendly_name=TEST_FRIENDLY_NAME_3,
                jid=TEST_JID_3,
                ip_address=TEST_HOST_3,
            ),
            BeolinkPeer(
                friendly_name=TEST_FRIENDLY_NAME_4,
                jid=TEST_JID_4,
                ip_address=TEST_HOST_4,
            ),
        ]
        client.get_beolink_listeners = AsyncMock()
        client.get_beolink_listeners.return_value = [
            BeolinkPeer(
                friendly_name=TEST_FRIENDLY_NAME_3,
                jid=TEST_JID_3,
                ip_address=TEST_HOST_3,
            ),
            BeolinkPeer(
                friendly_name=TEST_FRIENDLY_NAME_4,
                jid=TEST_JID_4,
                ip_address=TEST_HOST_4,
            ),
        ]

        client.get_listening_mode_set = AsyncMock()
        client.get_listening_mode_set.return_value = [
            ListeningMode(
                id=TEST_SOUND_MODE,
                name=TEST_SOUND_MODE_NAME,
                features=ListeningModeFeatures(),
                triggers=[ListeningModeTrigger()],
            ),
            ListeningMode(
                id=TEST_SOUND_MODE_2,
                name=TEST_SOUND_MODE_NAME,
                features=ListeningModeFeatures(),
                triggers=[ListeningModeTrigger()],
            ),
            ListeningMode(
                id=345,
                name=f"{TEST_SOUND_MODE_NAME} 2",
                features=ListeningModeFeatures(),
                triggers=[ListeningModeTrigger()],
            ),
        ]
        client.get_active_listening_mode = AsyncMock()
        client.get_active_listening_mode.return_value = ListeningModeRef(
            href="",
            id=123,
        )
        client.get_settings_queue = AsyncMock()
        client.get_settings_queue.return_value = PlayQueueSettings(
            repeat="none",
            shuffle=False,
        )
        client.get_bluetooth_remotes = AsyncMock()
        client.get_bluetooth_remotes.return_value = PairedRemoteResponse(
            items=[
                PairedRemote(
                    address="",
                    app_version="1.0.0",
                    battery_level=50,
                    connected=True,
                    serial_number=TEST_REMOTE_SERIAL,
                    name="BEORC",
                )
            ]
        )
        client.get_battery_state = AsyncMock()
        client.get_battery_state.return_value = BatteryState(
            battery_level=0,
            is_charging=False,
            remaining_charging_time_minutes=0,
            remaining_playing_time_minutes=0,
            state="BatteryNotPresent",
        )
        client.post_standby = AsyncMock()
        client.set_current_volume_level = AsyncMock()
        client.set_volume_mute = AsyncMock()
        client.post_playback_command = AsyncMock()
        client.seek_to_position = AsyncMock()
        client.post_clear_queue = AsyncMock()
        client.post_overlay_play = AsyncMock()
        client.post_uri_source = AsyncMock()
        client.run_provided_scene = AsyncMock()
        client.activate_preset = AsyncMock()
        client.start_deezer_flow = AsyncMock()
        client.add_to_queue = AsyncMock()
        client.post_remote_trigger = AsyncMock()
        client.set_active_source = AsyncMock()
        client.post_beolink_expand = AsyncMock()
        client.join_beolink_peer = AsyncMock()
        client.post_beolink_unexpand = AsyncMock()
        client.post_beolink_leave = AsyncMock()
        client.post_beolink_allstandby = AsyncMock()
        client.join_latest_beolink_experience = AsyncMock()
        client.activate_listening_mode = AsyncMock()
        client.set_settings_queue = AsyncMock()

        # Non-REST API client methods
        client.check_device_connection = AsyncMock()
        client.close_api_client = AsyncMock()

        # WebSocket listener
        client.connect_notifications = AsyncMock()
        client.disconnect_notifications = Mock()
        client.websocket_connected = False

        yield client


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
    """Mock successful setup entry."""
    with patch(
        "homeassistant.components.bang_olufsen.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        yield mock_setup_entry
