"""Tests for the arcam_fmj component."""

from asyncio import CancelledError, Queue
from collections.abc import AsyncGenerator, Generator
from contextlib import contextmanager
from unittest.mock import AsyncMock, Mock, patch

from arcam.fmj.client import Client, ResponsePacket
from arcam.fmj.state import State
import pytest

from homeassistant.components.arcam_fmj.const import DEFAULT_NAME, DOMAIN
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry

MOCK_HOST = "127.0.0.1"
MOCK_PORT = 50000
MOCK_TURN_ON = {
    "service": "switch.turn_on",
    "data": {"entity_id": "switch.test"},
}
MOCK_ENTITY_ID = "media_player.arcam_fmj_127_0_0_1"
MOCK_UUID = "456789abcdef"
MOCK_UDN = f"uuid:01234567-89ab-cdef-0123-{MOCK_UUID}"
MOCK_NAME = f"{DEFAULT_NAME} ({MOCK_HOST})"
MOCK_CONFIG_ENTRY = {CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}


@pytest.fixture(name="client")
def client_fixture() -> Generator[Mock]:
    """Get a mocked client."""
    client = Mock(Client)
    client.host = MOCK_HOST
    client.port = MOCK_PORT

    queue = Queue[BaseException | None]()
    listeners = set()

    async def _start():
        client.connected = True

    async def _process():
        result = await queue.get()
        client.connected = False
        if isinstance(result, BaseException):
            raise result

    @contextmanager
    def _listen(listener):
        listeners.add(listener)
        yield client
        listeners.remove(listener)

    @callback
    def _notify_data_updated(zn=1):
        packet = Mock(ResponsePacket)
        packet.zn = zn
        for listener in listeners:
            listener(packet)

    @callback
    def _notify_connection(exception: Exception | None = None):
        queue.put_nowait(exception)

    client.start.side_effect = _start
    client.process.side_effect = _process
    client.listen.side_effect = _listen
    client.notify_data_updated = _notify_data_updated
    client.notify_connection = _notify_connection

    yield client

    queue.put_nowait(CancelledError())


@pytest.fixture(name="state_1")
def state_1_fixture(client: Mock) -> State:
    """Get a mocked state."""
    state = Mock(State)
    state.client = client
    state.zn = 1
    state.get_power.return_value = True
    state.get_volume.return_value = 0.0
    state.get_source.return_value = None
    state.get_source_list.return_value = []
    state.get_incoming_audio_format.return_value = (None, None)
    state.get_incoming_video_parameters.return_value = None
    state.get_incoming_audio_sample_rate.return_value = 0
    state.get_mute.return_value = None
    state.get_decode_modes.return_value = []
    state.get_decode_mode.return_value = None
    state.__aenter__ = AsyncMock()
    state.__aexit__ = AsyncMock()
    return state


@pytest.fixture(name="state_2")
def state_2_fixture(client: Mock) -> State:
    """Get a mocked state."""
    state = Mock(State)
    state.client = client
    state.zn = 2
    state.get_power.return_value = True
    state.get_volume.return_value = 0.0
    state.get_source.return_value = None
    state.get_source_list.return_value = []
    state.get_incoming_audio_format.return_value = (None, None)
    state.get_incoming_video_parameters.return_value = None
    state.get_incoming_audio_sample_rate.return_value = 0
    state.get_mute.return_value = None
    state.get_decode_modes.return_value = []
    state.get_decode_mode.return_value = None
    state.__aenter__ = AsyncMock()
    state.__aexit__ = AsyncMock()
    return state


@pytest.fixture(name="mock_config_entry")
def mock_config_entry_fixture(hass: HomeAssistant) -> MockConfigEntry:
    """Get a mock config entry."""
    config_entry = MockConfigEntry(
        domain=DOMAIN,
        data=MOCK_CONFIG_ENTRY,
        title=MOCK_NAME,
        unique_id=MOCK_UUID,
    )
    config_entry.add_to_hass(hass)
    return config_entry


@pytest.fixture(name="player_setup")
async def player_setup_fixture(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    state_1: State,
    state_2: State,
    client: Mock,
) -> AsyncGenerator[None]:
    """Get standard player."""

    def state_mock(cli, zone):
        if zone == 1:
            return state_1
        if zone == 2:
            return state_2
        raise ValueError(f"Unknown player zone: {zone}")

    await async_setup_component(hass, "homeassistant", {})

    with (
        patch("homeassistant.components.arcam_fmj.Client", return_value=client),
        patch(
            "homeassistant.components.arcam_fmj.coordinator.State",
            side_effect=state_mock,
        ),
    ):
        assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
        await hass.async_block_till_done()
        yield
