"""Test the snapcast config flow."""

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

import pytest
from snapcast.control.client import Snapclient
from snapcast.control.group import Snapgroup
from snapcast.control.server import CONTROL_PORT
from snapcast.control.stream import Snapstream

from homeassistant.components.snapcast.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PORT

from tests.common import MockConfigEntry


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
    """Override async_setup_entry."""
    with patch(
        "homeassistant.components.snapcast.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        yield mock_setup_entry


@pytest.fixture
def mock_server(mock_create_server: AsyncMock) -> Generator[AsyncMock]:
    """Override async_setup_entry."""
    with patch(
        "homeassistant.components.snapcast.config_flow.snapcast.control.create_server",
        return_value=mock_create_server,
    ) as mock_server:
        yield mock_server


@pytest.fixture
def mock_create_server(
    mock_group_1: AsyncMock,
    mock_group_2: AsyncMock,
    mock_client_1: AsyncMock,
    mock_client_2: AsyncMock,
    mock_stream_1: AsyncMock,
    mock_stream_2: AsyncMock,
) -> Generator[AsyncMock]:
    """Create mock snapcast connection."""
    with patch(
        "homeassistant.components.snapcast.coordinator.Snapserver", autospec=True
    ) as mock_snapserver:
        mock_server = mock_snapserver.return_value
        mock_server.groups = [mock_group_1, mock_group_2]
        mock_server.clients = [mock_client_1, mock_client_2]
        mock_server.streams = [mock_stream_1, mock_stream_2]

        def get_stream(identifier: str) -> AsyncMock:
            if len(mock_server.streams) == 0:
                raise KeyError(identifier)
            return {s.identifier: s for s in mock_server.streams}[identifier]

        def get_group(identifier: str) -> AsyncMock:
            return {s.identifier: s for s in mock_server.groups}[identifier]

        def get_client(identifier: str) -> AsyncMock:
            return {s.identifier: s for s in mock_server.clients}[identifier]

        mock_server.stream = get_stream
        mock_server.group = get_group
        mock_server.client = get_client

        mock_client_1.groups_available = lambda: mock_server.groups
        mock_client_2.groups_available = lambda: mock_server.groups

        yield mock_server


@pytest.fixture
async def mock_config_entry() -> MockConfigEntry:
    """Return a mock config entry."""

    # Create a mock config entry
    return MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "127.0.0.1",
            CONF_PORT: CONTROL_PORT,
        },
    )


@pytest.fixture
def mock_group_1(mock_stream_1: AsyncMock, streams: dict[str, AsyncMock]) -> AsyncMock:
    """Create a mock Snapgroup."""
    group = AsyncMock(spec=Snapgroup)
    group.identifier = "4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"
    group.name = "test_group_1"
    group.friendly_name = "Test Group 1"
    group.stream = mock_stream_1.identifier
    group.muted = False
    type(group).stream_status = PropertyMock(return_value=mock_stream_1.status)
    group.volume = 48
    group.streams_by_name.return_value = {s.friendly_name: s for s in streams.values()}
    return group


@pytest.fixture
def mock_group_2(mock_stream_2: AsyncMock, streams: dict[str, AsyncMock]) -> AsyncMock:
    """Create a mock Snapgroup."""
    group = AsyncMock(spec=Snapgroup)
    group.identifier = "4dcc4e3b-c699-a04b-7f0c-8260d23c43e2"
    group.name = "test_group_2"
    group.friendly_name = "Test Group 2"
    group.stream = mock_stream_2.identifier
    group.muted = False
    type(group).stream_status = PropertyMock(return_value=mock_stream_2.status)
    group.volume = 65
    group.streams_by_name.return_value = {s.friendly_name: s for s in streams.values()}
    return group


@pytest.fixture
def mock_client_1(mock_group_1: AsyncMock) -> AsyncMock:
    """Create a mock Snapclient."""
    client = AsyncMock(spec=Snapclient)
    client.identifier = "00:21:6a:7d:74:fc#1"
    client.friendly_name = "test_client_1"
    client.version = "0.10.0"
    client.connected = True
    client.name = "Snapclient 1"
    client.latency = 6
    client.muted = False
    client.volume = 48
    client.group = mock_group_1
    mock_group_1.clients = [client.identifier]
    return client


@pytest.fixture
def mock_client_2(mock_group_2: AsyncMock) -> AsyncMock:
    """Create a mock Snapclient."""
    client = AsyncMock(spec=Snapclient)
    client.identifier = "00:21:6a:7d:74:fc#2"
    client.friendly_name = "test_client_2"
    client.version = "0.10.0"
    client.connected = True
    client.name = "Snapclient 2"
    client.latency = 6
    client.muted = False
    client.volume = 100
    client.group = mock_group_2
    mock_group_2.clients = [client.identifier]
    return client


@pytest.fixture
def mock_stream_1() -> AsyncMock:
    """Create a mock stream."""
    stream = AsyncMock(spec=Snapstream)
    stream.identifier = "test_stream_1"
    stream.status = "playing"
    stream.name = "Test Stream 1"
    stream.friendly_name = "Test Stream 1"
    stream.metadata = {
        "album": "Test Album",
        "artist": ["Test Artist 1", "Test Artist 2"],
        "title": "Test Title",
        "artUrl": "http://localhost/test_art.jpg",
        "albumArtist": [
            "Test Album Artist 1",
            "Test Album Artist 2",
        ],
        "trackNumber": 10,
        "duration": 60.0,
    }
    stream.meta = stream.metadata
    stream.properties = {
        "position": 30.0,
        **stream.metadata,
    }
    stream.path = None
    return stream


@pytest.fixture
def mock_stream_2() -> AsyncMock:
    """Create a mock stream."""
    stream = AsyncMock(spec=Snapstream)
    stream.identifier = "test_stream_2"
    stream.status = "idle"
    stream.name = "Test Stream 2"
    stream.friendly_name = "Test Stream 2"
    stream.metadata = None
    stream.meta = None
    stream.properties = None
    stream.path = None
    return stream


@pytest.fixture
def streams(mock_stream_1: AsyncMock, mock_stream_2: AsyncMock) -> dict[str, AsyncMock]:
    """Return a dictionary of mock streams."""
    return {
        mock_stream_1.identifier: mock_stream_1,
        mock_stream_2.identifier: mock_stream_2,
    }
