"""Test zha light."""

# pylint: disable=too-many-lines

from __future__ import annotations

import asyncio
import contextlib
import logging
from typing import Any
from unittest.mock import AsyncMock, call, patch, sentinel

import pytest
from zigpy.profiles import zha
from zigpy.zcl.clusters import general, lighting
import zigpy.zcl.foundation as zcl_f
import zigpy.zdo.types as zdo_t

from tests.common import (
    SIG_EP_INPUT,
    SIG_EP_OUTPUT,
    SIG_EP_PROFILE,
    SIG_EP_TYPE,
    create_mock_zigpy_device,
    get_entity,
    get_group_entity,
    group_entity_availability_test,
    join_zigpy_device,
    send_attributes_report,
    update_attribute_cache,
)
from zha.application import Platform
from zha.application.gateway import Gateway
from zha.application.platforms import GroupEntity, PlatformEntity
from zha.application.platforms.light.const import FLASH_EFFECTS, ColorMode, FlashMode
from zha.zigbee.device import Device
from zha.zigbee.group import Group, GroupMemberReference

ON = 1
OFF = 0
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e9"
IEEE_GROUPABLE_DEVICE3 = "03:2d:6f:00:0a:90:69:e7"

_LOGGER = logging.getLogger(__name__)

LIGHT_ON_OFF = {
    1: {
        SIG_EP_PROFILE: zha.PROFILE_ID,
        SIG_EP_TYPE: zha.DeviceType.ON_OFF_LIGHT,
        SIG_EP_INPUT: [
            general.Basic.cluster_id,
            general.Identify.cluster_id,
            general.OnOff.cluster_id,
        ],
        SIG_EP_OUTPUT: [general.Ota.cluster_id],
    }
}

LIGHT_LEVEL = {
    1: {
        SIG_EP_PROFILE: zha.PROFILE_ID,
        SIG_EP_TYPE: zha.DeviceType.DIMMABLE_LIGHT,
        SIG_EP_INPUT: [
            general.Basic.cluster_id,
            general.LevelControl.cluster_id,
            general.OnOff.cluster_id,
        ],
        SIG_EP_OUTPUT: [general.Ota.cluster_id],
    }
}

LIGHT_COLOR = {
    1: {
        SIG_EP_PROFILE: zha.PROFILE_ID,
        SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
        SIG_EP_INPUT: [
            general.Basic.cluster_id,
            general.Identify.cluster_id,
            general.LevelControl.cluster_id,
            general.OnOff.cluster_id,
            lighting.Color.cluster_id,
        ],
        SIG_EP_OUTPUT: [general.Ota.cluster_id],
    }
}


async def coordinator_mock(
    zha_gateway: Gateway,
) -> Device:
    """Test zha light platform."""

    zigpy_device = create_mock_zigpy_device(
        zha_gateway,
        {
            1: {
                SIG_EP_INPUT: [general.Groups.cluster_id],
                SIG_EP_OUTPUT: [],
                SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
                SIG_EP_PROFILE: zha.PROFILE_ID,
            }
        },
        ieee="00:15:8d:00:02:32:4f:32",
        nwk=0x0000,
        node_descriptor=zdo_t.NodeDescriptor(
            logical_type=zdo_t.LogicalType.Router,
            complex_descriptor_available=0,
            user_descriptor_available=0,
            reserved=0,
            aps_flags=0,
            frequency_band=zdo_t.NodeDescriptor.FrequencyBand.Freq2400MHz,
            mac_capability_flags=(
                zdo_t.NodeDescriptor.MACCapabilityFlags.FullFunctionDevice
                | zdo_t.NodeDescriptor.MACCapabilityFlags.MainsPowered
                | zdo_t.NodeDescriptor.MACCapabilityFlags.RxOnWhenIdle
                | zdo_t.NodeDescriptor.MACCapabilityFlags.AllocateAddress
            ),
            manufacturer_code=4107,
            maximum_buffer_size=82,
            maximum_incoming_transfer_size=128,
            server_mask=11264,
            maximum_outgoing_transfer_size=128,
            descriptor_capability_field=zdo_t.NodeDescriptor.DescriptorCapability.NONE,
        ),
    )
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    return zha_device


async def device_light_1_mock(
    zha_gateway: Gateway,
) -> Device:
    """Test zha light platform."""

    zigpy_device = create_mock_zigpy_device(
        zha_gateway,
        {
            1: {
                SIG_EP_INPUT: [
                    general.OnOff.cluster_id,
                    general.LevelControl.cluster_id,
                    lighting.Color.cluster_id,
                    general.Groups.cluster_id,
                    general.Identify.cluster_id,
                ],
                SIG_EP_OUTPUT: [],
                SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
                SIG_EP_PROFILE: zha.PROFILE_ID,
            }
        },
        ieee=IEEE_GROUPABLE_DEVICE,
        manufacturer="Philips",
        model="LWA004",
        nwk=0xB79D,
    )
    color_cluster = zigpy_device.endpoints[1].light_color
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": (
            lighting.Color.ColorCapabilities.Color_temperature
            | lighting.Color.ColorCapabilities.XY_attributes
        )
    }
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    return zha_device


async def device_light_2_mock(
    zha_gateway: Gateway,
) -> Device:
    """Test zha light platform."""

    zigpy_device = create_mock_zigpy_device(
        zha_gateway,
        {
            1: {
                SIG_EP_INPUT: [
                    general.OnOff.cluster_id,
                    general.LevelControl.cluster_id,
                    lighting.Color.cluster_id,
                    general.Groups.cluster_id,
                    general.Identify.cluster_id,
                ],
                SIG_EP_OUTPUT: [],
                SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
                SIG_EP_PROFILE: zha.PROFILE_ID,
            }
        },
        ieee=IEEE_GROUPABLE_DEVICE2,
        manufacturer="sengled",
        nwk=0xC79E,
    )
    color_cluster = zigpy_device.endpoints[1].light_color
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": lighting.Color.ColorCapabilities.Color_temperature
        | lighting.Color.ColorCapabilities.XY_attributes
    }
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    return zha_device


async def device_light_3_mock(
    zha_gateway: Gateway,
) -> Device:
    """Test zha light platform."""

    zigpy_device = create_mock_zigpy_device(
        zha_gateway,
        {
            1: {
                SIG_EP_INPUT: [
                    general.OnOff.cluster_id,
                    general.LevelControl.cluster_id,
                    lighting.Color.cluster_id,
                    general.Groups.cluster_id,
                    general.Identify.cluster_id,
                ],
                SIG_EP_OUTPUT: [],
                SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
                SIG_EP_PROFILE: zha.PROFILE_ID,
            }
        },
        ieee=IEEE_GROUPABLE_DEVICE3,
        nwk=0xB89F,
    )
    color_cluster = zigpy_device.endpoints[1].light_color
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": (
            lighting.Color.ColorCapabilities.Color_temperature
            | lighting.Color.ColorCapabilities.XY_attributes
            | lighting.Color.ColorCapabilities.Color_loop
        )
    }

    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    return zha_device


async def eWeLink_light_mock(
    zha_gateway: Gateway,
):
    """Mock eWeLink light."""

    zigpy_device = create_mock_zigpy_device(
        zha_gateway,
        {
            1: {
                SIG_EP_INPUT: [
                    general.OnOff.cluster_id,
                    general.LevelControl.cluster_id,
                    lighting.Color.cluster_id,
                    general.Groups.cluster_id,
                    general.Identify.cluster_id,
                ],
                SIG_EP_OUTPUT: [],
                SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
                SIG_EP_PROFILE: zha.PROFILE_ID,
            }
        },
        ieee="03:2d:6f:00:0a:90:69:e3",
        manufacturer="eWeLink",
        nwk=0xB79D,
    )
    color_cluster = zigpy_device.endpoints[1].light_color
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": (
            lighting.Color.ColorCapabilities.Color_temperature
            | lighting.Color.ColorCapabilities.XY_attributes
        ),
        "color_temp_physical_min": 0,
        "color_temp_physical_max": 0,
    }
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    return zha_device


async def test_light_refresh(
    zha_gateway: Gateway,
):
    """Test zha light platform refresh."""
    zigpy_device = create_mock_zigpy_device(zha_gateway, LIGHT_ON_OFF)
    on_off_cluster = zigpy_device.endpoints[1].on_off
    on_off_cluster.PLUGGED_ATTR_READS = {"on_off": 0}
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)

    entity = get_entity(zha_device, platform=Platform.LIGHT)
    assert bool(entity.state["on"]) is False

    on_off_cluster.read_attributes.reset_mock()

    # not enough time passed
    await asyncio.sleep(60)  # 1 minute
    await zha_gateway.async_block_till_done()
    assert on_off_cluster.read_attributes.call_count == 0
    assert on_off_cluster.read_attributes.await_count == 0
    assert bool(entity.state["on"]) is False

    # 1 interval - at least 1 call
    on_off_cluster.PLUGGED_ATTR_READS = {"on_off": 1}
    await asyncio.sleep(4800)  # 80 minutes
    await zha_gateway.async_block_till_done()
    assert on_off_cluster.read_attributes.call_count >= 1
    assert on_off_cluster.read_attributes.await_count >= 1
    assert bool(entity.state["on"]) is True

    # 2 intervals - at least 2 calls
    on_off_cluster.PLUGGED_ATTR_READS = {"on_off": 0}
    await asyncio.sleep(4800)  # 80 minutes
    await zha_gateway.async_block_till_done()
    assert on_off_cluster.read_attributes.call_count >= 2
    assert on_off_cluster.read_attributes.await_count >= 2
    assert bool(entity.state["on"]) is False

    read_call_count = on_off_cluster.read_attributes.call_count
    read_await_count = on_off_cluster.read_attributes.await_count

    entity.disable()

    assert entity.enabled is False

    on_off_cluster.PLUGGED_ATTR_READS = {"on_off": 1}
    await asyncio.sleep(4800)  # 80 minutes
    await zha_gateway.async_block_till_done()
    assert on_off_cluster.read_attributes.call_count == read_call_count
    assert on_off_cluster.read_attributes.await_count == read_await_count
    assert bool(entity.state["on"]) is False

    entity.enable()

    assert entity.enabled is True

    await asyncio.sleep(4800)  # 80 minutes
    await zha_gateway.async_block_till_done()
    assert on_off_cluster.read_attributes.call_count > read_call_count
    assert on_off_cluster.read_attributes.await_count > read_await_count
    assert bool(entity.state["on"]) is True


# TODO reporting is not checked
@patch(
    "zigpy.zcl.clusters.lighting.Color.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.Identify.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@pytest.mark.parametrize(
    "device, reporting",
    [(LIGHT_ON_OFF, (1, 0, 0)), (LIGHT_LEVEL, (1, 1, 0)), (LIGHT_COLOR, (1, 1, 3))],
)
async def test_light(
    zha_gateway: Gateway,
    device: dict,
    reporting: tuple,  # pylint: disable=unused-argument
) -> None:
    """Test zha light platform."""

    # create zigpy devices
    zigpy_device = create_mock_zigpy_device(zha_gateway, device)
    cluster_color: lighting.Color = getattr(
        zigpy_device.endpoints[1], "light_color", None
    )
    if cluster_color:
        cluster_color.PLUGGED_ATTR_READS = {
            "color_temperature": 100,
            "color_temp_physical_min": 0,
            "color_temp_physical_max": 600,
            "color_capabilities": (
                lighting.ColorCapabilities.XY_attributes
                | lighting.ColorCapabilities.Color_temperature
                | lighting.ColorCapabilities.Hue_and_saturation
            ),
        }
        update_attribute_cache(cluster_color)
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)

    cluster_on_off: general.OnOff = zigpy_device.endpoints[1].on_off
    cluster_level: general.LevelControl = getattr(
        zigpy_device.endpoints[1], "level", None
    )
    cluster_identify: general.Identify = getattr(
        zigpy_device.endpoints[1], "identify", None
    )

    entity = get_entity(zha_device, platform=Platform.LIGHT)
    assert bool(entity.state["on"]) is False

    # test turning the lights on and off from the light
    await async_test_on_off_from_light(zha_gateway, cluster_on_off, entity)

    # test turning the lights on and off from the client
    await async_test_on_off_from_client(zha_gateway, cluster_on_off, entity)
    await _async_shift_time(zha_gateway)

    # test short flashing the lights from the client
    if cluster_identify:
        await async_test_flash_from_client(
            zha_gateway, cluster_identify, entity, FlashMode.SHORT
        )
        await _async_shift_time(zha_gateway)

    # test turning the lights on and off from the client
    if cluster_level:
        await async_test_level_on_off_from_client(
            zha_gateway, cluster_on_off, cluster_level, entity
        )
        await _async_shift_time(zha_gateway)

        # test getting a brightness change from the network
        await async_test_on_from_light(zha_gateway, cluster_on_off, entity)
        await async_test_dimmer_from_light(
            zha_gateway, cluster_level, entity, 150, True
        )

    await async_test_off_from_client(zha_gateway, cluster_on_off, entity)
    await _async_shift_time(zha_gateway)

    # test long flashing the lights from the client
    if cluster_identify:
        await async_test_flash_from_client(
            zha_gateway, cluster_identify, entity, FlashMode.LONG
        )
        await _async_shift_time(zha_gateway)
        await async_test_flash_from_client(
            zha_gateway, cluster_identify, entity, FlashMode.SHORT
        )
        await _async_shift_time(zha_gateway)

    if cluster_color:
        # test color temperature from the client with transition
        assert entity.state["brightness"] != 50
        assert entity.state["color_temp"] != 200
        await entity.async_turn_on(brightness=50, transition=10, color_temp=200)
        await zha_gateway.async_block_till_done()
        assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
        assert entity.state["brightness"] == 50
        assert entity.state["color_temp"] == 200
        assert bool(entity.state["on"]) is True
        assert cluster_color.request.call_count == 1
        assert cluster_color.request.await_count == 1
        assert cluster_color.request.call_args == call(
            False,
            10,
            cluster_color.commands_by_name["move_to_color_temp"].schema,
            color_temp_mireds=200,
            transition_time=100.0,
            expect_reply=True,
            manufacturer=None,
        )
        cluster_color.request.reset_mock()

        # test color xy from the client
        assert entity.state["xy_color"] != [13369, 18087]
        await entity.async_turn_on(brightness=50, xy_color=[13369, 18087])
        await zha_gateway.async_block_till_done()
        assert entity.state["color_mode"] == ColorMode.XY
        assert entity.state["brightness"] == 50
        assert entity.state["xy_color"] == [13369, 18087]
        assert cluster_color.request.call_count == 1
        assert cluster_color.request.await_count == 1
        assert cluster_color.request.call_args == call(
            False,
            7,
            cluster_color.commands_by_name["move_to_color"].schema,
            color_x=876137415,
            color_y=1185331545,
            transition_time=0,
            expect_reply=True,
            manufacturer=None,
        )

        cluster_color.request.reset_mock()


async def async_test_on_off_from_light(
    zha_gateway: Gateway,
    cluster: general.OnOff,
    entity: PlatformEntity | GroupEntity,
) -> None:
    """Test on off functionality from the light."""
    # turn on at light
    await send_attributes_report(zha_gateway, cluster, {1: 0, 0: 1, 2: 3})
    await zha_gateway.async_block_till_done()

    # group member updates are debounced
    if isinstance(entity, GroupEntity):
        assert bool(entity.state["on"]) is False
        await asyncio.sleep(0.1)
        await zha_gateway.async_block_till_done()

    assert bool(entity.state["on"]) is True

    # turn off at light
    await send_attributes_report(zha_gateway, cluster, {1: 1, 0: 0, 2: 3})
    await zha_gateway.async_block_till_done()

    # group member updates are debounced
    if isinstance(entity, GroupEntity):
        assert bool(entity.state["on"]) is True
        await asyncio.sleep(0.1)
        await zha_gateway.async_block_till_done()

    assert bool(entity.state["on"]) is False


async def async_test_on_from_light(
    zha_gateway: Gateway,
    cluster: general.OnOff,
    entity: PlatformEntity | GroupEntity,
) -> None:
    """Test on off functionality from the light."""
    # turn on at light
    await send_attributes_report(
        zha_gateway, cluster, {general.OnOff.AttributeDefs.on_off.id: 1}
    )
    await zha_gateway.async_block_till_done()

    # group member updates are debounced
    if isinstance(entity, GroupEntity):
        assert bool(entity.state["on"]) is False
        await asyncio.sleep(0.1)
        await zha_gateway.async_block_till_done()

    assert bool(entity.state["on"]) is True


async def async_test_on_off_from_client(
    zha_gateway: Gateway,
    cluster: general.OnOff,
    entity: PlatformEntity | GroupEntity,
) -> None:
    """Test on off functionality from client."""
    # turn on via UI
    cluster.request.reset_mock()
    await entity.async_turn_on()
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True
    assert cluster.request.call_count == 1
    assert cluster.request.await_count == 1
    assert cluster.request.call_args == call(
        False,
        ON,
        cluster.commands_by_name["on"].schema,
        expect_reply=True,
        manufacturer=None,
    )

    await async_test_off_from_client(zha_gateway, cluster, entity)


async def async_test_off_from_client(
    zha_gateway: Gateway,
    cluster: general.OnOff,
    entity: PlatformEntity | GroupEntity,
) -> None:
    """Test turning off the light from the client."""

    # turn off via UI
    cluster.request.reset_mock()
    await entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is False
    assert cluster.request.call_count == 1
    assert cluster.request.await_count == 1
    assert cluster.request.call_args == call(
        False,
        OFF,
        cluster.commands_by_name["off"].schema,
        expect_reply=True,
        manufacturer=None,
    )


async def _async_shift_time(zha_gateway: Gateway):
    """Shift time to cause call later tasks to run."""
    await asyncio.sleep(11)
    await zha_gateway.async_block_till_done()


async def async_test_level_on_off_from_client(
    zha_gateway: Gateway,
    on_off_cluster: general.OnOff,
    level_cluster: general.LevelControl,
    entity: PlatformEntity | GroupEntity,
    expected_default_transition: int = 0,
) -> None:
    """Test on off functionality from client."""

    async def _reset_light():
        # reset the light
        await entity.async_turn_off()
        await zha_gateway.async_block_till_done()
        on_off_cluster.request.reset_mock()
        level_cluster.request.reset_mock()
        assert bool(entity.state["on"]) is False

    await _reset_light()
    await _async_shift_time(zha_gateway)

    # turn on via UI
    await entity.async_turn_on()
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True
    assert on_off_cluster.request.call_count == 1
    assert on_off_cluster.request.await_count == 1
    assert level_cluster.request.call_count == 0
    assert level_cluster.request.await_count == 0
    assert on_off_cluster.request.call_args == call(
        False,
        ON,
        on_off_cluster.commands_by_name["on"].schema,
        expect_reply=True,
        manufacturer=None,
    )

    await _reset_light()
    await _async_shift_time(zha_gateway)

    await entity.async_turn_on(transition=10)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True
    assert on_off_cluster.request.call_count == 0
    assert on_off_cluster.request.await_count == 0
    assert level_cluster.request.call_count == 1
    assert level_cluster.request.await_count == 1
    assert level_cluster.request.call_args == call(
        False,
        level_cluster.commands_by_name["move_to_level_with_on_off"].id,
        level_cluster.commands_by_name["move_to_level_with_on_off"].schema,
        level=254,
        transition_time=100,
        expect_reply=True,
        manufacturer=None,
    )

    await _reset_light()

    await entity.async_turn_on(brightness=10)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True
    # the onoff cluster is now not used when brightness is present by default
    assert on_off_cluster.request.call_count == 0
    assert on_off_cluster.request.await_count == 0
    assert level_cluster.request.call_count == 1
    assert level_cluster.request.await_count == 1
    assert level_cluster.request.call_args == call(
        False,
        level_cluster.commands_by_name["move_to_level_with_on_off"].id,
        level_cluster.commands_by_name["move_to_level_with_on_off"].schema,
        level=10,
        transition_time=int(expected_default_transition),
        expect_reply=True,
        manufacturer=None,
    )

    await _reset_light()

    await async_test_off_from_client(zha_gateway, on_off_cluster, entity)


async def async_test_dimmer_from_light(
    zha_gateway: Gateway,
    cluster: general.LevelControl,
    entity: PlatformEntity | GroupEntity,
    level: int,
    expected_state: bool,
) -> None:
    """Test dimmer functionality from the light."""

    await send_attributes_report(
        zha_gateway, cluster, {1: level + 10, 0: level, 2: level - 10 or 22}
    )
    await zha_gateway.async_block_till_done()
    assert entity.state["on"] == expected_state
    # hass uses None for brightness of 0 in state attributes
    if level == 0:
        assert entity.state["brightness"] is None
    else:
        # group member updates are debounced
        if isinstance(entity, GroupEntity):
            await asyncio.sleep(0.1)
            await zha_gateway.async_block_till_done()
        assert entity.state["brightness"] == level


async def async_test_flash_from_client(
    zha_gateway: Gateway,
    cluster: general.Identify,
    entity: PlatformEntity | GroupEntity,
    flash: str,
) -> None:
    """Test flash functionality from client."""
    # turn on via UI
    cluster.request.reset_mock()
    await entity.async_turn_on(flash=flash)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True
    assert cluster.request.call_count == 1
    assert cluster.request.await_count == 1
    assert cluster.request.call_args == call(
        False,
        cluster.commands_by_name["trigger_effect"].id,
        cluster.commands_by_name["trigger_effect"].schema,
        effect_id=FLASH_EFFECTS[flash],
        effect_variant=general.Identify.EffectVariant.Default,
        expect_reply=True,
        manufacturer=None,
    )


@patch(
    "zigpy.zcl.clusters.lighting.Color.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.Identify.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
async def test_zha_group_light_entity(
    zha_gateway: Gateway,
) -> None:
    """Test the light entity for a ZHA group."""

    coordinator = await coordinator_mock(zha_gateway)
    device_light_1 = await device_light_1_mock(zha_gateway)
    device_light_2 = await device_light_2_mock(zha_gateway)
    device_light_3 = await device_light_3_mock(zha_gateway)

    member_ieee_addresses = [device_light_1.ieee, device_light_2.ieee]
    members = [
        GroupMemberReference(ieee=device_light_1.ieee, endpoint_id=1),
        GroupMemberReference(ieee=device_light_2.ieee, endpoint_id=1),
    ]

    # test creating a group with 2 members
    zha_group: Group = await zha_gateway.async_create_zigpy_group("Test Group", members)
    await zha_gateway.async_block_till_done()

    assert zha_group is not None
    assert len(zha_group.members) == 2
    for member in zha_group.members:
        assert member.device.ieee in member_ieee_addresses
        assert member.group == zha_group
        assert member.endpoint is not None

    entity: GroupEntity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity.group_id == zha_group.group_id
    assert entity.info_object.fallback_name == zha_group.name

    device_1_light_entity = get_entity(device_light_1, platform=Platform.LIGHT)
    device_2_light_entity = get_entity(device_light_2, platform=Platform.LIGHT)
    device_3_light_entity = get_entity(device_light_3, platform=Platform.LIGHT)

    assert device_1_light_entity not in (device_2_light_entity, device_3_light_entity)
    assert device_2_light_entity != device_3_light_entity

    assert device_1_light_entity.unique_id in zha_group.all_member_entity_unique_ids
    assert device_2_light_entity.unique_id in zha_group.all_member_entity_unique_ids
    assert device_3_light_entity.unique_id not in zha_group.all_member_entity_unique_ids

    group_cluster_on_off = zha_group.zigpy_group.endpoint[general.OnOff.cluster_id]
    group_cluster_level = zha_group.zigpy_group.endpoint[
        general.LevelControl.cluster_id
    ]
    group_cluster_identify = zha_group.zigpy_group.endpoint[general.Identify.cluster_id]
    assert group_cluster_identify is not None

    dev1_cluster_on_off = device_light_1.device.endpoints[1].on_off
    dev2_cluster_on_off = device_light_2.device.endpoints[1].on_off
    dev3_cluster_on_off = device_light_3.device.endpoints[1].on_off

    dev1_cluster_level = device_light_1.device.endpoints[1].level

    # test that the lights were created and are off
    assert bool(entity.state["on"]) is False

    # Group entities do not support state restoration,
    # except for off_brightness and off_with_transition
    entity.restore_external_state_attributes(
        state=True,
        off_with_transition=False,
        off_brightness=12,
        brightness=34,
        color_temp=500,
        xy_color=(1, 2),
        color_mode=ColorMode.XY,
        effect="colorloop",
    )

    assert bool(entity.state["on"]) is False
    assert bool(entity.state["off_with_transition"]) is False
    assert entity.state["off_brightness"] == 12

    # test turning the lights on and off from the client
    await async_test_on_off_from_client(zha_gateway, group_cluster_on_off, entity)
    await _async_shift_time(zha_gateway)

    # test turning the lights on and off from the light
    await async_test_on_off_from_light(zha_gateway, dev1_cluster_on_off, entity)
    await _async_shift_time(zha_gateway)

    # test turning the lights on and off from the client
    await async_test_level_on_off_from_client(
        zha_gateway,
        group_cluster_on_off,
        group_cluster_level,
        entity,
        expected_default_transition=1,
    )
    await _async_shift_time(zha_gateway)

    # test getting a brightness change from the network
    await async_test_on_from_light(zha_gateway, dev1_cluster_on_off, entity)
    await async_test_dimmer_from_light(
        zha_gateway, dev1_cluster_level, entity, 150, True
    )

    # test short flashing the lights from the client
    await async_test_flash_from_client(
        zha_gateway, group_cluster_identify, entity, FlashMode.SHORT
    )
    await _async_shift_time(zha_gateway)
    # test long flashing the lights from the client
    await async_test_flash_from_client(
        zha_gateway, group_cluster_identify, entity, FlashMode.LONG
    )
    await _async_shift_time(zha_gateway)

    assert len(zha_group.members) == 2
    # test some of the group logic to make sure we key off states correctly
    await send_attributes_report(zha_gateway, dev1_cluster_on_off, {0: 1})
    await send_attributes_report(zha_gateway, dev2_cluster_on_off, {0: 1})
    await zha_gateway.async_block_till_done()

    # test that group light is on
    assert device_1_light_entity.state["on"] is True
    assert device_2_light_entity.state["on"] is True
    assert bool(entity.state["on"]) is True

    await send_attributes_report(zha_gateway, dev1_cluster_on_off, {0: 0})
    await zha_gateway.async_block_till_done()

    # test that group light is still on
    assert device_1_light_entity.state["on"] is False
    assert device_2_light_entity.state["on"] is True
    assert bool(entity.state["on"]) is True

    await send_attributes_report(zha_gateway, dev2_cluster_on_off, {0: 0})
    await zha_gateway.async_block_till_done()

    # test that group light is now off
    assert device_1_light_entity.state["on"] is False
    assert device_2_light_entity.state["on"] is False

    # group member updates are debounced
    assert bool(entity.state["on"]) is True
    await asyncio.sleep(0.1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is False

    await send_attributes_report(zha_gateway, dev1_cluster_on_off, {0: 1})
    await zha_gateway.async_block_till_done()

    # test that group light is now back on
    assert device_1_light_entity.state["on"] is True
    assert device_2_light_entity.state["on"] is False
    # group member updates are debounced
    assert bool(entity.state["on"]) is False
    await asyncio.sleep(0.1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True

    await group_entity_availability_test(
        zha_gateway, device_light_1, device_light_2, entity
    )

    # turn it off to test a new member add being tracked
    await send_attributes_report(zha_gateway, dev1_cluster_on_off, {0: 0})
    await zha_gateway.async_block_till_done()
    assert device_1_light_entity.state["on"] is False
    assert device_2_light_entity.state["on"] is False
    # group member updates are debounced
    assert bool(entity.state["on"]) is True
    await asyncio.sleep(0.1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is False

    # add a new member and test that his state is also tracked
    await zha_group.async_add_members(
        [GroupMemberReference(ieee=device_light_3.ieee, endpoint_id=1)]
    )
    await zha_gateway.async_block_till_done()
    assert device_3_light_entity.unique_id in zha_group.all_member_entity_unique_ids
    assert len(zha_group.members) == 3
    entity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity is not None
    await send_attributes_report(zha_gateway, dev3_cluster_on_off, {0: 1})
    await zha_gateway.async_block_till_done()

    assert device_1_light_entity.state["on"] is False
    assert device_2_light_entity.state["on"] is False
    assert device_3_light_entity.state["on"] is True
    # group member updates are debounced
    assert bool(entity.state["on"]) is False
    await asyncio.sleep(0.1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True

    # make the group have only 1 member and now there should be no entity
    await zha_group.async_remove_members(
        [
            GroupMemberReference(ieee=device_light_2.ieee, endpoint_id=1),
            GroupMemberReference(ieee=device_light_3.ieee, endpoint_id=1),
        ]
    )
    await zha_gateway.async_block_till_done()
    assert len(zha_group.members) == 1
    assert device_2_light_entity.unique_id not in zha_group.all_member_entity_unique_ids
    assert device_3_light_entity.unique_id not in zha_group.all_member_entity_unique_ids
    # assert entity.unique_id not in group_proxy.group_model.entities

    with pytest.raises(KeyError):
        get_group_entity(zha_group, platform=Platform.LIGHT)

    # add a member back and ensure that the group entity was created again
    await zha_group.async_add_members(
        [GroupMemberReference(ieee=device_light_3.ieee, endpoint_id=1)]
    )
    await zha_gateway.async_block_till_done()
    assert len(zha_group.members) == 2

    entity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity is not None
    await send_attributes_report(zha_gateway, dev3_cluster_on_off, {0: 1})
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True

    # add a 3rd member and ensure we still have an entity and we track the new member
    # First we turn the lights currently in the group off
    await send_attributes_report(zha_gateway, dev1_cluster_on_off, {0: 0})
    await send_attributes_report(zha_gateway, dev3_cluster_on_off, {0: 0})
    await zha_gateway.async_block_till_done()
    # group member updates are debounced
    assert bool(entity.state["on"]) is True
    await asyncio.sleep(0.1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is False

    # this will test that _reprobe_group is used correctly
    await zha_group.async_add_members(
        [
            GroupMemberReference(ieee=device_light_2.ieee, endpoint_id=1),
            GroupMemberReference(ieee=coordinator.ieee, endpoint_id=1),
        ]
    )
    await zha_gateway.async_block_till_done()
    assert len(zha_group.members) == 4
    entity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity is not None
    await send_attributes_report(zha_gateway, dev2_cluster_on_off, {0: 1})
    await zha_gateway.async_block_till_done()
    # group member updates are debounced
    assert bool(entity.state["on"]) is False
    await asyncio.sleep(0.1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True

    await zha_group.async_remove_members(
        [GroupMemberReference(ieee=coordinator.ieee, endpoint_id=1)]
    )
    await zha_gateway.async_block_till_done()
    entity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity is not None
    assert bool(entity.state["on"]) is True
    assert len(zha_group.members) == 3

    # remove the group and ensure that there is no entity and that the entity registry is cleaned up
    await zha_gateway.async_remove_zigpy_group(zha_group.group_id)
    await zha_gateway.async_block_till_done()

    with pytest.raises(KeyError):
        get_group_entity(zha_group, platform=Platform.LIGHT)


@pytest.mark.parametrize(
    ("plugged_attr_reads", "config_override", "expected_state"),
    [
        # HS light without cached hue or saturation
        (
            {
                "color_capabilities": (
                    lighting.Color.ColorCapabilities.Hue_and_saturation
                ),
            },
            {},
            {},
        ),
        # HS light with cached hue
        (
            {
                "color_capabilities": (
                    lighting.Color.ColorCapabilities.Hue_and_saturation
                ),
                "current_hue": 100,
            },
            {},
            {},
        ),
        # HS light with cached saturation
        (
            {
                "color_capabilities": (
                    lighting.Color.ColorCapabilities.Hue_and_saturation
                ),
                "current_saturation": 100,
            },
            {},
            {},
        ),
        # HS light with both
        (
            {
                "color_capabilities": (
                    lighting.Color.ColorCapabilities.Hue_and_saturation
                ),
                "current_hue": 100,
                "current_saturation": 100,
            },
            {},
            {},
        ),
    ],
)
# TODO expected_state is not used
# TODO remove? No light will ever only support HS, we no longer support it
async def test_light_initialization(
    zha_gateway: Gateway,
    plugged_attr_reads: dict[str, Any],
    config_override: dict[str, Any],
    expected_state: dict[str, Any],  # pylint: disable=unused-argument
) -> None:
    """Test ZHA light initialization with cached attributes and color modes."""

    # create zigpy devices
    zigpy_device = create_mock_zigpy_device(zha_gateway, LIGHT_COLOR)

    # mock attribute reads
    zigpy_device.endpoints[1].light_color.PLUGGED_ATTR_READS = plugged_attr_reads

    light_options = zha_gateway.config.config.light_options
    for key, value in config_override.items():
        setattr(light_options, key, value)
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    _entity = get_entity(zha_device, platform=Platform.LIGHT)

    # TODO ensure hue and saturation are properly set on startup


@patch(
    "zigpy.zcl.clusters.lighting.Color.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.Identify.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
async def test_transitions(
    zha_gateway: Gateway,
) -> None:
    """Test ZHA light transition code."""

    device_light_1 = await device_light_1_mock(zha_gateway)
    device_light_2 = await device_light_2_mock(zha_gateway)
    eWeLink_light = await eWeLink_light_mock(zha_gateway)
    member_ieee_addresses = [device_light_1.ieee, device_light_2.ieee]
    members = [
        GroupMemberReference(ieee=device_light_1.ieee, endpoint_id=1),
        GroupMemberReference(ieee=device_light_2.ieee, endpoint_id=1),
    ]

    # test creating a group with 2 members
    zha_group: Group = await zha_gateway.async_create_zigpy_group("Test Group", members)
    await zha_gateway.async_block_till_done()

    assert zha_group is not None
    assert len(zha_group.members) == 2
    for member in zha_group.members:
        assert member.device.ieee in member_ieee_addresses
        assert member.group == zha_group
        assert member.endpoint is not None

    entity: GroupEntity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity.group_id == zha_group.group_id

    device_1_light_entity = get_entity(device_light_1, platform=Platform.LIGHT)
    device_2_light_entity = get_entity(device_light_2, platform=Platform.LIGHT)
    eWeLink_light_entity = get_entity(eWeLink_light, platform=Platform.LIGHT)

    assert device_1_light_entity not in (device_2_light_entity, eWeLink_light_entity)
    assert device_2_light_entity != eWeLink_light_entity

    entity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert device_1_light_entity.unique_id in zha_group.all_member_entity_unique_ids
    assert device_2_light_entity.unique_id in zha_group.all_member_entity_unique_ids
    assert eWeLink_light_entity.unique_id not in zha_group.all_member_entity_unique_ids

    dev1_cluster_on_off = device_light_1.device.endpoints[1].on_off
    dev2_cluster_on_off = device_light_2.device.endpoints[1].on_off
    eWeLink_cluster_on_off = eWeLink_light.device.endpoints[1].on_off

    dev1_cluster_level = device_light_1.device.endpoints[1].level
    dev2_cluster_level = device_light_2.device.endpoints[1].level
    eWeLink_cluster_level = eWeLink_light.device.endpoints[1].level

    dev1_cluster_color = device_light_1.device.endpoints[1].light_color
    dev2_cluster_color = device_light_2.device.endpoints[1].light_color
    eWeLink_cluster_color = eWeLink_light.device.endpoints[1].light_color

    # test that the lights were created and are off
    assert bool(entity.state["on"]) is False
    assert bool(device_1_light_entity.state["on"]) is False
    assert bool(device_2_light_entity.state["on"]) is False

    # first test 0 length transition with no color and no brightness provided
    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_level.request.reset_mock()
    await device_1_light_entity.async_turn_on(transition=0)
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 0
    assert dev1_cluster_color.request.await_count == 0
    assert dev1_cluster_level.request.call_count == 1
    assert dev1_cluster_level.request.await_count == 1
    assert dev1_cluster_level.request.call_args == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=254,  # default "full on" brightness
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 254

    # test 0 length transition with no color and no brightness provided again, but for "force on" lights
    eWeLink_cluster_on_off.request.reset_mock()
    eWeLink_cluster_level.request.reset_mock()

    await eWeLink_light_entity.async_turn_on(transition=0)
    await zha_gateway.async_block_till_done()
    assert eWeLink_cluster_on_off.request.call_count == 1
    assert eWeLink_cluster_on_off.request.await_count == 1
    assert eWeLink_cluster_on_off.request.call_args_list[0] == call(
        False,
        eWeLink_cluster_on_off.commands_by_name["on"].id,
        eWeLink_cluster_on_off.commands_by_name["on"].schema,
        expect_reply=True,
        manufacturer=None,
    )
    assert eWeLink_cluster_color.request.call_count == 0
    assert eWeLink_cluster_color.request.await_count == 0
    assert eWeLink_cluster_level.request.call_count == 1
    assert eWeLink_cluster_level.request.await_count == 1
    assert eWeLink_cluster_level.request.call_args == call(
        False,
        eWeLink_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        eWeLink_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=254,  # default "full on" brightness
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(eWeLink_light_entity.state["on"]) is True
    assert eWeLink_light_entity.state["brightness"] == 254

    eWeLink_cluster_on_off.request.reset_mock()
    eWeLink_cluster_level.request.reset_mock()

    # test 0 length transition with brightness, but no color provided
    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_level.request.reset_mock()
    await device_1_light_entity.async_turn_on(transition=0, brightness=50)
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 0
    assert dev1_cluster_color.request.await_count == 0
    assert dev1_cluster_level.request.call_count == 1
    assert dev1_cluster_level.request.await_count == 1
    assert dev1_cluster_level.request.call_args == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=50,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 50

    dev1_cluster_level.request.reset_mock()

    # test non 0 length transition with color provided while light is on
    await device_1_light_entity.async_turn_on(
        transition=3.5, brightness=18, color_temp=432
    )
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 1
    assert dev1_cluster_color.request.await_count == 1
    assert dev1_cluster_level.request.call_count == 1
    assert dev1_cluster_level.request.await_count == 1
    assert dev1_cluster_level.request.call_args == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=18,
        transition_time=35,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_color.request.call_args == call(
        False,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=432,
        transition_time=35,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 18
    assert device_1_light_entity.state["color_temp"] == 432
    assert device_1_light_entity.state["color_mode"] == ColorMode.COLOR_TEMP

    dev1_cluster_level.request.reset_mock()
    dev1_cluster_color.request.reset_mock()

    # test 0 length transition to turn light off
    await device_1_light_entity.async_turn_off(transition=0)
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 0
    assert dev1_cluster_color.request.await_count == 0
    assert dev1_cluster_level.request.call_count == 1
    assert dev1_cluster_level.request.await_count == 1
    assert dev1_cluster_level.request.call_args == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=0,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is False

    dev1_cluster_level.request.reset_mock()

    # test non 0 length transition and color temp while turning light on (new_color_provided_while_off)
    await device_1_light_entity.async_turn_on(
        transition=1, brightness=25, color_temp=235
    )
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 1
    assert dev1_cluster_color.request.await_count == 1
    assert dev1_cluster_level.request.call_count == 2
    assert dev1_cluster_level.request.await_count == 2

    # first it comes on with no transition at 2 brightness
    assert dev1_cluster_level.request.call_args_list[0] == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=2,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_color.request.call_args == call(
        False,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=235,
        transition_time=0,  # no transition when new_color_provided_while_off
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_level.request.call_args_list[1] == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level"].id,
        dev1_cluster_level.commands_by_name["move_to_level"].schema,
        level=25,
        transition_time=10,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 25
    assert device_1_light_entity.state["color_temp"] == 235
    assert device_1_light_entity.state["color_mode"] == ColorMode.COLOR_TEMP

    dev1_cluster_level.request.reset_mock()
    dev1_cluster_color.request.reset_mock()

    # turn light 1 back off
    await device_1_light_entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 1
    assert dev1_cluster_on_off.request.await_count == 1
    assert dev1_cluster_color.request.call_count == 0
    assert dev1_cluster_color.request.await_count == 0
    assert dev1_cluster_level.request.call_count == 0
    assert dev1_cluster_level.request.await_count == 0

    assert bool(entity.state["on"]) is False

    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_color.request.reset_mock()
    dev1_cluster_level.request.reset_mock()

    # test no transition provided and color temp while turning light on (new_color_provided_while_off)
    await device_1_light_entity.async_turn_on(brightness=25, color_temp=236)
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 1
    assert dev1_cluster_color.request.await_count == 1
    assert dev1_cluster_level.request.call_count == 2
    assert dev1_cluster_level.request.await_count == 2

    # first it comes on with no transition at 2 brightness
    assert dev1_cluster_level.request.call_args_list[0] == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=2,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_color.request.call_args == call(
        False,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=236,
        transition_time=0,  # no transition when new_color_provided_while_off
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_level.request.call_args_list[1] == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level"].id,
        dev1_cluster_level.commands_by_name["move_to_level"].schema,
        level=25,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 25
    assert device_1_light_entity.state["color_temp"] == 236
    assert device_1_light_entity.state["color_mode"] == ColorMode.COLOR_TEMP

    dev1_cluster_level.request.reset_mock()
    dev1_cluster_color.request.reset_mock()

    # turn light 1 back off to setup group test
    await device_1_light_entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 1
    assert dev1_cluster_on_off.request.await_count == 1
    assert dev1_cluster_color.request.call_count == 0
    assert dev1_cluster_color.request.await_count == 0
    assert dev1_cluster_level.request.call_count == 0
    assert dev1_cluster_level.request.await_count == 0
    assert bool(entity.state["on"]) is False

    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_color.request.reset_mock()
    dev1_cluster_level.request.reset_mock()

    # test no transition when the same color temp is provided from off
    await device_1_light_entity.async_turn_on(color_temp=236)
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 1
    assert dev1_cluster_on_off.request.await_count == 1
    assert dev1_cluster_color.request.call_count == 1
    assert dev1_cluster_color.request.await_count == 1
    assert dev1_cluster_level.request.call_count == 0
    assert dev1_cluster_level.request.await_count == 0

    assert dev1_cluster_on_off.request.call_args == call(
        False,
        dev1_cluster_on_off.commands_by_name["on"].id,
        dev1_cluster_on_off.commands_by_name["on"].schema,
        expect_reply=True,
        manufacturer=None,
    )

    assert dev1_cluster_color.request.call_args == call(
        False,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=236,
        transition_time=0,  # no transition when new_color_provided_while_off
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 25
    assert device_1_light_entity.state["color_temp"] == 236
    assert device_1_light_entity.state["color_mode"] == ColorMode.COLOR_TEMP

    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_color.request.reset_mock()

    # turn light 1 back off to setup group test
    await device_1_light_entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    assert dev1_cluster_on_off.request.call_count == 1
    assert dev1_cluster_on_off.request.await_count == 1
    assert dev1_cluster_color.request.call_count == 0
    assert dev1_cluster_color.request.await_count == 0
    assert dev1_cluster_level.request.call_count == 0
    assert dev1_cluster_level.request.await_count == 0
    assert bool(entity.state["on"]) is False

    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_color.request.reset_mock()
    dev1_cluster_level.request.reset_mock()

    # test sengled light uses default minimum transition time
    dev2_cluster_on_off.request.reset_mock()
    dev2_cluster_color.request.reset_mock()
    dev2_cluster_level.request.reset_mock()

    await device_2_light_entity.async_turn_on(transition=0, brightness=100)
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 0
    assert dev2_cluster_on_off.request.await_count == 0
    assert dev2_cluster_color.request.call_count == 0
    assert dev2_cluster_color.request.await_count == 0
    assert dev2_cluster_level.request.call_count == 1
    assert dev2_cluster_level.request.await_count == 1
    assert dev2_cluster_level.request.call_args == call(
        False,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=100,
        transition_time=1,  # transition time - sengled light uses default minimum
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_2_light_entity.state["on"]) is True
    assert device_2_light_entity.state["brightness"] == 100

    dev2_cluster_level.request.reset_mock()

    # turn the sengled light back off
    await device_2_light_entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 1
    assert dev2_cluster_on_off.request.await_count == 1
    assert dev2_cluster_color.request.call_count == 0
    assert dev2_cluster_color.request.await_count == 0
    assert dev2_cluster_level.request.call_count == 0
    assert dev2_cluster_level.request.await_count == 0
    assert bool(device_2_light_entity.state["on"]) is False

    dev2_cluster_on_off.request.reset_mock()

    # test non 0 length transition and color temp while turning light on and sengled (new_color_provided_while_off)
    await device_2_light_entity.async_turn_on(
        transition=1, brightness=25, color_temp=235
    )
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 0
    assert dev2_cluster_on_off.request.await_count == 0
    assert dev2_cluster_color.request.call_count == 1
    assert dev2_cluster_color.request.await_count == 1
    assert dev2_cluster_level.request.call_count == 2
    assert dev2_cluster_level.request.await_count == 2

    # first it comes on with no transition at 2 brightness
    assert dev2_cluster_level.request.call_args_list[0] == call(
        False,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=2,
        transition_time=1,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev2_cluster_color.request.call_args == call(
        False,
        dev2_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev2_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=235,
        transition_time=1,  # sengled transition == 1 when new_color_provided_while_off
        expect_reply=True,
        manufacturer=None,
    )
    assert dev2_cluster_level.request.call_args_list[1] == call(
        False,
        dev2_cluster_level.commands_by_name["move_to_level"].id,
        dev2_cluster_level.commands_by_name["move_to_level"].schema,
        level=25,
        transition_time=10,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_2_light_entity.state["on"]) is True
    assert device_2_light_entity.state["brightness"] == 25
    assert device_2_light_entity.state["color_temp"] == 235
    assert device_2_light_entity.state["color_mode"] == ColorMode.COLOR_TEMP

    dev2_cluster_level.request.reset_mock()
    dev2_cluster_color.request.reset_mock()

    # turn the sengled light back off
    await device_2_light_entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 1
    assert dev2_cluster_on_off.request.await_count == 1
    assert dev2_cluster_color.request.call_count == 0
    assert dev2_cluster_color.request.await_count == 0
    assert dev2_cluster_level.request.call_count == 0
    assert dev2_cluster_level.request.await_count == 0
    assert bool(device_2_light_entity.state["on"]) is False

    dev2_cluster_on_off.request.reset_mock()

    # test non 0 length transition and color temp while turning group light on (new_color_provided_while_off)
    await entity.async_turn_on(transition=1, brightness=25, color_temp=235)
    await zha_gateway.async_block_till_done()

    group_on_off_cluster_handler = zha_group.endpoint[general.OnOff.cluster_id]
    group_level_cluster_handler = zha_group.endpoint[general.LevelControl.cluster_id]
    group_color_cluster_handler = zha_group.endpoint[lighting.Color.cluster_id]
    assert group_on_off_cluster_handler.request.call_count == 0
    assert group_on_off_cluster_handler.request.await_count == 0
    assert group_color_cluster_handler.request.call_count == 1
    assert group_color_cluster_handler.request.await_count == 1
    assert group_level_cluster_handler.request.call_count == 1
    assert group_level_cluster_handler.request.await_count == 1

    # groups are omitted from the 3 call dance for new_color_provided_while_off
    assert group_color_cluster_handler.request.call_args == call(
        False,
        dev2_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev2_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=235,
        transition_time=10,  # sengled transition == 1 when new_color_provided_while_off
        expect_reply=True,
        manufacturer=None,
    )
    assert group_level_cluster_handler.request.call_args == call(
        False,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=25,
        transition_time=10,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(entity.state["on"]) is True
    assert entity.state["brightness"] == 25
    assert entity.state["color_temp"] == 235
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP

    group_on_off_cluster_handler.request.reset_mock()
    group_color_cluster_handler.request.reset_mock()
    group_level_cluster_handler.request.reset_mock()

    # turn the sengled light back on
    await device_2_light_entity.async_turn_on()
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 1
    assert dev2_cluster_on_off.request.await_count == 1
    assert dev2_cluster_color.request.call_count == 0
    assert dev2_cluster_color.request.await_count == 0
    assert dev2_cluster_level.request.call_count == 0
    assert dev2_cluster_level.request.await_count == 0
    assert bool(device_2_light_entity.state["on"]) is True

    dev2_cluster_on_off.request.reset_mock()

    # turn the light off with a transition
    await device_2_light_entity.async_turn_off(transition=2)
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 0
    assert dev2_cluster_on_off.request.await_count == 0
    assert dev2_cluster_color.request.call_count == 0
    assert dev2_cluster_color.request.await_count == 0
    assert dev2_cluster_level.request.call_count == 1
    assert dev2_cluster_level.request.await_count == 1
    assert dev2_cluster_level.request.call_args == call(
        False,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=0,
        transition_time=20,  # transition time
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_2_light_entity.state["on"]) is False

    dev2_cluster_level.request.reset_mock()

    # turn the light back on with no args should use a transition and last known brightness
    await device_2_light_entity.async_turn_on()
    await zha_gateway.async_block_till_done()
    assert dev2_cluster_on_off.request.call_count == 0
    assert dev2_cluster_on_off.request.await_count == 0
    assert dev2_cluster_color.request.call_count == 0
    assert dev2_cluster_color.request.await_count == 0
    assert dev2_cluster_level.request.call_count == 1
    assert dev2_cluster_level.request.await_count == 1
    assert dev2_cluster_level.request.call_args == call(
        False,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=25,
        transition_time=1,  # transition time - sengled light uses default minimum
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(device_2_light_entity.state["on"]) is True

    dev2_cluster_level.request.reset_mock()
    eWeLink_cluster_on_off.request.reset_mock()
    eWeLink_cluster_level.request.reset_mock()
    eWeLink_cluster_color.request.reset_mock()

    # test eWeLink color temp while turning light on from off (new_color_provided_while_off)
    await eWeLink_light_entity.async_turn_on(color_temp=235)
    await zha_gateway.async_block_till_done()
    assert eWeLink_cluster_on_off.request.call_count == 1
    assert eWeLink_cluster_on_off.request.await_count == 1
    assert eWeLink_cluster_color.request.call_count == 1
    assert eWeLink_cluster_color.request.await_count == 1
    assert eWeLink_cluster_level.request.call_count == 0
    assert eWeLink_cluster_level.request.await_count == 0

    # first it comes on
    assert eWeLink_cluster_on_off.request.call_args_list[0] == call(
        False,
        eWeLink_cluster_on_off.commands_by_name["on"].id,
        eWeLink_cluster_on_off.commands_by_name["on"].schema,
        expect_reply=True,
        manufacturer=None,
    )
    assert eWeLink_cluster_color.request.call_args == call(
        False,
        eWeLink_cluster_color.commands_by_name["move_to_color_temp"].id,
        eWeLink_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=235,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(eWeLink_light_entity.state["on"]) is True
    assert eWeLink_light_entity.state["color_temp"] == 235
    assert eWeLink_light_entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert eWeLink_light_entity.min_mireds == 153
    assert eWeLink_light_entity.max_mireds == 500


@patch(
    "zigpy.zcl.clusters.lighting.Color.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
async def test_on_with_off_color(zha_gateway: Gateway) -> None:
    """Test turning on the light and sending color commands before on/level commands for supporting lights."""
    device_light_1 = await device_light_1_mock(zha_gateway)
    dev1_cluster_on_off = device_light_1.device.endpoints[1].on_off
    dev1_cluster_level = device_light_1.device.endpoints[1].level
    dev1_cluster_color = device_light_1.device.endpoints[1].light_color

    entity = get_entity(device_light_1, platform=Platform.LIGHT)

    # Execute_if_off will override the "enhanced turn on from an off-state" config option that's enabled here
    dev1_cluster_color.PLUGGED_ATTR_READS = {
        "options": lighting.Color.Options.Execute_if_off
    }
    update_attribute_cache(dev1_cluster_color)

    # turn on via UI
    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_level.request.reset_mock()
    dev1_cluster_color.request.reset_mock()

    await entity.async_turn_on(color_temp=235)

    assert dev1_cluster_on_off.request.call_count == 1
    assert dev1_cluster_on_off.request.await_count == 1
    assert dev1_cluster_color.request.call_count == 1
    assert dev1_cluster_color.request.await_count == 1
    assert dev1_cluster_level.request.call_count == 0
    assert dev1_cluster_level.request.await_count == 0

    assert dev1_cluster_on_off.request.call_args_list[0] == call(
        False,
        dev1_cluster_on_off.commands_by_name["on"].id,
        dev1_cluster_on_off.commands_by_name["on"].schema,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_color.request.call_args == call(
        False,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=235,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(entity.state["on"]) is True
    assert entity.state["color_temp"] == 235
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert entity.supported_color_modes == {ColorMode.COLOR_TEMP, ColorMode.XY}
    assert entity._internal_supported_color_modes == {
        ColorMode.COLOR_TEMP,
        ColorMode.XY,
        ColorMode.ONOFF,
        ColorMode.BRIGHTNESS,
    }

    # now let's turn off the Execute_if_off option and see if the old behavior is restored
    dev1_cluster_color.PLUGGED_ATTR_READS = {"options": 0}
    update_attribute_cache(dev1_cluster_color)

    # turn off via UI, so the old "enhanced turn on from an off-state" behavior can do something
    await async_test_off_from_client(zha_gateway, dev1_cluster_on_off, entity)

    # turn on via UI (with a different color temp, so the "enhanced turn on" does something)
    dev1_cluster_on_off.request.reset_mock()
    dev1_cluster_level.request.reset_mock()
    dev1_cluster_color.request.reset_mock()

    await entity.async_turn_on(color_temp=240)

    assert dev1_cluster_on_off.request.call_count == 0
    assert dev1_cluster_on_off.request.await_count == 0
    assert dev1_cluster_color.request.call_count == 1
    assert dev1_cluster_color.request.await_count == 1
    assert dev1_cluster_level.request.call_count == 2
    assert dev1_cluster_level.request.await_count == 2

    # first it comes on with no transition at 2 brightness
    assert dev1_cluster_level.request.call_args_list[0] == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id,
        dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
        level=2,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_color.request.call_args == call(
        False,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].id,
        dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
        color_temp_mireds=240,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )
    assert dev1_cluster_level.request.call_args_list[1] == call(
        False,
        dev1_cluster_level.commands_by_name["move_to_level"].id,
        dev1_cluster_level.commands_by_name["move_to_level"].schema,
        level=254,
        transition_time=0,
        expect_reply=True,
        manufacturer=None,
    )

    assert bool(entity.state["on"]) is True
    assert entity.state["color_temp"] == 240
    assert entity.state["brightness"] == 254
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP


@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
async def test_group_member_assume_state(zha_gateway: Gateway) -> None:
    """Test the group members assume state function."""

    coordinator = await coordinator_mock(zha_gateway)
    device_light_1 = await device_light_1_mock(zha_gateway)
    device_light_2 = await device_light_2_mock(zha_gateway)

    zha_gateway.config.config.light_options.group_members_assume_state = True

    zha_gateway.coordinator_zha_device = coordinator
    coordinator._zha_gateway = zha_gateway
    device_light_1._zha_gateway = zha_gateway
    device_light_2._zha_gateway = zha_gateway

    member_ieee_addresses = [device_light_1.ieee, device_light_2.ieee]
    members = [
        GroupMemberReference(ieee=device_light_1.ieee, endpoint_id=1),
        GroupMemberReference(ieee=device_light_2.ieee, endpoint_id=1),
    ]

    # test creating a group with 2 members
    zha_group: Group = await zha_gateway.async_create_zigpy_group("Test Group", members)
    await zha_gateway.async_block_till_done()

    assert zha_group is not None
    assert len(zha_group.members) == 2
    for member in zha_group.members:
        assert member.device.ieee in member_ieee_addresses
        assert member.group == zha_group
        assert member.endpoint is not None

    entity: GroupEntity = get_group_entity(zha_group, platform=Platform.LIGHT)
    assert entity.group_id == zha_group.group_id

    device_1_light_entity = get_entity(device_light_1, platform=Platform.LIGHT)
    device_2_light_entity = get_entity(device_light_2, platform=Platform.LIGHT)

    group_cluster_on_off = zha_group.endpoint[general.OnOff.cluster_id]

    # test that the lights were created and are off
    assert bool(entity.state["on"]) is False

    group_cluster_on_off.request.reset_mock()
    await asyncio.sleep(11)

    # turn on via UI
    await entity.async_turn_on()
    await zha_gateway.async_block_till_done()
    await asyncio.sleep(1)  # wait for assume debounce

    # members also instantly assume STATE_ON
    assert bool(device_1_light_entity.state["on"]) is True
    assert bool(device_2_light_entity.state["on"]) is True
    assert bool(entity.state["on"]) is True

    # turn off via UI
    await entity.async_turn_off()
    await zha_gateway.async_block_till_done()
    await asyncio.sleep(1)

    # members also instantly assume STATE_OFF
    assert bool(device_1_light_entity.state["on"]) is False
    assert bool(device_2_light_entity.state["on"]) is False
    assert bool(entity.state["on"]) is False

    # now test members with different state not being overridden
    # turn on light 1 to brightness 50
    await device_1_light_entity.async_turn_on(brightness=50)
    await zha_gateway.async_block_till_done()
    assert bool(device_1_light_entity.state["on"]) is True
    assert device_1_light_entity.state["brightness"] == 50

    # turn on light 2 to brightness 100
    await device_2_light_entity.async_turn_on(brightness=100)
    await zha_gateway.async_block_till_done()
    assert bool(device_2_light_entity.state["on"]) is True
    assert device_2_light_entity.state["brightness"] == 100

    await asyncio.sleep(1)  # wait for assume debounce

    # turn on group, but do not change brightness
    await entity.async_turn_on(brightness=None)
    await zha_gateway.async_block_till_done()
    await asyncio.sleep(1)

    assert entity.state["brightness"] == 75  # average

    # but members do not change unchanged state
    assert device_1_light_entity.state["brightness"] == 50
    assert device_2_light_entity.state["brightness"] == 100


@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
async def test_transition_brightness_buffering(zha_gateway: Gateway) -> None:
    """Test that brightness reports during a transition are buffered.

    The last received report is applied when the transition completes, which
    handles lights that claim SUCCESS but fail to reach the target brightness.
    """
    device_light_1 = await device_light_1_mock(zha_gateway)
    dev1_cluster_level = device_light_1.device.endpoints[1].level
    entity = get_entity(device_light_1, platform=Platform.LIGHT)

    assert bool(entity.state["on"]) is False

    # Turn on with a short transition and a target brightness of 200.
    await entity.async_turn_on(transition=0.1, brightness=200)
    await zha_gateway.async_block_till_done()

    # The state is optimistically set to the target brightness immediately.
    assert bool(entity.state["on"]) is True
    assert entity.state["brightness"] == 200
    assert entity.is_transitioning

    # Simulate intermediate brightness reports during the transition (light slowly ramping up).
    # These should be buffered, not immediately applied to HA state.
    await send_attributes_report(
        zha_gateway,
        dev1_cluster_level,
        {general.LevelControl.AttributeDefs.current_level.id: 50},
    )
    await zha_gateway.async_block_till_done()
    assert entity.state["brightness"] == 200  # still the optimistic value

    # The light only goes to brightness 120 (for some reason), not the requested 200.
    await send_attributes_report(
        zha_gateway,
        dev1_cluster_level,
        {general.LevelControl.AttributeDefs.current_level.id: 120},
    )
    await zha_gateway.async_block_till_done()
    assert entity.state["brightness"] == 200  # still buffered, not yet applied

    # Wait for the transition timer to fire (0.1 + 0.5s delay = 0.6s).
    await asyncio.sleep(0.8)
    await zha_gateway.async_block_till_done()

    # After the transition, the last buffered report (120) is applied instead of the target (200).
    assert not entity.is_transitioning
    assert entity.state["brightness"] == 120

    # Now verify that if no brightness reports arrive during a transition, the
    # optimistically set target brightness is preserved unchanged.
    await entity.async_turn_on(transition=0.1, brightness=150)
    await zha_gateway.async_block_till_done()

    assert entity.state["brightness"] == 150
    assert entity.is_transitioning

    # No level reports during this transition.
    await asyncio.sleep(0.8)
    await zha_gateway.async_block_till_done()

    assert not entity.is_transitioning
    assert entity.state["brightness"] == 150  # target preserved


@patch(
    "zigpy.zcl.clusters.general.LevelControl.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
@patch(
    "zigpy.zcl.clusters.general.OnOff.request",
    new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
)
async def test_turn_on_during_off_transition(zha_gateway: Gateway) -> None:
    """Test turning a light on while it is mid-way through an off transition.

    Some devices refuse to turn on while a transition to off is still running,
    even though they return Status.SUCCESS for the on command. The device then
    correctly reports on_off=false. That report must be buffered and applied
    when the transition window ends, so HA reflects the actual off state.
    """
    device_light_1 = await device_light_1_mock(zha_gateway)
    dev1_cluster_on_off = device_light_1.device.endpoints[1].on_off
    entity = get_entity(device_light_1, platform=Platform.LIGHT)

    # Start with the light on.
    await entity.async_turn_on(brightness=200)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is True

    # Turn it off with a transition (timer runs for 1 + 0.5s = 1.5s).
    await entity.async_turn_off(transition=1)
    await zha_gateway.async_block_till_done()
    assert bool(entity.state["on"]) is False
    assert entity.is_transitioning

    # Before the off-transition timer fires, turn the light back on.
    # The device accepts the command (returns SUCCESS) but refuses to execute it.
    await entity.async_turn_on(brightness=150)
    await zha_gateway.async_block_till_done()
    # Optimistically, HA now thinks it's on.
    assert bool(entity.state["on"]) is True
    assert entity.state["brightness"] == 150
    assert entity.is_transitioning

    # The device correctly reports it is still off (it refused the on command).
    # This must be buffered, not ignored.
    await send_attributes_report(
        zha_gateway,
        dev1_cluster_on_off,
        {general.OnOff.AttributeDefs.on_off.id: 0},
    )
    await zha_gateway.async_block_till_done()

    # During the transition window the state is still optimistically on.
    assert bool(entity.state["on"]) is True

    # Once the transition timer fires, the buffered off report is applied.
    await asyncio.sleep(0.8)
    await zha_gateway.async_block_till_done()
    assert not entity.is_transitioning
    assert bool(entity.state["on"]) is False


async def test_light_state_restoration(zha_gateway: Gateway) -> None:
    """Test the light state restoration function."""
    device_light_3 = await device_light_3_mock(zha_gateway)
    entity = get_entity(device_light_3, platform=Platform.LIGHT)
    entity.restore_external_state_attributes(
        state=True,
        off_with_transition=False,
        off_brightness=12,
        brightness=34,
        color_temp=500,
        xy_color=(1, 2),
        color_mode=ColorMode.XY,
        effect="colorloop",
    )

    assert entity.state["on"] is True
    assert entity.state["brightness"] == 34
    assert entity.state["color_temp"] == 500
    assert entity.state["xy_color"] == (1, 2)
    assert entity.state["color_mode"] == ColorMode.XY
    assert entity.state["effect"] == "colorloop"

    entity.restore_external_state_attributes(
        state=None,
        off_with_transition=None,
        off_brightness=None,
        brightness=None,
        color_temp=None,
        xy_color=None,
        color_mode=None,
        effect=None,
    )

    assert entity.state["on"] is True
    assert entity.state["brightness"] == 34
    assert entity.state["color_temp"] == 500
    assert entity.state["xy_color"] == (1, 2)
    assert entity.state["color_mode"] == ColorMode.XY
    assert entity.state["effect"] == "colorloop"


async def test_light_state_restoration_unsupported_color_mode(
    zha_gateway: Gateway,
) -> None:
    """Test that restoring an unsupported color_mode is ignored."""
    zigpy_device = create_mock_zigpy_device(zha_gateway, LIGHT_COLOR)
    color_cluster = zigpy_device.endpoints[1].light_color
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": lighting.Color.ColorCapabilities.Color_temperature,
        "color_temperature": 250,
        "color_temp_physical_min": 153,
        "color_temp_physical_max": 500,
    }
    update_attribute_cache(color_cluster)
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    entity = get_entity(zha_device, platform=Platform.LIGHT)

    assert entity.supported_color_modes == {ColorMode.COLOR_TEMP}
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP

    # Attempt to restore XY color_mode on a color_temp-only light
    entity.restore_external_state_attributes(
        state=True,
        off_with_transition=False,
        off_brightness=None,
        brightness=100,
        color_temp=300,
        xy_color=None,
        color_mode=ColorMode.XY,
        effect=None,
    )

    # color_mode should remain COLOR_TEMP since XY is not supported
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert entity.state["color_temp"] == 300


async def test_color_temp_only_light_ignores_incorrect_color_mode(
    zha_gateway: Gateway,
) -> None:
    """Test color_temp-only light ignores incorrect color_mode reads when polling."""
    zigpy_device = create_mock_zigpy_device(zha_gateway, LIGHT_COLOR)
    color_cluster = zigpy_device.endpoints[1].light_color
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": lighting.Color.ColorCapabilities.Color_temperature,
        "color_temperature": 250,
        "color_temp_physical_min": 153,
        "color_temp_physical_max": 500,
    }
    update_attribute_cache(color_cluster)
    zha_device = await join_zigpy_device(zha_gateway, zigpy_device)
    entity = get_entity(zha_device, platform=Platform.LIGHT)

    assert entity.supported_color_modes == {ColorMode.COLOR_TEMP}
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert entity.state["color_temp"] == 250

    # Simulate the device incorrectly reporting XY color mode during a poll
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": lighting.Color.ColorCapabilities.Color_temperature,
        "color_mode": lighting.Color.ColorMode.X_and_Y,
        "color_temperature": 300,
        "color_temp_physical_min": 153,
        "color_temp_physical_max": 500,
    }
    update_attribute_cache(color_cluster)

    # Trigger a poll
    await entity.async_update()

    # color_mode should remain COLOR_TEMP and color_temp should be updated
    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert entity.state["color_temp"] == 300
    assert entity.state["xy_color"] is None

    # Same test with Hue_and_saturation mode
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": lighting.Color.ColorCapabilities.Color_temperature,
        "color_mode": lighting.Color.ColorMode.Hue_and_saturation,
        "color_temperature": 400,
        "color_temp_physical_min": 153,
        "color_temp_physical_max": 500,
    }
    update_attribute_cache(color_cluster)

    await entity.async_update()

    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert entity.state["color_temp"] == 400
    assert entity.state["xy_color"] is None


async def test_poll_updates_color_mode_on_dual_mode_light(
    zha_gateway: Gateway,
) -> None:
    """Test polling XY + COLOR_TEMP light updates color_mode and attribute values."""
    device = await device_light_1_mock(zha_gateway)
    entity = get_entity(device, platform=Platform.LIGHT)
    color_cluster = device.device.endpoints[1].light_color

    assert entity.supported_color_modes == {ColorMode.COLOR_TEMP, ColorMode.XY}

    # Poll with color_temp mode
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": (
            lighting.Color.ColorCapabilities.Color_temperature
            | lighting.Color.ColorCapabilities.XY_attributes
        ),
        "color_mode": lighting.Color.ColorMode.Color_temperature,
        "color_temperature": 350,
        "current_x": 20000,
        "current_y": 20000,
    }
    update_attribute_cache(color_cluster)
    await entity.async_update()

    assert entity.state["color_mode"] == ColorMode.COLOR_TEMP
    assert entity.state["color_temp"] == 350
    assert entity.state["xy_color"] is None

    # Poll with XY mode
    color_cluster.PLUGGED_ATTR_READS = {
        "color_capabilities": (
            lighting.Color.ColorCapabilities.Color_temperature
            | lighting.Color.ColorCapabilities.XY_attributes
        ),
        "color_mode": lighting.Color.ColorMode.X_and_Y,
        "color_temperature": 350,
        "current_x": 30000,
        "current_y": 25000,
    }
    update_attribute_cache(color_cluster)
    await entity.async_update()

    assert entity.state["color_mode"] == ColorMode.XY
    assert entity.state["xy_color"] == (30000 / 65535, 25000 / 65535)
    assert entity.state["color_temp"] is None


async def test_turn_on_cancellation_cleans_up_transition_flag(
    zha_gateway: Gateway,
) -> None:
    """Test that task cancellation resets the transitioning flag.

    When a mode:restart automation cancels the task, the light must not be
    stuck ignoring attribute reports indefinitely.
    """
    device = await device_light_1_mock(zha_gateway)
    entity = get_entity(device, platform=Platform.LIGHT)

    cluster_level = device.device.endpoints[1].level

    # Make the level cluster block indefinitely so we can cancel the task while
    # it is suspended at the first await inside async_turn_on.
    blocked: asyncio.Future[None] = asyncio.get_running_loop().create_future()
    original_request = cluster_level.request

    async def blocking_request(*args, **kwargs):
        await blocked
        return await original_request(*args, **kwargs)

    cluster_level.request = AsyncMock(side_effect=blocking_request)

    # Start turn_on as a separate task, mirroring what HA does for automations.
    task = asyncio.ensure_future(entity.async_turn_on(brightness=200, transition=1))

    # Yield control so the task can run until it suspends on the cluster call.
    await asyncio.sleep(0)
    await asyncio.sleep(0)

    # The transitioning flag must be set now, with no timer running yet.
    assert entity.is_transitioning is True
    assert entity._transition_listener is None

    # Cancel the task (what mode:restart does to the running automation task).
    task.cancel()
    with contextlib.suppress(asyncio.CancelledError):
        await task

    # The finally block must have cleared the flag.
    assert entity.is_transitioning is False


async def test_turn_off_cancellation_cleans_up_transition_flag(
    zha_gateway: Gateway,
) -> None:
    """Test that task cancellation during async_turn_off resets the transitioning flag."""
    device = await device_light_1_mock(zha_gateway)
    entity = get_entity(device, platform=Platform.LIGHT)

    cluster_on_off = device.device.endpoints[1].on_off

    # Make the on/off cluster block indefinitely so we can cancel mid-turn-off.
    blocked: asyncio.Future[None] = asyncio.get_running_loop().create_future()
    original_request = cluster_on_off.request

    async def blocking_request(*args, **kwargs):
        await blocked
        return await original_request(*args, **kwargs)

    cluster_on_off.request = AsyncMock(side_effect=blocking_request)

    task = asyncio.ensure_future(entity.async_turn_off())

    await asyncio.sleep(0)
    await asyncio.sleep(0)

    assert entity.is_transitioning is True
    assert entity._transition_listener is None

    task.cancel()
    with contextlib.suppress(asyncio.CancelledError):
        await task

    assert entity.is_transitioning is False
