import datetime
from unittest.mock import AsyncMock

import pytest
from bleak.backends.device import BLEDevice

from switchbot import SwitchBotAdvertisement, SwitchbotModel
from switchbot.adv_parsers.humidifier import process_evaporative_humidifier
from switchbot.const.evaporative_humidifier import HumidifierMode, HumidifierWaterLevel
from switchbot.devices import evaporative_humidifier

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)
    )
    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": None,
                "child_lock": False,
                "over_humidify_protection": True,
                "tank_removed": False,
                "tilted_alert": False,
                "filter_missing": False,
                "humidity": 51,
                "temperature": 16.8,
                "filter_run_time": datetime.timedelta(days=3, seconds=57600),
                "filter_alert": False,
                "water_level": HumidifierWaterLevel.LOW,
            }
            | 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_process_advertisement():
    data = process_evaporative_humidifier(
        b"#\x00\x00\x15\x1c\x00",
        b"\xd4\x8cIU\x95\xb2\x08\x06\x88\xb3\x90\x81\x00X\x00X2",
    )

    assert data == {
        "isOn": False,
        "mode": None,
        "target_humidity": None,
        "child_lock": False,
        "over_humidify_protection": None,
        "tank_removed": False,
        "tilted_alert": False,
        "filter_missing": False,
        "humidity": 51,
        "temperature": 16.8,
        "filter_run_time": datetime.timedelta(days=3, seconds=57600),
        "filter_alert": False,
        "water_level": HumidifierWaterLevel.LOW,
    }


@pytest.mark.asyncio
async def test_process_advertisement_empty():
    data = process_evaporative_humidifier(None, None)

    assert data == {
        "isOn": None,
        "mode": None,
        "target_humidity": None,
        "child_lock": None,
        "over_humidify_protection": None,
        "tank_removed": None,
        "tilted_alert": None,
        "filter_missing": None,
        "humidity": None,
        "temperature": None,
        "filter_run_time": None,
        "filter_alert": None,
        "water_level": None,
    }


@pytest.mark.asyncio
async def test_turn_on():
    device = create_device_for_command_testing({"isOn": False})
    device._send_command = AsyncMock(return_value=b"\x01")

    assert device.is_on() is False
    await device.turn_on()
    assert device.is_on() is True


@pytest.mark.asyncio
async def test_turn_off():
    device = create_device_for_command_testing({"isOn": True})
    device._send_command = AsyncMock(return_value=b"\x01")

    assert device.is_on() is True
    await device.turn_off()
    assert device.is_on() is False


@pytest.mark.asyncio
async def test_set_mode():
    device = create_device_for_command_testing(
        {"isOn": True, "mode": HumidifierMode.LOW}
    )
    device._send_command = AsyncMock(return_value=b"\x01")

    assert device.get_mode() is HumidifierMode.LOW
    await device.set_mode(HumidifierMode.AUTO)
    assert device.get_mode() is HumidifierMode.AUTO

    await device.set_mode(HumidifierMode.TARGET_HUMIDITY, 60)
    assert device.get_mode() is HumidifierMode.TARGET_HUMIDITY
    assert device.get_target_humidity() == 60

    await device.set_mode(HumidifierMode.DRYING_FILTER)
    assert device.get_mode() is HumidifierMode.DRYING_FILTER

    with pytest.raises(ValueError):
        await device.set_mode(0)

    with pytest.raises(TypeError):
        await device.set_mode(HumidifierMode.TARGET_HUMIDITY)


@pytest.mark.asyncio
async def test_set_child_lock():
    device = create_device_for_command_testing({"child_lock": False})
    device._send_command = AsyncMock(return_value=b"\x01")

    assert device.is_child_lock_enabled() is False
    await device.set_child_lock(True)
    assert device.is_child_lock_enabled() is True


@pytest.mark.asyncio
async def test_start_drying_filter():
    device = create_device_for_command_testing(
        {"isOn": True, "mode": HumidifierMode.AUTO}
    )
    device._send_command = AsyncMock(return_value=b"\x01")

    assert device.get_mode() is HumidifierMode.AUTO
    await device.start_drying_filter()
    assert device.get_mode() is HumidifierMode.DRYING_FILTER


@pytest.mark.asyncio
async def test_stop_drying_filter():
    device = create_device_for_command_testing(
        {"isOn": True, "mode": HumidifierMode.DRYING_FILTER}
    )
    device._send_command = AsyncMock(return_value=b"\x00")

    assert device.is_on() is True
    assert device.get_mode() is HumidifierMode.DRYING_FILTER
    await device.stop_drying_filter()
    assert device.is_on() is False
    assert device.get_mode() is None


@pytest.mark.asyncio
async def test_attributes():
    device = create_device_for_command_testing()
    device._send_command = AsyncMock(return_value=b"\x01")

    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() is HumidifierWaterLevel.LOW
    assert device.get_filter_run_time() == datetime.timedelta(days=3, seconds=57600)
    assert device.get_humidity() == 51
    assert device.get_temperature() == 16.8