"""Tradfri lights platform tests."""
from copy import deepcopy
from unittest.mock import MagicMock, Mock, PropertyMock, patch

import pytest
from pytradfri.device import Device
from pytradfri.device.light import Light
from pytradfri.device.light_control import LightControl

from homeassistant.core import HomeAssistant

from .common import setup_integration

DEFAULT_TEST_FEATURES = {
    "can_set_dimmer": False,
    "can_set_color": False,
    "can_set_temp": False,
}
# [
#     {bulb features},
#     {turn_on arguments},
#     {expected result}
# ]
TURN_ON_TEST_CASES = [
    # Turn On
    [{}, {}, {"state": "on"}],
    # Brightness > 0
    [{"can_set_dimmer": True}, {"brightness": 100}, {"state": "on", "brightness": 100}],
    # Brightness == 1
    [{"can_set_dimmer": True}, {"brightness": 1}, {"brightness": 1}],
    # Brightness > 254
    [{"can_set_dimmer": True}, {"brightness": 1000}, {"brightness": 254}],
    # color_temp
    [{"can_set_temp": True}, {"color_temp": 250}, {"color_temp": 250}],
    # color_temp < 250
    [{"can_set_temp": True}, {"color_temp": 1}, {"color_temp": 250}],
    # color_temp > 454
    [{"can_set_temp": True}, {"color_temp": 1000}, {"color_temp": 454}],
    # hs color
    [
        {"can_set_color": True},
        {"hs_color": [300, 100]},
        {"state": "on", "hs_color": [300, 100]},
    ],
    # ct + brightness
    [
        {"can_set_dimmer": True, "can_set_temp": True},
        {"color_temp": 250, "brightness": 200},
        {"state": "on", "color_temp": 250, "brightness": 200},
    ],
    # ct + brightness (no temp support)
    [
        {"can_set_dimmer": True, "can_set_temp": False, "can_set_color": True},
        {"color_temp": 250, "brightness": 200},
        {"state": "on", "hs_color": [26.807, 34.869], "brightness": 200},
    ],
    # ct + brightness (no temp or color support)
    [
        {"can_set_dimmer": True, "can_set_temp": False, "can_set_color": False},
        {"color_temp": 250, "brightness": 200},
        {"state": "on", "brightness": 200},
    ],
    # hs + brightness
    [
        {"can_set_dimmer": True, "can_set_color": True},
        {"hs_color": [300, 100], "brightness": 200},
        {"state": "on", "hs_color": [300, 100], "brightness": 200},
    ],
]

# Result of transition is not tested, but data is passed to turn on service.
TRANSITION_CASES_FOR_TESTS = [None, 0, 1]


@pytest.fixture(autouse=True, scope="module")
def setup():
    """Set up patches for pytradfri methods."""
    p_1 = patch(
        "pytradfri.device.LightControl.raw",
        new_callable=PropertyMock,
        return_value=[{"mock": "mock"}],
    )
    p_2 = patch("pytradfri.device.LightControl.lights")
    p_1.start()
    p_2.start()

    yield

    p_1.stop()
    p_2.stop()


async def generate_psk(self, code):
    """Mock psk."""
    return "mock"


def mock_light(test_features=None, test_state=None, light_number=0):
    """Mock a tradfri light."""
    if test_features is None:
        test_features = {}
    if test_state is None:
        test_state = {}
    mock_light_data = Mock(**test_state)

    dev_info_mock = MagicMock()
    dev_info_mock.manufacturer = "manufacturer"
    dev_info_mock.model_number = "model"
    dev_info_mock.firmware_version = "1.2.3"
    _mock_light = Mock(
        id=f"mock-light-id-{light_number}",
        reachable=True,
        observe=Mock(),
        device_info=dev_info_mock,
        has_light_control=True,
        has_socket_control=False,
        has_blind_control=False,
        has_signal_repeater_control=False,
        has_air_purifier_control=False,
    )
    _mock_light.name = f"tradfri_light_{light_number}"

    # Set supported features for the light.
    features = {**DEFAULT_TEST_FEATURES, **test_features}
    light_control = LightControl(_mock_light)
    for attr, value in features.items():
        setattr(light_control, attr, value)
    # Store the initial state.
    setattr(light_control, "lights", [mock_light_data])
    _mock_light.light_control = light_control
    return _mock_light


async def test_light(hass: HomeAssistant, mock_gateway, mock_api_factory) -> None:
    """Test that lights are correctly added."""
    features = {"can_set_dimmer": True, "can_set_color": True, "can_set_temp": True}

    state = {
        "state": True,
        "dimmer": 100,
        "color_temp": 250,
        "hsb_xy_color": (100, 100, 100, 100, 100),
    }

    mock_gateway.mock_devices.append(
        mock_light(test_features=features, test_state=state)
    )
    await setup_integration(hass)

    lamp_1 = hass.states.get("light.tradfri_light_0")
    assert lamp_1 is not None
    assert lamp_1.state == "on"
    assert lamp_1.attributes["brightness"] == 100
    assert lamp_1.attributes["hs_color"] == (0.549, 0.153)


async def test_light_observed(
    hass: HomeAssistant, mock_gateway, mock_api_factory
) -> None:
    """Test that lights are correctly observed."""
    light = mock_light()
    mock_gateway.mock_devices.append(light)
    await setup_integration(hass)
    assert len(light.observe.mock_calls) > 0


async def test_light_available(
    hass: HomeAssistant, mock_gateway, mock_api_factory
) -> None:
    """Test light available property."""
    light = mock_light({"state": True}, light_number=1)
    light.reachable = True

    light2 = mock_light({"state": True}, light_number=2)
    light2.reachable = False

    mock_gateway.mock_devices.append(light)
    mock_gateway.mock_devices.append(light2)
    await setup_integration(hass)

    assert hass.states.get("light.tradfri_light_1").state == "on"

    assert hass.states.get("light.tradfri_light_2").state == "unavailable"


def create_all_turn_on_cases():
    """Create all turn on test cases."""
    # Combine TURN_ON_TEST_CASES and TRANSITION_CASES_FOR_TESTS
    all_turn_on_test_cases = [
        ["test_features", "test_data", "expected_result", "device_id"],
        [],
    ]
    index = 1
    for test_case in TURN_ON_TEST_CASES:
        for trans in TRANSITION_CASES_FOR_TESTS:
            case = deepcopy(test_case)
            if trans is not None:
                case[1]["transition"] = trans
            case.append(index)
            index += 1
            all_turn_on_test_cases[1].append(case)

    return all_turn_on_test_cases


@pytest.mark.parametrize(*create_all_turn_on_cases())
async def test_turn_on(
    hass: HomeAssistant,
    mock_gateway,
    mock_api_factory,
    test_features,
    test_data,
    expected_result,
    device_id,
) -> None:
    """Test turning on a light."""
    # Note pytradfri style, not hass. Values not really important.
    initial_state = {
        "state": False,
        "dimmer": 0,
        "color_temp": 250,
        "hsb_xy_color": (100, 100, 100, 100, 100),
    }

    # Setup the gateway with a mock light.
    light = mock_light(
        test_features=test_features, test_state=initial_state, light_number=device_id
    )
    mock_gateway.mock_devices.append(light)
    await setup_integration(hass)

    # Use the turn_on service call to change the light state.
    await hass.services.async_call(
        "light",
        "turn_on",
        {"entity_id": f"light.tradfri_light_{device_id}", **test_data},
        blocking=True,
    )
    await hass.async_block_till_done()

    # Check that the light is observed.
    mock_func = light.observe
    assert len(mock_func.mock_calls) > 0
    _, callkwargs = mock_func.call_args
    assert "callback" in callkwargs
    # Callback function to refresh light state.
    callback = callkwargs["callback"]

    responses = mock_gateway.mock_responses
    # State on command data.
    data = {"3311": [{"5850": 1}]}
    # Add data for all sent commands.
    for resp in responses:
        data["3311"][0] = {**data["3311"][0], **resp["3311"][0]}

    # Use the callback function to update the light state.
    dev = Device(data)
    light_data = Light(dev, 0)
    light.light_control.lights[0] = light_data
    callback(light)
    await hass.async_block_till_done()

    # Check that the state is correct.
    states = hass.states.get(f"light.tradfri_light_{device_id}")
    for result, value in expected_result.items():
        if result == "state":
            assert states.state == value
        else:
            # Allow some rounding error in color conversions.
            assert states.attributes[result] == pytest.approx(value, abs=0.01)


async def test_turn_off(hass: HomeAssistant, mock_gateway, mock_api_factory) -> None:
    """Test turning off a light."""
    state = {"state": True, "dimmer": 100}

    light = mock_light(test_state=state)
    mock_gateway.mock_devices.append(light)
    await setup_integration(hass)

    # Use the turn_off service call to change the light state.
    await hass.services.async_call(
        "light", "turn_off", {"entity_id": "light.tradfri_light_0"}, blocking=True
    )
    await hass.async_block_till_done()

    # Check that the light is observed.
    mock_func = light.observe
    assert len(mock_func.mock_calls) > 0
    _, callkwargs = mock_func.call_args
    assert "callback" in callkwargs
    # Callback function to refresh light state.
    callback = callkwargs["callback"]

    responses = mock_gateway.mock_responses
    data = {"3311": [{}]}
    # Add data for all sent commands.
    for resp in responses:
        data["3311"][0] = {**data["3311"][0], **resp["3311"][0]}

    # Use the callback function to update the light state.
    dev = Device(data)
    light_data = Light(dev, 0)
    light.light_control.lights[0] = light_data
    callback(light)
    await hass.async_block_till_done()

    # Check that the state is correct.
    states = hass.states.get("light.tradfri_light_0")
    assert states.state == "off"
