"""Tests for the lifx integration light platform."""

from datetime import timedelta
from unittest.mock import patch

import aiolifx_effects
import pytest

from homeassistant.components import lifx
from homeassistant.components.lifx import DOMAIN
from homeassistant.components.lifx.const import ATTR_POWER
from homeassistant.components.lifx.light import ATTR_INFRARED, ATTR_ZONES
from homeassistant.components.lifx.manager import (
    ATTR_DIRECTION,
    ATTR_PALETTE,
    ATTR_SATURATION_MAX,
    ATTR_SATURATION_MIN,
    ATTR_SPEED,
    ATTR_THEME,
    SERVICE_EFFECT_COLORLOOP,
    SERVICE_EFFECT_MORPH,
    SERVICE_EFFECT_MOVE,
)
from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_BRIGHTNESS_PCT,
    ATTR_COLOR_MODE,
    ATTR_COLOR_NAME,
    ATTR_COLOR_TEMP,
    ATTR_COLOR_TEMP_KELVIN,
    ATTR_EFFECT,
    ATTR_HS_COLOR,
    ATTR_KELVIN,
    ATTR_RGB_COLOR,
    ATTR_SUPPORTED_COLOR_MODES,
    ATTR_TRANSITION,
    ATTR_XY_COLOR,
    DOMAIN as LIGHT_DOMAIN,
    ColorMode,
)
from homeassistant.const import (
    ATTR_ENTITY_ID,
    CONF_HOST,
    STATE_OFF,
    STATE_ON,
    STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util

from . import (
    IP_ADDRESS,
    MAC_ADDRESS,
    SERIAL,
    MockFailingLifxCommand,
    MockLifxCommand,
    MockMessage,
    _mocked_brightness_bulb,
    _mocked_bulb,
    _mocked_bulb_new_firmware,
    _mocked_clean_bulb,
    _mocked_light_strip,
    _mocked_tile,
    _mocked_white_bulb,
    _patch_config_flow_try_connect,
    _patch_device,
    _patch_discovery,
)

from tests.common import MockConfigEntry, async_fire_time_changed


@pytest.fixture(autouse=True)
def patch_lifx_state_settle_delay():
    """Set asyncio.sleep for state settles to zero."""
    with patch("homeassistant.components.lifx.light.LIFX_STATE_SETTLE_DELAY", 0):
        yield


async def test_light_unique_id(hass: HomeAssistant) -> None:
    """Test a light unique id."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "1.2.3.4"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb()
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"
    entity_registry = er.async_get(hass)
    assert entity_registry.async_get(entity_id).unique_id == SERIAL

    device_registry = dr.async_get(hass)
    device = device_registry.async_get_device(
        identifiers=set(), connections={(dr.CONNECTION_NETWORK_MAC, SERIAL)}
    )
    assert device.identifiers == {(DOMAIN, SERIAL)}


async def test_light_unique_id_new_firmware(hass: HomeAssistant) -> None:
    """Test a light unique id with newer firmware."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "1.2.3.4"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb_new_firmware()
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"
    entity_registry = er.async_get(hass)
    assert entity_registry.async_get(entity_id).unique_id == SERIAL
    device_registry = dr.async_get(hass)
    device = device_registry.async_get_device(
        identifiers=set(),
        connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
    )
    assert device.identifiers == {(DOMAIN, SERIAL)}


async def test_light_strip(hass: HomeAssistant) -> None:
    """Test a light strip."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_light_strip()
    bulb.power_level = 65535
    bulb.color = [65535, 65535, 65535, 65535]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 255
    assert attributes[ATTR_COLOR_MODE] == ColorMode.HS
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert attributes[ATTR_HS_COLOR] == (360.0, 100.0)
    assert attributes[ATTR_RGB_COLOR] == (255, 0, 0)
    assert attributes[ATTR_XY_COLOR] == (0.701, 0.299)

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is True
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
        blocking=True,
    )
    call_dict = bulb.set_color_zones.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 0,
        "color": [],
        "duration": 0,
        "end_index": 0,
        "start_index": 0,
    }
    bulb.set_color_zones.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
        blocking=True,
    )
    call_dict = bulb.set_color_zones.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 0,
        "color": [],
        "duration": 0,
        "end_index": 0,
        "start_index": 0,
    }
    bulb.set_color_zones.reset_mock()

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
        blocking=True,
    )
    # Single color uses the fast path
    assert bulb.set_color.calls[0][0][0] == [1820, 19660, 65535, 3500]
    bulb.set_color.reset_mock()
    assert len(bulb.set_color_zones.calls) == 0

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 10, 30)},
        blocking=True,
    )
    # Single color uses the fast path
    assert bulb.set_color.calls[0][0][0] == [64643, 62964, 65535, 3500]
    bulb.set_color.reset_mock()
    assert len(bulb.set_color_zones.calls) == 0

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.3, 0.7)},
        blocking=True,
    )
    # Single color uses the fast path
    assert bulb.set_color.calls[0][0][0] == [15848, 65535, 65535, 3500]
    bulb.set_color.reset_mock()
    assert len(bulb.set_color_zones.calls) == 0

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128},
        blocking=True,
    )
    # multiple zones in effect and we are changing the brightness
    # we need to do each zone individually
    assert len(bulb.set_color.calls) == 0
    call_dict = bulb.set_color_zones.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 0,
        "color": [0, 65535, 32896, 3500],
        "duration": 0,
        "end_index": 0,
        "start_index": 0,
    }
    call_dict = bulb.set_color_zones.calls[1][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 0,
        "color": [54612, 65535, 32896, 3500],
        "duration": 0,
        "end_index": 1,
        "start_index": 1,
    }
    call_dict = bulb.set_color_zones.calls[7][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 1,
        "color": [46420, 65535, 32896, 3500],
        "duration": 0,
        "end_index": 7,
        "start_index": 7,
    }
    bulb.set_color_zones.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_RGB_COLOR: (255, 255, 255),
            ATTR_ZONES: [0, 2],
        },
        blocking=True,
    )
    # set a two zones
    assert len(bulb.set_color.calls) == 0
    call_dict = bulb.set_color_zones.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 0,
        "color": [0, 0, 65535, 3500],
        "duration": 0,
        "end_index": 0,
        "start_index": 0,
    }
    call_dict = bulb.set_color_zones.calls[1][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 1,
        "color": [0, 0, 65535, 3500],
        "duration": 0,
        "end_index": 2,
        "start_index": 2,
    }
    bulb.set_color_zones.reset_mock()

    bulb.get_color_zones.reset_mock()
    bulb.set_power.reset_mock()

    bulb.power_level = 0
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3]},
        blocking=True,
    )
    # set a one zone
    assert len(bulb.set_power.calls) == 2
    assert len(bulb.get_color_zones.calls) == 2
    assert len(bulb.set_color.calls) == 0
    call_dict = bulb.set_color_zones.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "apply": 1,
        "color": [0, 0, 65535, 3500],
        "duration": 0,
        "end_index": 3,
        "start_index": 3,
    }
    bulb.get_color_zones.reset_mock()
    bulb.set_power.reset_mock()
    bulb.set_color_zones.reset_mock()

    bulb.set_color_zones = MockFailingLifxCommand(bulb)
    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            DOMAIN,
            "set_state",
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_RGB_COLOR: (255, 255, 255),
                ATTR_ZONES: [3],
            },
            blocking=True,
        )

    bulb.set_color_zones = MockLifxCommand(bulb)
    bulb.get_color_zones = MockFailingLifxCommand(bulb)

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            DOMAIN,
            "set_state",
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_RGB_COLOR: (255, 255, 255),
                ATTR_ZONES: [3],
            },
            blocking=True,
        )

    bulb.get_color_zones = MockLifxCommand(bulb)
    bulb.get_color = MockFailingLifxCommand(bulb)

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            DOMAIN,
            "set_state",
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_RGB_COLOR: (255, 255, 255),
                ATTR_ZONES: [3],
            },
            blocking=True,
        )


async def test_extended_multizone_messages(hass: HomeAssistant) -> None:
    """Test a light strip that supports extended multizone."""
    config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    config_entry.add_to_hass(hass)
    bulb = _mocked_light_strip()
    bulb.product = 38  # LIFX Beam
    bulb.power_level = 65535
    bulb.color = [65535, 65535, 65535, 3500]
    bulb.color_zones = [(65535, 65535, 65535, 3500)] * 8
    bulb.zones_count = 8
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 255
    assert attributes[ATTR_COLOR_MODE] == ColorMode.HS
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert attributes[ATTR_HS_COLOR] == (360.0, 100.0)
    assert attributes[ATTR_RGB_COLOR] == (255, 0, 0)
    assert attributes[ATTR_XY_COLOR] == (0.701, 0.299)

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is True
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
        blocking=True,
    )
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1

    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
        blocking=True,
    )
    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1
    bulb.set_color.reset_mock()
    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
        blocking=True,
    )

    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1
    bulb.set_color.reset_mock()
    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 10, 30)},
        blocking=True,
    )
    # always use a set_extended_color_zones
    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1
    bulb.set_color.reset_mock()
    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.3, 0.7)},
        blocking=True,
    )
    # Single color uses the fast path
    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1
    bulb.set_color.reset_mock()
    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()

    bulb.color_zones = [
        (0, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (54612, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
        (46420, 65535, 65535, 3500),
    ]

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128},
        blocking=True,
    )

    # always use set_extended_color_zones
    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1
    bulb.set_color.reset_mock()
    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_RGB_COLOR: (255, 255, 255),
            ATTR_ZONES: [0, 2],
        },
        blocking=True,
    )
    # set a two zones
    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0
    assert len(bulb.set_extended_color_zones.calls) == 1
    bulb.set_color.reset_mock()
    bulb.set_color_zones.reset_mock()
    bulb.set_extended_color_zones.reset_mock()

    bulb.power_level = 0
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3]},
        blocking=True,
    )
    # set a one zone
    assert len(bulb.set_power.calls) == 2
    assert len(bulb.get_color_zones.calls) == 0
    assert len(bulb.set_color.calls) == 0
    assert len(bulb.set_color_zones.calls) == 0

    bulb.get_color_zones.reset_mock()
    bulb.set_power.reset_mock()
    bulb.set_color_zones.reset_mock()

    bulb.set_extended_color_zones = MockFailingLifxCommand(bulb)

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            DOMAIN,
            "set_state",
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_RGB_COLOR: (255, 255, 255),
                ATTR_ZONES: [3],
            },
            blocking=True,
        )

    bulb.set_extended_color_zones = MockLifxCommand(bulb)
    bulb.get_extended_color_zones = MockFailingLifxCommand(bulb)

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            DOMAIN,
            "set_state",
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_RGB_COLOR: (255, 255, 255),
                ATTR_ZONES: [3],
            },
            blocking=True,
        )


async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None:
    """Test the firmware flame and morph effects on a matrix device."""
    config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    config_entry.add_to_hass(hass)
    bulb = _mocked_tile()
    bulb.power_level = 0
    bulb.color = [65535, 65535, 65535, 65535]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_flame"},
        blocking=True,
    )

    assert len(bulb.set_power.calls) == 1
    assert len(bulb.set_tile_effect.calls) == 1

    call_dict = bulb.set_tile_effect.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "effect": 3,
        "speed": 3,
        "palette": [],
    }
    bulb.get_tile_effect.reset_mock()
    bulb.set_tile_effect.reset_mock()
    bulb.set_power.reset_mock()

    bulb.power_level = 0
    await hass.services.async_call(
        DOMAIN,
        SERVICE_EFFECT_MORPH,
        {ATTR_ENTITY_ID: entity_id, ATTR_SPEED: 4, ATTR_THEME: "autumn"},
        blocking=True,
    )

    bulb.power_level = 65535
    bulb.effect = {
        "effect": "MORPH",
        "speed": 4.0,
        "palette": [
            (5643, 65535, 32768, 3500),
            (15109, 65535, 32768, 3500),
            (8920, 65535, 32768, 3500),
            (10558, 65535, 32768, 3500),
        ],
    }
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
    await hass.async_block_till_done()

    state = hass.states.get(entity_id)
    assert state.state == STATE_ON

    assert len(bulb.set_power.calls) == 1
    assert len(bulb.set_tile_effect.calls) == 1
    call_dict = bulb.set_tile_effect.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "effect": 2,
        "speed": 4,
        "palette": [
            (5643, 65535, 32768, 3500),
            (15109, 65535, 32768, 3500),
            (8920, 65535, 32768, 3500),
            (10558, 65535, 32768, 3500),
        ],
    }
    bulb.get_tile_effect.reset_mock()
    bulb.set_tile_effect.reset_mock()
    bulb.set_power.reset_mock()

    bulb.power_level = 0
    await hass.services.async_call(
        DOMAIN,
        SERVICE_EFFECT_MORPH,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_SPEED: 6,
            ATTR_PALETTE: [
                (0, 100, 255, 3500),
                (60, 100, 255, 3500),
                (120, 100, 255, 3500),
                (180, 100, 255, 3500),
                (240, 100, 255, 3500),
                (300, 100, 255, 3500),
            ],
        },
        blocking=True,
    )

    bulb.power_level = 65535
    bulb.effect = {
        "effect": "MORPH",
        "speed": 6,
        "palette": [
            (0, 65535, 65535, 3500),
            (10922, 65535, 65535, 3500),
            (21845, 65535, 65535, 3500),
            (32768, 65535, 65535, 3500),
            (43690, 65535, 65535, 3500),
            (54612, 65535, 65535, 3500),
        ],
    }
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
    await hass.async_block_till_done()

    state = hass.states.get(entity_id)
    assert state.state == STATE_ON

    assert len(bulb.set_power.calls) == 1
    assert len(bulb.set_tile_effect.calls) == 1
    call_dict = bulb.set_tile_effect.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "effect": 2,
        "speed": 6,
        "palette": [
            (0, 65535, 65535, 3500),
            (10922, 65535, 65535, 3500),
            (21845, 65535, 65535, 3500),
            (32768, 65535, 65535, 3500),
            (43690, 65535, 65535, 3500),
            (54613, 65535, 65535, 3500),
        ],
    }
    bulb.get_tile_effect.reset_mock()
    bulb.set_tile_effect.reset_mock()
    bulb.set_power.reset_mock()


async def test_lightstrip_move_effect(hass: HomeAssistant) -> None:
    """Test the firmware move effect on a light strip."""
    config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    config_entry.add_to_hass(hass)
    bulb = _mocked_light_strip()
    bulb.product = 38
    bulb.power_level = 0
    bulb.color = [65535, 65535, 65535, 65535]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_move"},
        blocking=True,
    )

    assert len(bulb.set_power.calls) == 1
    assert len(bulb.set_multizone_effect.calls) == 1

    call_dict = bulb.set_multizone_effect.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "effect": 1,
        "speed": 3.0,
        "direction": 0,
    }

    bulb.get_multizone_effect.reset_mock()
    bulb.set_multizone_effect.reset_mock()
    bulb.set_power.reset_mock()

    bulb.power_level = 0
    await hass.services.async_call(
        DOMAIN,
        SERVICE_EFFECT_MOVE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_SPEED: 4.5,
            ATTR_DIRECTION: "left",
            ATTR_THEME: "sports",
        },
        blocking=True,
    )

    bulb.power_level = 65535
    bulb.effect = {"name": "MOVE", "speed": 4.5, "direction": "Left"}
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
    await hass.async_block_till_done()

    state = hass.states.get(entity_id)
    assert state.state == STATE_ON

    assert len(bulb.set_power.calls) == 1
    assert len(bulb.set_extended_color_zones.calls) == 1
    assert len(bulb.set_multizone_effect.calls) == 1
    call_dict = bulb.set_multizone_effect.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "effect": 1,
        "speed": 4.5,
        "direction": 1,
    }
    bulb.get_multizone_effect.reset_mock()
    bulb.set_multizone_effect.reset_mock()
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_stop"},
        blocking=True,
    )
    assert len(bulb.set_power.calls) == 0
    assert len(bulb.set_multizone_effect.calls) == 1
    call_dict = bulb.set_multizone_effect.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {
        "effect": 0,
        "speed": 3.0,
        "direction": 0,
    }
    bulb.get_multizone_effect.reset_mock()
    bulb.set_multizone_effect.reset_mock()
    bulb.set_power.reset_mock()


async def test_color_light_with_temp(
    hass: HomeAssistant, mock_effect_conductor
) -> None:
    """Test a color light with temp."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb()
    bulb.power_level = 65535
    bulb.color = [65535, 65535, 65535, 65535]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 255
    assert attributes[ATTR_COLOR_MODE] == ColorMode.HS
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert attributes[ATTR_HS_COLOR] == (360.0, 100.0)
    assert attributes[ATTR_RGB_COLOR] == (255, 0, 0)
    assert attributes[ATTR_XY_COLOR] == (0.701, 0.299)

    bulb.color = [32000, None, 32000, 6000]

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is True
    bulb.set_power.reset_mock()
    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert attributes[ATTR_HS_COLOR] == (30.754, 7.122)
    assert attributes[ATTR_RGB_COLOR] == (255, 246, 236)
    assert attributes[ATTR_XY_COLOR] == (0.34, 0.339)
    bulb.color = [65535, 65535, 65535, 65535]

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is True
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [65535, 65535, 25700, 65535]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [1820, 19660, 65535, 3500]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 30, 80)},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [63107, 57824, 65535, 3500]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.46, 0.376)},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [4956, 30583, 65535, 3500]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_colorloop"},
        blocking=True,
    )
    start_call = mock_effect_conductor.start.mock_calls
    first_call = start_call[0][1]
    assert isinstance(first_call[0], aiolifx_effects.EffectColorloop)
    assert first_call[1][0] == bulb
    mock_effect_conductor.start.reset_mock()
    mock_effect_conductor.stop.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        SERVICE_EFFECT_COLORLOOP,
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_PCT: 50, ATTR_SATURATION_MAX: 90},
        blocking=True,
    )
    start_call = mock_effect_conductor.start.mock_calls
    first_call = start_call[0][1]
    assert isinstance(first_call[0], aiolifx_effects.EffectColorloop)
    assert first_call[1][0] == bulb
    mock_effect_conductor.start.reset_mock()
    mock_effect_conductor.stop.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        SERVICE_EFFECT_COLORLOOP,
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128, ATTR_SATURATION_MIN: 90},
        blocking=True,
    )
    start_call = mock_effect_conductor.start.mock_calls
    first_call = start_call[0][1]
    assert isinstance(first_call[0], aiolifx_effects.EffectColorloop)
    assert first_call[1][0] == bulb
    mock_effect_conductor.start.reset_mock()
    mock_effect_conductor.stop.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_pulse"},
        blocking=True,
    )
    assert len(mock_effect_conductor.stop.mock_calls) == 1
    start_call = mock_effect_conductor.start.mock_calls
    first_call = start_call[0][1]
    assert isinstance(first_call[0], aiolifx_effects.EffectPulse)
    assert first_call[1][0] == bulb
    mock_effect_conductor.start.reset_mock()
    mock_effect_conductor.stop.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_stop"},
        blocking=True,
    )
    assert len(mock_effect_conductor.stop.mock_calls) == 2


async def test_white_bulb(hass: HomeAssistant) -> None:
    """Test a white bulb."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_white_bulb()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
    ]
    assert attributes[ATTR_COLOR_TEMP_KELVIN] == 6000
    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is True
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, None, 25700, 6000]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 400},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, 0, 32000, 2500]
    bulb.set_color.reset_mock()


async def test_config_zoned_light_strip_fails(hass: HomeAssistant) -> None:
    """Test we handle failure to update zones."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    light_strip = _mocked_light_strip()
    entity_id = "light.my_bulb"

    class MockFailingLifxCommand:
        """Mock a lifx command that fails on the 3rd try."""

        def __init__(self, bulb, **kwargs):
            """Init command."""
            self.bulb = bulb
            self.call_count = 0

        def __call__(self, callb=None, *args, **kwargs):
            """Call command."""
            self.call_count += 1
            response = None if self.call_count >= 3 else MockMessage()
            if callb:
                callb(self.bulb, response)

    light_strip.get_color_zones = MockFailingLifxCommand(light_strip)

    with _patch_discovery(device=light_strip), _patch_device(device=light_strip):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        entity_registry = er.async_get(hass)
        assert entity_registry.async_get(entity_id).unique_id == SERIAL
        assert hass.states.get(entity_id).state == STATE_OFF

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
        await hass.async_block_till_done()
        assert hass.states.get(entity_id).state == STATE_UNAVAILABLE


async def test_white_light_fails(hass: HomeAssistant) -> None:
    """Test we handle failure to power on off."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_white_bulb()
    entity_id = "light.my_bulb"

    bulb.set_power = MockFailingLifxCommand(bulb)

    with _patch_discovery(device=bulb), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        entity_registry = er.async_get(hass)
        assert entity_registry.async_get(entity_id).unique_id == SERIAL
        assert hass.states.get(entity_id).state == STATE_OFF
        with pytest.raises(HomeAssistantError):
            await hass.services.async_call(
                LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
            )
        assert bulb.set_power.calls[0][0][0] is True
        bulb.set_power.reset_mock()

        bulb.set_power = MockLifxCommand(bulb)
        bulb.set_color = MockFailingLifxCommand(bulb)

        with pytest.raises(HomeAssistantError):
            await hass.services.async_call(
                LIGHT_DOMAIN,
                "turn_on",
                {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP_KELVIN: 6000},
                blocking=True,
            )
        assert bulb.set_color.calls[0][0][0] == [1, 0, 3, 6000]
        bulb.set_color.reset_mock()


async def test_brightness_bulb(hass: HomeAssistant) -> None:
    """Test a brightness only bulb."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_brightness_bulb()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.BRIGHTNESS
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.BRIGHTNESS,
    ]
    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is True
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, None, 25700, 6000]
    bulb.set_color.reset_mock()


async def test_transitions_brightness_only(hass: HomeAssistant) -> None:
    """Test transitions with a brightness only device."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_brightness_bulb()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.BRIGHTNESS
    assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.BRIGHTNESS,
    ]
    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()
    bulb.power_level = 0

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, ATTR_BRIGHTNESS: 100},
        blocking=True,
    )
    assert bulb.set_power.calls[0][0][0] is True
    call_dict = bulb.set_power.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 5000}
    bulb.set_power.reset_mock()

    bulb.power_level = 0

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, ATTR_BRIGHTNESS: 200},
        blocking=True,
    )
    assert bulb.set_power.calls[0][0][0] is True
    call_dict = bulb.set_power.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 5000}
    bulb.set_power.reset_mock()

    await hass.async_block_till_done()
    bulb.get_color.reset_mock()

    # Ensure we force an update after the transition
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5))
    await hass.async_block_till_done()
    assert len(bulb.get_color.calls) == 2


async def test_transitions_color_bulb(hass: HomeAssistant) -> None:
    """Test transitions with a color bulb."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb_new_firmware()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()
    bulb.power_level = 0

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_off",
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TRANSITION: 5,
        },
        blocking=True,
    )
    assert bulb.set_power.calls[0][0][0] is False
    call_dict = bulb.set_power.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 0}  # already off
    bulb.set_power.reset_mock()
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {
            ATTR_RGB_COLOR: (255, 5, 10),
            ATTR_ENTITY_ID: entity_id,
            ATTR_TRANSITION: 5,
            ATTR_BRIGHTNESS: 100,
        },
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [65316, 64249, 25700, 3500]
    assert bulb.set_power.calls[0][0][0] is True
    call_dict = bulb.set_power.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 5000}
    bulb.set_power.reset_mock()
    bulb.set_color.reset_mock()

    bulb.power_level = 12800

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {
            ATTR_RGB_COLOR: (5, 5, 10),
            ATTR_ENTITY_ID: entity_id,
            ATTR_TRANSITION: 5,
            ATTR_BRIGHTNESS: 200,
        },
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [43690, 32767, 51400, 3500]
    call_dict = bulb.set_color.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 5000}
    bulb.set_power.reset_mock()
    bulb.set_color.reset_mock()

    await hass.async_block_till_done()
    bulb.get_color.reset_mock()

    # Ensure we force an update after the transition
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5))
    await hass.async_block_till_done()
    assert len(bulb.get_color.calls) == 2

    bulb.set_power.reset_mock()
    bulb.set_color.reset_mock()
    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_off",
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TRANSITION: 5,
        },
        blocking=True,
    )
    assert bulb.set_power.calls[0][0][0] is False
    call_dict = bulb.set_power.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 5000}
    bulb.set_power.reset_mock()
    bulb.set_color.reset_mock()


async def test_lifx_set_state_color(hass: HomeAssistant) -> None:
    """Test lifx.set_state works with color names and RGB."""
    config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    config_entry.add_to_hass(hass)
    bulb = _mocked_bulb_new_firmware()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 2700]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    # brightness should convert from 8 to 16 bits
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, None, 65535, 2700]
    bulb.set_color.reset_mock()

    # brightness_pct should convert into 16 bit
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_PCT: 90},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, None, 59110, 2700]
    bulb.set_color.reset_mock()

    # color name should turn into hue, saturation
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_NAME: "red", ATTR_BRIGHTNESS_PCT: 100},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [0, 65535, 65535, 3500]
    bulb.set_color.reset_mock()

    # unknown color name should reset back to neutral white, i.e. 3500K
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_NAME: "deepblack"},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [0, 0, 32000, 3500]
    bulb.set_color.reset_mock()

    # RGB should convert to hue, saturation
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (0, 255, 0)},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [21845, 65535, 32000, 3500]
    bulb.set_color.reset_mock()

    # XY should convert to hue, saturation
    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.34, 0.339)},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [5461, 5139, 32000, 3500]
    bulb.set_color.reset_mock()


async def test_lifx_set_state_kelvin(hass: HomeAssistant) -> None:
    """Test set_state works with old and new kelvin parameter names."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb_new_firmware()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255, ATTR_KELVIN: 3500},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, 0, 65535, 3500]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100, ATTR_COLOR_TEMP_KELVIN: 2700},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, 0, 25700, 2700]
    bulb.set_color.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255, ATTR_COLOR_TEMP: 400},
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [32000, 0, 65535, 2500]
    bulb.set_color.reset_mock()


async def test_infrared_color_bulb(hass: HomeAssistant) -> None:
    """Test setting infrared with a color bulb."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb_new_firmware()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"
    attributes = state.attributes
    assert attributes[ATTR_BRIGHTNESS] == 125
    assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
    await hass.services.async_call(
        LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True
    )
    assert bulb.set_power.calls[0][0][0] is False
    bulb.set_power.reset_mock()

    await hass.services.async_call(
        DOMAIN,
        "set_state",
        {
            ATTR_INFRARED: 100,
            ATTR_ENTITY_ID: entity_id,
            ATTR_BRIGHTNESS: 100,
        },
        blocking=True,
    )
    assert bulb.set_infrared.calls[0][0][0] == 25700


async def test_color_bulb_is_actually_off(hass: HomeAssistant) -> None:
    """Test setting a color when we think a bulb is on but its actually off."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb_new_firmware()
    bulb.power_level = 65535
    bulb.color = [32000, None, 32000, 6000]
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"

    state = hass.states.get(entity_id)
    assert state.state == "on"

    class MockLifxCommandActuallyOff:
        """Mock a lifx command that will update our power level state."""

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

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

    bulb.set_color = MockLifxCommandActuallyOff(bulb)

    await hass.services.async_call(
        LIGHT_DOMAIN,
        "turn_on",
        {
            ATTR_RGB_COLOR: (100, 100, 100),
            ATTR_ENTITY_ID: entity_id,
            ATTR_BRIGHTNESS: 100,
        },
        blocking=True,
    )
    assert bulb.set_color.calls[0][0][0] == [0, 0, 25700, 3500]
    assert len(bulb.set_power.calls) == 1


async def test_clean_bulb(hass: HomeAssistant) -> None:
    """Test setting HEV cycle state on Clean bulbs."""
    config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    config_entry.add_to_hass(hass)
    bulb = _mocked_clean_bulb()
    bulb.power_level = 0
    bulb.hev_cycle = {"duration": 7200, "remaining": 0, "last_power": False}
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"
    state = hass.states.get(entity_id)
    assert state.state == "off"
    await hass.services.async_call(
        DOMAIN,
        "set_hev_cycle_state",
        {ATTR_ENTITY_ID: entity_id, ATTR_POWER: True},
        blocking=True,
    )

    call_dict = bulb.set_hev_cycle.calls[0][1]
    call_dict.pop("callb")
    assert call_dict == {"duration": 0, "enable": True}
    bulb.set_hev_cycle.reset_mock()


async def test_set_hev_cycle_state_fails_for_color_bulb(hass: HomeAssistant) -> None:
    """Test that set_hev_cycle_state fails for a non-Clean bulb."""
    config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    config_entry.add_to_hass(hass)
    bulb = _mocked_bulb()
    bulb.power_level = 0
    with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
        device=bulb
    ), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()

    entity_id = "light.my_bulb"
    state = hass.states.get(entity_id)
    assert state.state == "off"

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            DOMAIN,
            "set_hev_cycle_state",
            {ATTR_ENTITY_ID: entity_id, ATTR_POWER: True},
            blocking=True,
        )
