"""Common code for tests."""
from __future__ import annotations

from collections.abc import Callable
from enum import Enum
from typing import NamedTuple
from unittest.mock import MagicMock

import pyvera as pv

from homeassistant import config_entries
from homeassistant.components.vera.const import (
    CONF_CONTROLLER,
    CONF_LEGACY_UNIQUE_ID,
    DOMAIN,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry

SetupCallback = Callable[[pv.VeraController, dict], None]


class ControllerData(NamedTuple):
    """Test data about a specific vera controller."""

    controller: pv.VeraController
    update_callback: Callable


class ComponentData(NamedTuple):
    """Test data about the vera component."""

    controller_data: tuple[ControllerData]


class ConfigSource(Enum):
    """Source of configuration."""

    FILE = "file"
    CONFIG_FLOW = "config_flow"
    CONFIG_ENTRY = "config_entry"


class ControllerConfig(NamedTuple):
    """Test config for mocking a vera controller."""

    config: dict
    options: dict
    config_source: ConfigSource
    serial_number: str
    devices: tuple[pv.VeraDevice, ...]
    scenes: tuple[pv.VeraScene, ...]
    setup_callback: SetupCallback
    legacy_entity_unique_id: bool


def new_simple_controller_config(
    config: dict = None,
    options: dict = None,
    config_source=ConfigSource.CONFIG_FLOW,
    serial_number="1111",
    devices: tuple[pv.VeraDevice, ...] = (),
    scenes: tuple[pv.VeraScene, ...] = (),
    setup_callback: SetupCallback = None,
    legacy_entity_unique_id=False,
) -> ControllerConfig:
    """Create simple controller config."""
    return ControllerConfig(
        config=config or {CONF_CONTROLLER: "http://127.0.0.1:123"},
        options=options,
        config_source=config_source,
        serial_number=serial_number,
        devices=devices,
        scenes=scenes,
        setup_callback=setup_callback,
        legacy_entity_unique_id=legacy_entity_unique_id,
    )


class ComponentFactory:
    """Factory class."""

    def __init__(self, vera_controller_class_mock):
        """Initialize the factory."""
        self.vera_controller_class_mock = vera_controller_class_mock

    async def configure_component(
        self,
        hass: HomeAssistant,
        controller_config: ControllerConfig = None,
        controller_configs: tuple[ControllerConfig] = (),
    ) -> ComponentData:
        """Configure the component with multiple specific mock data."""
        configs = list(controller_configs)

        if controller_config:
            configs.append(controller_config)

        return ComponentData(
            controller_data=tuple(
                [
                    await self._configure_component(hass, controller_config)
                    for controller_config in configs
                ]
            )
        )

    async def _configure_component(
        self, hass: HomeAssistant, controller_config: ControllerConfig
    ) -> ControllerData:
        """Configure the component with specific mock data."""
        component_config = {
            **(controller_config.config or {}),
            **(controller_config.options or {}),
        }

        if controller_config.legacy_entity_unique_id:
            component_config[CONF_LEGACY_UNIQUE_ID] = True

        controller: pv.VeraController = MagicMock()
        controller.base_url = component_config.get(CONF_CONTROLLER)
        controller.register = MagicMock()
        controller.start = MagicMock()
        controller.stop = MagicMock()
        controller.refresh_data = MagicMock()
        controller.temperature_units = "C"
        controller.serial_number = controller_config.serial_number
        controller.get_devices = MagicMock(return_value=controller_config.devices)
        controller.get_scenes = MagicMock(return_value=controller_config.scenes)

        for vera_obj in controller.get_devices() + controller.get_scenes():
            vera_obj.vera_controller = controller

        controller.get_devices.reset_mock()
        controller.get_scenes.reset_mock()

        if controller_config.setup_callback:
            controller_config.setup_callback(controller)

        self.vera_controller_class_mock.return_value = controller

        hass_config = {}

        # Setup component through config file import.
        if controller_config.config_source == ConfigSource.FILE:
            hass_config[DOMAIN] = component_config

        # Setup Home Assistant.
        assert await async_setup_component(hass, DOMAIN, hass_config)
        await hass.async_block_till_done()

        # Setup component through config flow.
        if controller_config.config_source == ConfigSource.CONFIG_FLOW:
            await hass.config_entries.flow.async_init(
                DOMAIN,
                context={"source": config_entries.SOURCE_USER},
                data=component_config,
            )
            await hass.async_block_till_done()

        # Setup component directly from config entry.
        if controller_config.config_source == ConfigSource.CONFIG_ENTRY:
            entry = MockConfigEntry(
                domain=DOMAIN,
                data=controller_config.config,
                options=controller_config.options,
                unique_id="12345",
            )
            entry.add_to_hass(hass)

            await hass.config_entries.async_setup(entry.entry_id)
            await hass.async_block_till_done()

        update_callback = (
            controller.register.call_args_list[0][0][1]
            if controller.register.call_args_list
            else None
        )

        return ControllerData(controller=controller, update_callback=update_callback)