import pytest
from pytest_mock import MockerFixture

from kasa import Module, SmartDevice

from ...device_fixtures import get_parent_and_child_modules, parametrize

powerprotection = parametrize(
    "has powerprotection",
    component_filter="power_protection",
    protocol_filter={"SMART"},
)


@powerprotection
@pytest.mark.parametrize(
    ("feature", "prop_name", "type"),
    [
        ("overloaded", "overloaded", bool),
        ("power_protection_threshold", "protection_threshold", int),
    ],
)
async def test_features(dev, feature, prop_name, type):
    """Test that features are registered and work as expected."""
    powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection))
    assert powerprot
    device = powerprot._device

    prop = getattr(powerprot, prop_name)
    assert isinstance(prop, type)

    feat = device.features[feature]
    assert feat.value == prop
    assert isinstance(feat.value, type)


@powerprotection
async def test_set_enable(dev: SmartDevice, mocker: MockerFixture):
    """Test enable."""
    powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection))
    assert powerprot
    device = powerprot._device

    original_enabled = powerprot.enabled
    original_threshold = powerprot.protection_threshold

    try:
        # Simple enable with an existing threshold
        call_spy = mocker.spy(powerprot, "call")
        await powerprot.set_enabled(True)
        params = {
            "enabled": True,
            "protection_power": mocker.ANY,
        }
        call_spy.assert_called_with("set_protection_power", params)

        # Enable with no threshold param when 0
        call_spy.reset_mock()
        await powerprot.set_protection_threshold(0)
        await device.update()
        await powerprot.set_enabled(True)
        params = {
            "enabled": True,
            "protection_power": int(powerprot._max_power / 2),
        }
        call_spy.assert_called_with("set_protection_power", params)

        # Enable false should not update the threshold
        call_spy.reset_mock()
        await powerprot.set_protection_threshold(0)
        await device.update()
        await powerprot.set_enabled(False)
        params = {
            "enabled": False,
            "protection_power": 0,
        }
        call_spy.assert_called_with("set_protection_power", params)

    finally:
        await powerprot.set_enabled(original_enabled, threshold=original_threshold)


@powerprotection
async def test_set_threshold(dev: SmartDevice, mocker: MockerFixture):
    """Test enable."""
    powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection))
    assert powerprot

    call_spy = mocker.spy(powerprot, "call")
    await powerprot.set_protection_threshold(123)
    params = {
        "enabled": mocker.ANY,
        "protection_power": 123,
    }
    call_spy.assert_called_with("set_protection_power", params)

    with pytest.raises(ValueError, match="Threshold out of range"):
        await powerprot.set_protection_threshold(-10)