import datetime
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from bleak.backends.device import BLEDevice

from switchbot import (
    HumidifierAction,
    HumidifierMode,
    HumidifierWaterLevel,
    SwitchBotAdvertisement,
    SwitchbotModel,
)
from switchbot.devices import evaporative_humidifier
from switchbot.devices.device import SwitchbotEncryptedDevice, SwitchbotOperationError

from .test_adv_parser import generate_ble_device


def create_device_for_command_testing(init_data: dict | None = None):
    ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
    evaporative_humidifier_device = (
        evaporative_humidifier.SwitchbotEvaporativeHumidifier(
            ble_device, "ff", "ffffffffffffffffffffffffffffffff"
        )
    )
    evaporative_humidifier_device.update_from_advertisement(
        make_advertisement_data(ble_device, init_data)
    )
    evaporative_humidifier_device._send_command = AsyncMock()
    evaporative_humidifier_device._check_command_result = MagicMock()
    evaporative_humidifier_device.update = AsyncMock()
    return evaporative_humidifier_device


def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None):
    if init_data is None:
        init_data = {}
    """Set advertisement data with defaults."""
    return SwitchBotAdvertisement(
        address="aa:bb:cc:dd:ee:ff",
        data={
            "rawAdvData": b"#\x00\x00\x15\x1c\x00",
            "data": {
                "isOn": False,
                "mode": None,
                "target_humidity": 52,
                "child_lock": False,
                "over_humidify_protection": True,
                "tank_removed": False,
                "tilted_alert": False,
                "filter_missing": False,
                "is_meter_binded": True,
                "humidity": 51,
                "temperature": 16.8,
                "temp": {"c": 16.8, "f": 62.24},
                "filter_run_time": datetime.timedelta(days=3, seconds=57600),
                "filter_alert": False,
                "water_level": "medium",
            }
            | init_data,
            "isEncrypted": False,
            "model": "#",
            "modelFriendlyName": "Evaporative Humidifier",
            "modelName": SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
        },
        device=ble_device,
        rssi=-80,
        active=True,
    )


@pytest.mark.asyncio
async def test_turn_on():
    """Test the turn_on method."""
    device = create_device_for_command_testing({"isOn": True})
    await device.turn_on()
    assert device.is_on() is True


@pytest.mark.asyncio
async def test_turn_off():
    """Test the turn_off method."""
    device = create_device_for_command_testing({"isOn": False})
    await device.turn_off()
    assert device.is_on() is False


@pytest.mark.asyncio
async def test_get_basic_is_none():
    """Test the get_basic_info when it returns None."""
    device = create_device_for_command_testing()
    device._get_basic_info = AsyncMock(return_value=None)
    assert await device.get_basic_info() is None


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("basic_info", "result"),
    [
        (
            bytearray(b"\x01\x86\x88\xb1\x98\x82\x00\x1e\x00\x88-\xc4\xff\xff \n\x07"),
            [
                True,
                HumidifierMode(6),
                True,
                False,
                False,
                False,
                False,
                True,
                49,
                24.8,
                24.8,
                76.64,
                "medium",
                30,
                45,
            ],
        ),
        (
            bytearray(b"\x01\x08 \xb1\x98r\x00\x1e\x00\x89-\xc4\xff\xff\x00\x00\x00"),
            [
                False,
                HumidifierMode(8),
                False,
                True,
                False,
                False,
                False,
                True,
                49,
                24.7,
                24.7,
                76.46,
                "medium",
                30,
                45,
            ],
        ),
    ],
)
async def test_get_basic_info(basic_info, result):
    """Test the get_basic_info method."""
    device = create_device_for_command_testing()
    device._get_basic_info = AsyncMock(return_value=basic_info)

    info = await device.get_basic_info()
    assert info["isOn"] is result[0]
    assert info["mode"] == result[1]
    assert info["over_humidify_protection"] is result[2]
    assert info["child_lock"] is result[3]
    assert info["tank_removed"] is result[4]
    assert info["tilted_alert"] is result[5]
    assert info["filter_missing"] is result[6]
    assert info["is_meter_binded"] is result[7]
    assert info["humidity"] == result[8]
    assert info["temperature"] == result[9]
    assert info["temp"]["c"] == result[10]
    assert info["temp"]["f"] == result[11]
    assert info["water_level"] == result[12]
    assert info["filter_run_time"] == result[13]
    assert info["target_humidity"] == result[14]


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("err_msg", "mode", "water_level"),
    [
        (
            "Target humidity can only be set in target humidity mode or sleep mode",
            HumidifierMode.AUTO,
            "low",
        ),
        (
            "Cannot perform operation when water tank is empty",
            HumidifierMode.TARGET_HUMIDITY,
            "empty",
        ),
    ],
)
async def test_set_target_humidity_with_invalid_conditions(err_msg, mode, water_level):
    """Test setting target humidity with invalid mode."""
    device = create_device_for_command_testing()
    device.get_mode = MagicMock(return_value=mode)
    device.get_water_level = MagicMock(return_value=water_level)
    with pytest.raises(SwitchbotOperationError, match=err_msg):
        await device.set_target_humidity(45)


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("err_msg", "mode", "water_level", "is_meter_binded", "target_humidity"),
    [
        (
            "Cannot perform operation when water tank is empty",
            HumidifierMode.TARGET_HUMIDITY,
            "empty",
            True,
            45,
        ),
        (
            "Cannot set target humidity or auto mode when meter is not binded",
            HumidifierMode.TARGET_HUMIDITY,
            "medium",
            False,
            45,
        ),
        (
            "Target humidity must be set before switching to target humidity mode or sleep mode",
            HumidifierMode.TARGET_HUMIDITY,
            "medium",
            True,
            None,
        ),
    ],
)
async def test_set_mode_with_invalid_conditions(
    err_msg, mode, water_level, is_meter_binded, target_humidity
):
    """Test setting target humidity with invalid mode."""
    device = create_device_for_command_testing()
    device.get_water_level = MagicMock(return_value=water_level)
    device.is_meter_binded = MagicMock(return_value=is_meter_binded)
    device.get_target_humidity = MagicMock(return_value=target_humidity)
    with pytest.raises(SwitchbotOperationError, match=err_msg):
        await device.set_mode(mode)


@pytest.mark.asyncio
async def test_set_target_humidity():
    """Test setting target humidity."""
    device = create_device_for_command_testing()
    device.get_mode = MagicMock(return_value=HumidifierMode.TARGET_HUMIDITY)

    await device.set_target_humidity(45)
    device._send_command.assert_awaited_once_with("570f430202002d")


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("mode", "command"),
    [
        (HumidifierMode.TARGET_HUMIDITY, "570f430202002d"),
        (HumidifierMode.AUTO, "570f4302040000"),
        (HumidifierMode.SLEEP, "570f430203002d"),
        (HumidifierMode.DRYING_FILTER, "570f43010108"),
    ],
)
async def test_set_mode(mode, command):
    """Test setting mode."""
    device = create_device_for_command_testing()
    device._validate_water_level(mode)
    device.get_target_humidity = MagicMock(return_value=45)

    await device.set_mode(mode)
    device._send_command.assert_awaited_once_with(command)


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("init_data", "result"),
    [
        (
            {"isOn": False, "mode": HumidifierMode.AUTO},
            [False, HumidifierMode.AUTO, HumidifierAction.OFF],
        ),
        (
            {"isOn": True, "mode": HumidifierMode.TARGET_HUMIDITY},
            [True, HumidifierMode.TARGET_HUMIDITY, HumidifierAction.HUMIDIFYING],
        ),
        (
            {"isOn": True, "mode": HumidifierMode.DRYING_FILTER},
            [True, HumidifierMode.DRYING_FILTER, HumidifierAction.DRYING],
        ),
    ],
)
async def test_status_from_process_adv(init_data, result):
    """Test status from process advertisement."""
    device = create_device_for_command_testing(init_data)

    assert device.is_on() is result[0]
    assert device.get_mode() is result[1]
    assert device.is_child_lock_enabled() is False
    assert device.is_over_humidify_protection_enabled() is True
    assert device.is_tank_removed() is False
    assert device.is_filter_missing() is False
    assert device.is_filter_alert_on() is False
    assert device.is_tilted_alert_on() is False
    assert device.get_water_level() == "medium"
    assert device.get_filter_run_time() == datetime.timedelta(days=3, seconds=57600)
    assert device.get_target_humidity() == 52
    assert device.get_humidity() == 51
    assert device.get_temperature() == 16.8
    assert device.get_action() == result[2]
    assert device.is_meter_binded() is True


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("enabled", "command"),
    [
        (True, "570f430501"),
        (False, "570f430500"),
    ],
)
async def test_set_child_lock(enabled, command):
    """Test setting child lock."""
    device = create_device_for_command_testing()
    await device.set_child_lock(enabled)
    device._send_command.assert_awaited_once_with(command)


@pytest.mark.asyncio
@patch.object(SwitchbotEncryptedDevice, "verify_encryption_key", new_callable=AsyncMock)
async def test_verify_encryption_key(mock_parent_verify):
    ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
    key_id = "ff"
    encryption_key = "ffffffffffffffffffffffffffffffff"

    mock_parent_verify.return_value = True

    result = await evaporative_humidifier.SwitchbotEvaporativeHumidifier.verify_encryption_key(
        device=ble_device,
        key_id=key_id,
        encryption_key=encryption_key,
    )

    mock_parent_verify.assert_awaited_once_with(
        ble_device,
        key_id,
        encryption_key,
        SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
    )

    assert result is True


def test_evaporative_humidifier_modes():
    assert HumidifierMode.get_modes() == [
        "high",
        "medium",
        "low",
        "quiet",
        "target_humidity",
        "sleep",
        "auto",
        "drying_filter",
    ]


def test_evaporative_humidifier_water_levels():
    assert HumidifierWaterLevel.get_levels() == [
        "empty",
        "low",
        "medium",
        "high",
    ]
