"""Tradfri lights platform tests."""

from copy import deepcopy
from unittest.mock import Mock, MagicMock, patch, PropertyMock

import pytest
from pytradfri.device import Device, LightControl, Light

from homeassistant.components import tradfri

from tests.common import MockConfigEntry


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 == 0
    [
        {'can_set_dimmer': True},
        {'brightness': 0},
        {
            'brightness': 0
        }
    ],
    # Brightness < 0
    [
        {'can_set_dimmer': True},
        {'brightness': -1},
        {
            'brightness': 0
        }
    ],
    # 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(request):
    """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()

    def teardown():
        """Remove patches for pytradfri methods."""
        p_1.stop()
        p_2.stop()

    request.addfinalizer(teardown)


@pytest.fixture
def mock_gateway():
    """Mock a Tradfri gateway."""
    def get_devices():
        """Return mock devices."""
        return gateway.mock_devices

    def get_groups():
        """Return mock groups."""
        return gateway.mock_groups

    gateway = Mock(
        get_devices=get_devices,
        get_groups=get_groups,
        mock_devices=[],
        mock_groups=[],
        mock_responses=[]
    )
    return gateway


@pytest.fixture
def mock_api(mock_gateway):
    """Mock api."""
    async def api(command):
        """Mock api function."""
        # Store the data for "real" command objects.
        if(hasattr(command, '_data') and not isinstance(command, Mock)):
            mock_gateway.mock_responses.append(command._data)
        return command
    return api


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


async def setup_gateway(hass, mock_gateway, mock_api):
    """Load the Tradfri platform with a mock gateway."""
    entry = MockConfigEntry(domain=tradfri.DOMAIN, data={
        'host': 'mock-host',
        'identity': 'mock-identity',
        'key': 'mock-key',
        'import_groups': True,
        'gateway_id': 'mock-gateway-id',
    })
    hass.data[tradfri.KEY_GATEWAY] = {entry.entry_id: mock_gateway}
    hass.data[tradfri.KEY_API] = {entry.entry_id: mock_api}
    await hass.config_entries.async_forward_entry_setup(
        entry, 'light'
    )


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

    mock_light = Mock(
        id='mock-light-id-{}'.format(n),
        reachable=True,
        observe=Mock(),
        device_info=MagicMock()
    )
    mock_light.name = 'tradfri_light_{}'.format(n)

    # Set supported features for the light.
    features = {**DEFAULT_TEST_FEATURES, **test_features}
    lc = LightControl(mock_light)
    for k, v in features.items():
        setattr(lc, k, v)
    # Store the initial state.
    setattr(lc, 'lights', [mock_light_data])
    mock_light.light_control = lc
    return mock_light


async def test_light(hass, mock_gateway, mock_api):
    """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_gateway(hass, mock_gateway, mock_api)

    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, mock_gateway, mock_api):
    """Test that lights are correctly observed."""
    light = mock_light()
    mock_gateway.mock_devices.append(light)
    await setup_gateway(hass, mock_gateway, mock_api)
    assert len(light.observe.mock_calls) > 0


async def test_light_available(hass, mock_gateway, mock_api):
    """Test light available property."""
    light = mock_light({'state': True}, n=1)
    light.reachable = True

    light2 = mock_light({'state': True}, n=2)
    light2.reachable = False

    mock_gateway.mock_devices.append(light)
    mock_gateway.mock_devices.append(light2)
    await setup_gateway(hass, mock_gateway, mock_api)

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

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


# Combine TURN_ON_TEST_CASES and TRANSITION_CASES_FOR_TESTS
ALL_TURN_ON_TEST_CASES = [
    ["test_features", "test_data", "expected_result", "id"],
    []
]

idx = 1
for tc in TURN_ON_TEST_CASES:
    for trans in TRANSITION_CASES_FOR_TESTS:
        case = deepcopy(tc)
        if trans is not None:
            case[1]['transition'] = trans
        case.append(idx)
        idx = idx + 1
        ALL_TURN_ON_TEST_CASES[1].append(case)


@pytest.mark.parametrize(*ALL_TURN_ON_TEST_CASES)
async def test_turn_on(hass,
                       mock_gateway,
                       mock_api,
                       test_features,
                       test_data,
                       expected_result,
                       id):
    """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,
                       n=id)
    mock_gateway.mock_devices.append(light)
    await setup_gateway(hass, mock_gateway, mock_api)

    # Use the turn_on service call to change the light state.
    await hass.services.async_call('light', 'turn_on', {
        'entity_id': 'light.tradfri_light_{}'.format(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.
    cb = callkwargs['callback']

    responses = mock_gateway.mock_responses
    # State on command data.
    data = {'3311': [{'5850': 1}]}
    # Add data for all sent commands.
    for r in responses:
        data['3311'][0] = {**data['3311'][0], **r['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
    cb(light)
    await hass.async_block_till_done()

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


async def test_turn_off(hass, mock_gateway, mock_api):
    """Test turning off a light."""
    state = {
        'state': True,
        'dimmer': 100,
    }

    light = mock_light(test_state=state)
    mock_gateway.mock_devices.append(light)
    await setup_gateway(hass, mock_gateway, mock_api)

    # 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.
    cb = callkwargs['callback']

    responses = mock_gateway.mock_responses
    data = {'3311': [{}]}
    # Add data for all sent commands.
    for r in responses:
        data['3311'][0] = {**data['3311'][0], **r['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
    cb(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'


def mock_group(test_state={}, n=0):
    """Mock a Tradfri group."""
    default_state = {
        'state': False,
        'dimmer': 0,
    }

    state = {**default_state, **test_state}

    mock_group = Mock(
        member_ids=[],
        observe=Mock(),
        **state
    )
    mock_group.name = 'tradfri_group_{}'.format(n)
    return mock_group


async def test_group(hass, mock_gateway, mock_api):
    """Test that groups are correctly added."""
    mock_gateway.mock_groups.append(mock_group())
    state = {'state': True, 'dimmer': 100}
    mock_gateway.mock_groups.append(mock_group(state, 1))
    await setup_gateway(hass, mock_gateway, mock_api)

    group = hass.states.get('light.tradfri_group_0')
    assert group is not None
    assert group.state == 'off'

    group = hass.states.get('light.tradfri_group_1')
    assert group is not None
    assert group.state == 'on'
    assert group.attributes['brightness'] == 100


async def test_group_turn_on(hass, mock_gateway, mock_api):
    """Test turning on a group."""
    group = mock_group()
    group2 = mock_group(n=1)
    group3 = mock_group(n=2)
    mock_gateway.mock_groups.append(group)
    mock_gateway.mock_groups.append(group2)
    mock_gateway.mock_groups.append(group3)
    await setup_gateway(hass, mock_gateway, mock_api)

    # Use the turn_off service call to change the light state.
    await hass.services.async_call('light', 'turn_on', {
        'entity_id': 'light.tradfri_group_0'}, blocking=True)
    await hass.services.async_call('light', 'turn_on', {
        'entity_id': 'light.tradfri_group_1',
        'brightness': 100}, blocking=True)
    await hass.services.async_call('light', 'turn_on', {
        'entity_id': 'light.tradfri_group_2',
        'brightness': 100,
        'transition': 1}, blocking=True)
    await hass.async_block_till_done()

    group.set_state.assert_called_with(1)
    group2.set_dimmer.assert_called_with(100)
    group3.set_dimmer.assert_called_with(100, transition_time=10)


async def test_group_turn_off(hass, mock_gateway, mock_api):
    """Test turning off a group."""
    group = mock_group({'state': True})
    mock_gateway.mock_groups.append(group)
    await setup_gateway(hass, mock_gateway, mock_api)

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

    group.set_state.assert_called_with(0)
