"""Tests for the lifx integration."""

from __future__ import annotations

import asyncio
from contextlib import contextmanager
from unittest.mock import AsyncMock, MagicMock, Mock, patch

from aiolifx.aiolifx import Light

from homeassistant.components.lifx import discovery
from homeassistant.components.lifx.const import TARGET_ANY

MODULE = "homeassistant.components.lifx"
MODULE_CONFIG_FLOW = "homeassistant.components.lifx.config_flow"
IP_ADDRESS = "127.0.0.1"
LABEL = "My Bulb"
GROUP = "My Group"
SERIAL = "aa:bb:cc:dd:ee:cc"
MAC_ADDRESS = "aa:bb:cc:dd:ee:cd"
DHCP_FORMATTED_MAC = "aabbccddeecd"
DEFAULT_ENTRY_TITLE = LABEL


class MockMessage:
    """Mock a lifx message."""

    def __init__(self, **kwargs):
        """Init message."""
        self.target_addr = SERIAL
        self.count = 9
        for k, v in kwargs.items():
            if k != "callb":
                setattr(self, k, v)


class MockFailingLifxCommand:
    """Mock a lifx command that fails."""

    def __init__(self, bulb, **kwargs):
        """Init command."""
        self.bulb = bulb
        self.calls = []

    def __call__(self, *args, **kwargs):
        """Call command."""
        if callb := kwargs.get("callb"):
            callb(self.bulb, None)
        self.calls.append([args, kwargs])

    def reset_mock(self):
        """Reset mock."""
        self.calls = []


class MockLifxCommand:
    """Mock a lifx command."""

    def __name__(self):
        """Return name."""
        return "mock_lifx_command"

    def __init__(self, bulb, **kwargs):
        """Init command."""
        self.bulb = bulb
        self.calls = []
        self.msg_kwargs = kwargs
        for k, v in kwargs.items():
            if k != "callb":
                setattr(self.bulb, k, v)

    def __call__(self, *args, **kwargs):
        """Call command."""
        if callb := kwargs.get("callb"):
            callb(self.bulb, MockMessage(**self.msg_kwargs))
        self.calls.append([args, kwargs])

    def reset_mock(self):
        """Reset mock."""
        self.calls = []


def _mocked_bulb() -> Light:
    bulb = Light(asyncio.get_running_loop(), SERIAL, IP_ADDRESS)
    bulb.host_firmware_version = "3.00"
    bulb.label = LABEL
    bulb.group = GROUP
    bulb.color = [1, 2, 3, 4]
    bulb.power_level = 0
    bulb.fire_and_forget = AsyncMock()
    bulb.set_reboot = Mock()
    bulb.try_sending = AsyncMock()
    bulb.set_infrared = MockLifxCommand(bulb)
    bulb.get_label = MockLifxCommand(bulb)
    bulb.get_group = MockLifxCommand(bulb)
    bulb.get_color = MockLifxCommand(bulb)
    bulb.set_power = MockLifxCommand(bulb)
    bulb.set_color = MockLifxCommand(bulb)
    bulb.get_hostfirmware = MockLifxCommand(bulb)
    bulb.get_wifiinfo = MockLifxCommand(bulb, signal=100)
    bulb.get_version = MockLifxCommand(bulb)
    bulb.set_waveform_optional = MockLifxCommand(bulb)
    bulb.product = 1  # LIFX Original 1000
    return bulb


def _mocked_failing_bulb() -> Light:
    bulb = _mocked_bulb()
    bulb.get_color = MockFailingLifxCommand(bulb)
    bulb.set_power = MockFailingLifxCommand(bulb)
    bulb.set_color = MockFailingLifxCommand(bulb)
    bulb.get_hostfirmware = MockFailingLifxCommand(bulb)
    bulb.get_version = MockFailingLifxCommand(bulb)
    return bulb


def _mocked_white_bulb() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 19  # LIFX White 900 BR30 (High Voltage)
    return bulb


def _mocked_brightness_bulb() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 51  # LIFX Mini White
    return bulb


def _mocked_clean_bulb() -> Light:
    bulb = _mocked_bulb()
    bulb.get_hev_cycle = MockLifxCommand(bulb)
    bulb.set_hev_cycle = MockLifxCommand(bulb)
    bulb.get_hev_configuration = MockLifxCommand(bulb)
    bulb.get_last_hev_cycle_result = MockLifxCommand(bulb)
    bulb.hev_cycle_configuration = {"duration": 7200, "indication": False}
    bulb.hev_cycle = {
        "duration": 7200,
        "remaining": 30,
        "last_power": False,
    }
    bulb.last_hev_cycle_result = 0
    bulb.product = 90
    return bulb


def _mocked_infrared_bulb() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 29  # LIFX A19 Night Vision
    bulb.infrared_brightness = 65535
    bulb.set_infrared = MockLifxCommand(bulb)
    bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=65535)
    return bulb


def _mocked_light_strip() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 31  # LIFX Z
    bulb.color_zones = [MagicMock(), MagicMock()]
    bulb.effect = {"effect": "MOVE", "speed": 3, "duration": 0, "direction": "RIGHT"}
    bulb.get_color_zones = MockLifxCommand(bulb)
    bulb.set_color_zones = MockLifxCommand(bulb)
    bulb.get_multizone_effect = MockLifxCommand(bulb)
    bulb.set_multizone_effect = MockLifxCommand(bulb)
    bulb.get_extended_color_zones = MockLifxCommand(bulb)
    bulb.set_extended_color_zones = MockLifxCommand(bulb)
    return bulb


def _mocked_tile() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 55  # LIFX Tile
    bulb.effect = {"effect": "OFF"}
    bulb.get_tile_effect = MockLifxCommand(bulb)
    bulb.set_tile_effect = MockLifxCommand(bulb)
    bulb.get64 = MockLifxCommand(bulb)
    bulb.get_device_chain = MockLifxCommand(bulb)
    return bulb


def _mocked_ceiling() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 176  # LIFX Ceiling
    bulb.effect = {"effect": "OFF"}
    bulb.get_tile_effect = MockLifxCommand(bulb)
    bulb.set_tile_effect = MockLifxCommand(bulb)
    bulb.get64 = MockLifxCommand(bulb)
    bulb.get_device_chain = MockLifxCommand(bulb)
    return bulb


def _mocked_bulb_old_firmware() -> Light:
    bulb = _mocked_bulb()
    bulb.host_firmware_version = "2.77"
    return bulb


def _mocked_bulb_new_firmware() -> Light:
    bulb = _mocked_bulb()
    bulb.host_firmware_version = "3.90"
    return bulb


def _mocked_relay() -> Light:
    bulb = _mocked_bulb()
    bulb.product = 70  # LIFX Switch
    return bulb


def _patch_device(device: Light | None = None, no_device: bool = False):
    """Patch out discovery."""

    class MockLifxConnecton:
        """Mock lifx discovery."""

        def __init__(self, *args, **kwargs):
            """Init connection."""
            if no_device:
                self.device = _mocked_failing_bulb()
            else:
                self.device = device or _mocked_bulb()
            self.device.mac_addr = TARGET_ANY

        async def async_setup(self):
            """Mock setup."""

        def async_stop(self):
            """Mock teardown."""

    @contextmanager
    def _patcher():
        with patch("homeassistant.components.lifx.LIFXConnection", MockLifxConnecton):
            yield

    return _patcher()


def _patch_discovery(device: Light | None = None, no_device: bool = False):
    """Patch out discovery."""

    class MockLifxDiscovery:
        """Mock lifx discovery."""

        def __init__(self, *args, **kwargs):
            """Init discovery."""
            if no_device:
                self.lights = {}
                return
            discovered = device or _mocked_bulb()
            self.lights = {discovered.mac_addr: discovered}

        def start(self):
            """Mock start."""

        def cleanup(self):
            """Mock cleanup."""

    @contextmanager
    def _patcher():
        with (
            patch.object(discovery, "DEFAULT_TIMEOUT", 0),
            patch(
                "homeassistant.components.lifx.discovery.LifxDiscovery",
                MockLifxDiscovery,
            ),
        ):
            yield

    return _patcher()


def _patch_config_flow_try_connect(
    device: Light | None = None, no_device: bool = False
):
    """Patch out discovery."""

    class MockLifxConnection:
        """Mock lifx discovery."""

        def __init__(self, *args, **kwargs):
            """Init connection."""
            if no_device:
                self.device = _mocked_failing_bulb()
            else:
                self.device = device or _mocked_bulb()
            self.device.mac_addr = TARGET_ANY

        async def async_setup(self):
            """Mock setup."""

        def async_stop(self):
            """Mock teardown."""

    @contextmanager
    def _patcher():
        with patch(
            "homeassistant.components.lifx.config_flow.LIFXConnection",
            MockLifxConnection,
        ):
            yield

    return _patcher()
