from datetime import datetime

import pytest

from kasa import Device, KasaException, Module
from kasa.iot import IotStrip

from .conftest import handle_turn_on, strip, strip_iot, turn_on


@strip
@turn_on
async def test_children_change_state(dev, turn_on):
    await handle_turn_on(dev, turn_on)
    for plug in dev.children:
        orig_state = plug.is_on
        if orig_state:
            await plug.turn_off()
            await dev.update()
            assert plug.is_on is False
            assert plug.is_off is True

            await plug.turn_on()
            await dev.update()
            assert plug.is_on is True
            assert plug.is_off is False
        else:
            await plug.turn_on()
            await dev.update()
            assert plug.is_on is True
            assert plug.is_off is False

            await plug.turn_off()
            await dev.update()
            assert plug.is_on is False
            assert plug.is_off is True


@strip
async def test_children_alias(dev):
    test_alias = "TEST1234"
    for plug in dev.children:
        original = plug.alias
        await plug.set_alias(alias=test_alias)
        await dev.update()  # TODO: set_alias does not call parent's update()..
        assert plug.alias == test_alias

        await plug.set_alias(alias=original)
        await dev.update()  # TODO: set_alias does not call parent's update()..
        assert plug.alias == original


@strip
async def test_children_on_since(dev):
    on_sinces = []
    for plug in dev.children:
        if plug.is_on:
            on_sinces.append(plug.on_since)
            assert isinstance(plug.on_since, datetime)
        else:
            assert plug.on_since is None

    if dev.is_off:
        assert dev.on_since is None
    # TODO: testing this would require some mocking utcnow which is not
    # very straightforward.
    # else:
    #    assert dev.on_since == max(on_sinces)


@strip
async def test_get_plug_by_name(dev: IotStrip):
    name = dev.children[0].alias
    assert dev.get_plug_by_name(name) == dev.children[0]  # type: ignore[arg-type]

    with pytest.raises(KasaException):
        dev.get_plug_by_name("NONEXISTING NAME")


@strip
async def test_get_plug_by_index(dev: IotStrip):
    assert dev.get_plug_by_index(0) == dev.children[0]

    with pytest.raises(KasaException):
        dev.get_plug_by_index(-1)

    with pytest.raises(KasaException):
        dev.get_plug_by_index(len(dev.children))


@strip
async def test_plug_features(dev: IotStrip):
    """Test the child plugs have default features."""
    for child in dev.children:
        assert "state" in child.features
        assert "on_since" in child.features


@pytest.mark.skip("this test will wear out your relays")
async def test_all_binary_states(dev):
    # test every binary state
    # TODO: this needs to be fixed, dev.plugs is not available for each device..
    for state in range(2 ** len(dev.children)):
        # create binary state map
        state_map = {}
        for plug_index in range(len(dev.children)):
            state_map[plug_index] = bool((state >> plug_index) & 1)

            if state_map[plug_index]:
                await dev.turn_on(index=plug_index)
            else:
                await dev.turn_off(index=plug_index)

        # check state map applied
        for index, state in dev.is_on.items():
            assert state_map[index] == state

        # toggle each outlet with state map applied
        for plug_index in range(len(dev.children)):
            # toggle state
            if state_map[plug_index]:
                await dev.turn_off(index=plug_index)
            else:
                await dev.turn_on(index=plug_index)

            # only target outlet should have state changed
            for index, state in dev.is_on.items():
                if index == plug_index:
                    assert state != state_map[index]
                else:
                    assert state == state_map[index]

            # reset state
            if state_map[plug_index]:
                await dev.turn_on(index=plug_index)
            else:
                await dev.turn_off(index=plug_index)

            # original state map should be restored
            for index, state in dev.is_on.items():
                assert state == state_map[index]


@strip
def test_children_api(dev):
    """Test the child device API."""
    first = dev.children[0]
    first_by_get_child_device = dev.get_child_device(first.device_id)
    assert first == first_by_get_child_device


@strip_iot
async def test_children_energy(dev: Device):
    if Module.Energy not in dev.modules:
        pytest.skip(f"skipping device {dev.model} does not support energy")

    for plug in dev.children:
        # For now all known strips with energy support these
        energy = plug.modules[Module.Energy]
        assert "voltage" in energy._module_features
        assert "current" in energy._module_features
        assert "current_consumption" in energy._module_features
