"""Test Z-Wave lights."""
from homeassistant.components import zwave
from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP,
    ATTR_HS_COLOR,
    ATTR_TRANSITION,
    ATTR_WHITE_VALUE,
    SUPPORT_BRIGHTNESS,
    SUPPORT_COLOR,
    SUPPORT_COLOR_TEMP,
    SUPPORT_TRANSITION,
    SUPPORT_WHITE_VALUE,
)
from homeassistant.components.zwave import const, light

from tests.async_mock import MagicMock, patch
from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed


class MockLightValues(MockEntityValues):
    """Mock Z-Wave light values."""

    def __init__(self, **kwargs):
        """Initialize the mock zwave values."""
        self.dimming_duration = None
        self.color = None
        self.color_channels = None
        super().__init__(**kwargs)


def test_get_device_detects_dimmer(mock_openzwave):
    """Test get_device returns a normal dimmer."""
    node = MockNode()
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)

    device = light.get_device(node=node, values=values, node_config={})
    assert isinstance(device, light.ZwaveDimmer)
    assert device.supported_features == SUPPORT_BRIGHTNESS


def test_get_device_detects_colorlight(mock_openzwave):
    """Test get_device returns a color light."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)

    device = light.get_device(node=node, values=values, node_config={})
    assert isinstance(device, light.ZwaveColorLight)
    assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_COLOR


def test_get_device_detects_zw098(mock_openzwave):
    """Test get_device returns a zw098 color light."""
    node = MockNode(
        manufacturer_id="0086",
        product_id="0062",
        command_classes=[const.COMMAND_CLASS_SWITCH_COLOR],
    )
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(node=node, values=values, node_config={})
    assert isinstance(device, light.ZwaveColorLight)
    assert device.supported_features == (
        SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP
    )


def test_get_device_detects_rgbw_light(mock_openzwave):
    """Test get_device returns a color light."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    color_channels = MockValue(data=0x1D, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)

    device = light.get_device(node=node, values=values, node_config={})
    device.value_added()
    assert isinstance(device, light.ZwaveColorLight)
    assert device.supported_features == (
        SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE
    )


def test_dimmer_turn_on(mock_openzwave):
    """Test turning on a dimmable Z-Wave light."""
    node = MockNode()
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(node=node, values=values, node_config={})

    device.turn_on()

    assert node.set_dimmer.called
    value_id, brightness = node.set_dimmer.mock_calls[0][1]
    assert value_id == value.value_id
    assert brightness == 255

    node.reset_mock()

    device.turn_on(**{ATTR_BRIGHTNESS: 224})

    assert node.set_dimmer.called
    value_id, brightness = node.set_dimmer.mock_calls[0][1]

    assert value_id == value.value_id
    assert brightness == 87  # round(224 / 255 * 99)

    node.reset_mock()

    device.turn_on(**{ATTR_BRIGHTNESS: 120})

    assert node.set_dimmer.called
    value_id, brightness = node.set_dimmer.mock_calls[0][1]

    assert value_id == value.value_id
    assert brightness == 47  # round(120 / 255 * 99)

    with patch.object(light, "_LOGGER", MagicMock()) as mock_logger:
        device.turn_on(**{ATTR_TRANSITION: 35})
        assert mock_logger.debug.called
        assert node.set_dimmer.called
        msg, entity_id = mock_logger.debug.mock_calls[0][1]
        assert entity_id == device.entity_id


def test_dimmer_min_brightness(mock_openzwave):
    """Test turning on a dimmable Z-Wave light to its minimum brightness."""
    node = MockNode()
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(node=node, values=values, node_config={})

    assert not device.is_on

    device.turn_on(**{ATTR_BRIGHTNESS: 1})

    assert device.is_on
    assert device.brightness == 1

    device.turn_on(**{ATTR_BRIGHTNESS: 0})

    assert device.is_on
    assert device.brightness == 0


def test_dimmer_transitions(mock_openzwave):
    """Test dimming transition on a dimmable Z-Wave light."""
    node = MockNode()
    value = MockValue(data=0, node=node)
    duration = MockValue(data=0, node=node)
    values = MockLightValues(primary=value, dimming_duration=duration)
    device = light.get_device(node=node, values=values, node_config={})
    assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION

    # Test turn_on
    # Factory Default
    device.turn_on()
    assert duration.data == 0xFF

    # Seconds transition
    device.turn_on(**{ATTR_TRANSITION: 45})
    assert duration.data == 45

    # Minutes transition
    device.turn_on(**{ATTR_TRANSITION: 245})
    assert duration.data == 0x83

    # Clipped transition
    device.turn_on(**{ATTR_TRANSITION: 10000})
    assert duration.data == 0xFE

    # Test turn_off
    # Factory Default
    device.turn_off()
    assert duration.data == 0xFF

    # Seconds transition
    device.turn_off(**{ATTR_TRANSITION: 45})
    assert duration.data == 45

    # Minutes transition
    device.turn_off(**{ATTR_TRANSITION: 245})
    assert duration.data == 0x83

    # Clipped transition
    device.turn_off(**{ATTR_TRANSITION: 10000})
    assert duration.data == 0xFE


def test_dimmer_turn_off(mock_openzwave):
    """Test turning off a dimmable Z-Wave light."""
    node = MockNode()
    value = MockValue(data=46, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(node=node, values=values, node_config={})

    device.turn_off()

    assert node.set_dimmer.called
    value_id, brightness = node.set_dimmer.mock_calls[0][1]
    assert value_id == value.value_id
    assert brightness == 0


def test_dimmer_value_changed(mock_openzwave):
    """Test value changed for dimmer lights."""
    node = MockNode()
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(node=node, values=values, node_config={})

    assert not device.is_on

    value.data = 46
    value_changed(value)

    assert device.is_on
    assert device.brightness == 118


def test_dimmer_refresh_value(mock_openzwave):
    """Test value changed for dimmer lights."""
    node = MockNode()
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(
        node=node,
        values=values,
        node_config={zwave.CONF_REFRESH_VALUE: True, zwave.CONF_REFRESH_DELAY: 5},
    )

    assert not device.is_on

    with patch.object(light, "Timer") as mock_timer:
        value.data = 46
        value_changed(value)

        assert not device.is_on
        assert mock_timer.called
        assert len(mock_timer.mock_calls) == 2
        timeout, callback = mock_timer.mock_calls[0][1][:2]
        assert timeout == 5
        assert mock_timer().start.called
        assert len(mock_timer().start.mock_calls) == 1

        with patch.object(light, "Timer") as mock_timer_2:
            value_changed(value)
            assert not device.is_on
            assert mock_timer().cancel.called
            assert len(mock_timer_2.mock_calls) == 2
            timeout, callback = mock_timer_2.mock_calls[0][1][:2]
            assert timeout == 5
            assert mock_timer_2().start.called
            assert len(mock_timer_2().start.mock_calls) == 1

            callback()
            assert device.is_on
            assert device.brightness == 118


def test_set_hs_color(mock_openzwave):
    """Test setting zwave light color."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB only
    color_channels = MockValue(data=0x1C, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert color.data == "#0000000000"

    device.turn_on(**{ATTR_HS_COLOR: (30, 50)})

    assert color.data == "#ffbf7f0000"


def test_set_white_value(mock_openzwave):
    """Test setting zwave light color."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGBW
    color_channels = MockValue(data=0x1D, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert color.data == "#0000000000"

    device.turn_on(**{ATTR_WHITE_VALUE: 200})

    assert color.data == "#ffffffc800"


def test_disable_white_if_set_color(mock_openzwave):
    """
    Test that _white is set to 0 if turn_on with ATTR_HS_COLOR.

    See Issue #13930 - many RGBW ZWave bulbs will only activate the RGB LED to
    produce color if _white is set to zero.
    """
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB only
    color_channels = MockValue(data=0x1C, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})
    device._white = 234

    assert color.data == "#0000000000"
    assert device.white_value == 234

    device.turn_on(**{ATTR_HS_COLOR: (30, 50)})

    assert device.white_value == 0
    assert color.data == "#ffbf7f0000"


def test_zw098_set_color_temp(mock_openzwave):
    """Test setting zwave light color."""
    node = MockNode(
        manufacturer_id="0086",
        product_id="0062",
        command_classes=[const.COMMAND_CLASS_SWITCH_COLOR],
    )
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB, warm white, cold white
    color_channels = MockValue(data=0x1F, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert color.data == "#0000000000"

    device.turn_on(**{ATTR_COLOR_TEMP: 200})

    assert color.data == "#00000000ff"

    device.turn_on(**{ATTR_COLOR_TEMP: 400})

    assert color.data == "#000000ff00"


def test_rgb_not_supported(mock_openzwave):
    """Test value changed for rgb lights."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports color temperature only
    color_channels = MockValue(data=0x01, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.hs_color is None


def test_no_color_value(mock_openzwave):
    """Test value changed for rgb lights."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    values = MockLightValues(primary=value)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.hs_color is None


def test_no_color_channels_value(mock_openzwave):
    """Test value changed for rgb lights."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    values = MockLightValues(primary=value, color=color)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.hs_color is None


def test_rgb_value_changed(mock_openzwave):
    """Test value changed for rgb lights."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB only
    color_channels = MockValue(data=0x1C, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.hs_color == (0, 0)

    color.data = "#ffbf800000"
    value_changed(color)

    assert device.hs_color == (29.764, 49.804)


def test_rgbww_value_changed(mock_openzwave):
    """Test value changed for rgb lights."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB, Warm White
    color_channels = MockValue(data=0x1D, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.hs_color == (0, 0)
    assert device.white_value == 0

    color.data = "#c86400c800"
    value_changed(color)

    assert device.hs_color == (30, 100)
    assert device.white_value == 200


def test_rgbcw_value_changed(mock_openzwave):
    """Test value changed for rgb lights."""
    node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB, Cold White
    color_channels = MockValue(data=0x1E, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.hs_color == (0, 0)
    assert device.white_value == 0

    color.data = "#c86400c800"
    value_changed(color)

    assert device.hs_color == (30, 100)
    assert device.white_value == 200


def test_ct_value_changed(mock_openzwave):
    """Test value changed for zw098 lights."""
    node = MockNode(
        manufacturer_id="0086",
        product_id="0062",
        command_classes=[const.COMMAND_CLASS_SWITCH_COLOR],
    )
    value = MockValue(data=0, node=node)
    color = MockValue(data="#0000000000", node=node)
    # Supports RGB, Cold White
    color_channels = MockValue(data=0x1F, node=node)
    values = MockLightValues(primary=value, color=color, color_channels=color_channels)
    device = light.get_device(node=node, values=values, node_config={})

    assert device.color_temp == light.TEMP_MID_HASS

    color.data = "#000000ff00"
    value_changed(color)

    assert device.color_temp == light.TEMP_WARM_HASS

    color.data = "#00000000ff"
    value_changed(color)

    assert device.color_temp == light.TEMP_COLD_HASS
