"""Test configuration for the ZHA component."""
import functools
from unittest import mock
from unittest.mock import patch

import asynctest
import pytest
import zigpy
from zigpy.application import ControllerApplication

from homeassistant.components.zha.core.const import COMPONENTS, DATA_ZHA, DOMAIN
from homeassistant.components.zha.core.gateway import ZHAGateway
from homeassistant.components.zha.core.store import async_get_registry
from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg

from .common import FakeDevice, FakeEndpoint, async_setup_entry

from tests.common import MockConfigEntry

FIXTURE_GRP_ID = 0x1001
FIXTURE_GRP_NAME = "fixture group"


@pytest.fixture(name="config_entry")
async def config_entry_fixture(hass):
    """Fixture representing a config entry."""
    config_entry = MockConfigEntry(domain=DOMAIN)
    config_entry.add_to_hass(hass)
    return config_entry


@pytest.fixture
async def setup_zha(hass, config_entry):
    """Load the ZHA component.

    This will init the ZHA component. It loads the component in HA so that
    we can test the domains that ZHA supports without actually having a zigbee
    network running.
    """
    # this prevents needing an actual radio and zigbee network available
    with patch("homeassistant.components.zha.async_setup_entry", async_setup_entry):
        hass.data[DATA_ZHA] = {}

        # init ZHA
        await hass.config_entries.async_forward_entry_setup(config_entry, DOMAIN)
        await hass.async_block_till_done()


@pytest.fixture(name="zha_gateway")
async def zha_gateway_fixture(hass, config_entry, setup_zha):
    """Fixture representing a zha gateway.

    Create a ZHAGateway object that can be used to interact with as if we
    had a real zigbee network running.
    """
    for component in COMPONENTS:
        hass.data[DATA_ZHA][component] = hass.data[DATA_ZHA].get(component, {})
    zha_storage = await async_get_registry(hass)
    dev_reg = await get_dev_reg(hass)
    gateway = ZHAGateway(hass, {}, config_entry)
    gateway.zha_storage = zha_storage
    gateway.ha_device_registry = dev_reg
    gateway.application_controller = mock.MagicMock(spec_set=ControllerApplication)
    groups = zigpy.group.Groups(gateway.application_controller)
    groups.add_listener(gateway)
    groups.add_group(FIXTURE_GRP_ID, FIXTURE_GRP_NAME, suppress_event=True)
    gateway.application_controller.configure_mock(groups=groups)
    gateway._initialize_groups()
    return gateway


@pytest.fixture
def channel():
    """Channel mock factory fixture."""

    def channel(name: str, cluster_id: int, endpoint_id: int = 1):
        ch = mock.MagicMock()
        ch.name = name
        ch.generic_id = f"channel_0x{cluster_id:04x}"
        ch.id = f"{endpoint_id}:0x{cluster_id:04x}"
        ch.async_configure = asynctest.CoroutineMock()
        ch.async_initialize = asynctest.CoroutineMock()
        return ch

    return channel


@pytest.fixture
def zigpy_device_mock():
    """Make a fake device using the specified cluster classes."""

    def _mock_dev(
        endpoints,
        ieee="00:0d:6f:00:0a:90:69:e7",
        manufacturer="FakeManufacturer",
        model="FakeModel",
        node_desc=b"\x02@\x807\x10\x7fd\x00\x00*d\x00\x00",
    ):
        """Make a fake device using the specified cluster classes."""
        device = FakeDevice(ieee, manufacturer, model, node_desc)
        for epid, ep in endpoints.items():
            endpoint = FakeEndpoint(manufacturer, model, epid)
            endpoint.device = device
            device.endpoints[epid] = endpoint
            endpoint.device_type = ep["device_type"]
            profile_id = ep.get("profile_id")
            if profile_id:
                endpoint.profile_id = profile_id

            for cluster_id in ep.get("in_clusters", []):
                endpoint.add_input_cluster(cluster_id)

            for cluster_id in ep.get("out_clusters", []):
                endpoint.add_output_cluster(cluster_id)

        return device

    return _mock_dev


@pytest.fixture
def _zha_device_restored_or_joined(hass, zha_gateway, config_entry):
    """Make a restored or joined ZHA devices."""

    async def _zha_device(is_new_join, zigpy_dev):
        if is_new_join:
            for cmp in COMPONENTS:
                await hass.config_entries.async_forward_entry_setup(config_entry, cmp)
            await hass.async_block_till_done()
            await zha_gateway.async_device_initialized(zigpy_dev)
        else:
            await zha_gateway.async_device_restored(zigpy_dev)
            for cmp in COMPONENTS:
                await hass.config_entries.async_forward_entry_setup(config_entry, cmp)
        await hass.async_block_till_done()
        return zha_gateway.get_device(zigpy_dev.ieee)

    return _zha_device


@pytest.fixture
def zha_device_joined(_zha_device_restored_or_joined):
    """Return a newly joined ZHA device."""

    return functools.partial(_zha_device_restored_or_joined, True)


@pytest.fixture
def zha_device_restored(_zha_device_restored_or_joined):
    """Return a restored ZHA device."""

    return functools.partial(_zha_device_restored_or_joined, False)


@pytest.fixture(params=["zha_device_joined", "zha_device_restored"])
def zha_device_joined_restored(request):
    """Join or restore ZHA device."""
    return request.getfixturevalue(request.param)