"""Basic checks for HomeKitSwitch."""

from collections.abc import Callable
from unittest import mock

from aiohomekit.model import Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.testing import FakeController
import pytest

from homeassistant.components.homekit_controller.const import KNOWN_DEVICES
from homeassistant.components.light import (
    ATTR_COLOR_MODE,
    ATTR_SUPPORTED_COLOR_MODES,
    ColorMode,
)
from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from .common import setup_test_component

LIGHT_BULB_NAME = "TestDevice"
LIGHT_BULB_ENTITY_ID = "light.testdevice"


def create_lightbulb_service(accessory: Accessory) -> Service:
    """Define lightbulb characteristics."""
    service = accessory.add_service(ServicesTypes.LIGHTBULB, name=LIGHT_BULB_NAME)

    on_char = service.add_char(CharacteristicsTypes.ON)
    on_char.value = 0

    brightness = service.add_char(CharacteristicsTypes.BRIGHTNESS)
    brightness.value = 0

    return service


def create_lightbulb_service_with_hs(accessory: Accessory) -> Service:
    """Define a lightbulb service with hue + saturation."""
    service = create_lightbulb_service(accessory)

    hue = service.add_char(CharacteristicsTypes.HUE)
    hue.value = 0

    saturation = service.add_char(CharacteristicsTypes.SATURATION)
    saturation.value = 0

    return service


def create_lightbulb_service_with_color_temp(accessory: Accessory) -> Service:
    """Define a lightbulb service with color temp."""
    service = create_lightbulb_service(accessory)

    color_temp = service.add_char(CharacteristicsTypes.COLOR_TEMPERATURE)
    color_temp.value = 0

    return service


@pytest.mark.parametrize("brightness", [1, 2, 3])
async def test_turn_on_low_brightness_no_zero(
    hass: HomeAssistant, get_next_aid: Callable[[], int], brightness: int
) -> None:
    """Test low brightness values never send 0 percent to the device.

    Sending brightness 0 with on results in full brightness on some
    devices such as Nanoleaf Essentials bulbs.
    """
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_hs
    )

    await hass.services.async_call(
        "light",
        "turn_on",
        {"entity_id": "light.testdevice", "brightness": brightness},
        blocking=True,
    )
    helper.async_assert_service_values(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 1,
        },
    )


async def test_switch_change_light_state(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can turn a HomeKit light on and off again."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_hs
    )

    await hass.services.async_call(
        "light",
        "turn_on",
        {"entity_id": "light.testdevice", "brightness": 255, "hs_color": [4, 5]},
        blocking=True,
    )
    helper.async_assert_service_values(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.HUE: 4,
            CharacteristicsTypes.SATURATION: 5,
        },
    )

    await hass.services.async_call(
        "light",
        "turn_on",
        {"entity_id": "light.testdevice", "brightness": 255, "color_temp_kelvin": 3333},
        blocking=True,
    )
    helper.async_assert_service_values(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.HUE: 27,
            CharacteristicsTypes.SATURATION: 49,
        },
    )

    await hass.services.async_call(
        "light", "turn_off", {"entity_id": "light.testdevice"}, blocking=True
    )
    helper.async_assert_service_values(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: False,
        },
    )


async def test_switch_change_light_state_color_temp(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can turn change color_temp."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_color_temp
    )

    await hass.services.async_call(
        "light",
        "turn_on",
        {"entity_id": "light.testdevice", "brightness": 255, "color_temp_kelvin": 2500},
        blocking=True,
    )
    helper.async_assert_service_values(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.COLOR_TEMPERATURE: 400,
        },
    )


async def test_switch_read_light_state_dimmer(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can read the state of a HomeKit light accessory."""
    helper = await setup_test_component(hass, get_next_aid(), create_lightbulb_service)

    # Initial state is that the light is off
    state = await helper.poll_and_get_state()
    assert state.state == "off"
    assert state.attributes[ATTR_COLOR_MODE] is None
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.BRIGHTNESS]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0

    # Simulate that someone switched on the device in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes[ATTR_COLOR_MODE] == ColorMode.BRIGHTNESS
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.BRIGHTNESS]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0

    # Simulate that device switched off in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: False,
        },
    )
    assert state.state == "off"


async def test_switch_push_light_state_dimmer(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can read the state of a HomeKit light accessory."""
    helper = await setup_test_component(hass, get_next_aid(), create_lightbulb_service)

    # Initial state is that the light is off
    state = hass.states.get(LIGHT_BULB_ENTITY_ID)
    assert state.state == "off"

    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255

    # Simulate that device switched off in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: False,
        },
    )
    assert state.state == "off"


async def test_switch_read_light_state_hs(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can read the state of a HomeKit light accessory."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_hs
    )

    # Initial state is that the light is off
    state = await helper.poll_and_get_state()
    assert state.state == "off"
    assert state.attributes[ATTR_COLOR_MODE] is None
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0

    # Simulate that someone switched on the device in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.HUE: 4,
            CharacteristicsTypes.SATURATION: 5,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes["hs_color"] == (4, 5)
    assert state.attributes[ATTR_COLOR_MODE] == ColorMode.HS
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0

    # Simulate that device switched off in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: False,
        },
    )
    assert state.state == "off"

    # Simulate that device switched on in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.HUE: 6,
            CharacteristicsTypes.SATURATION: 7,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes["hs_color"] == (6, 7)
    assert state.attributes[ATTR_COLOR_MODE] == ColorMode.HS
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
        ColorMode.COLOR_TEMP,
        ColorMode.HS,
    ]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0


async def test_switch_push_light_state_hs(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can read the state of a HomeKit light accessory."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_hs
    )

    # Initial state is that the light is off
    state = hass.states.get(LIGHT_BULB_ENTITY_ID)
    assert state.state == "off"

    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.HUE: 4,
            CharacteristicsTypes.SATURATION: 5,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes["hs_color"] == (4, 5)

    # Simulate that device switched off in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: False,
        },
    )
    assert state.state == "off"


async def test_switch_read_light_state_color_temp(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can read the color_temp of a  light accessory."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_color_temp
    )

    # Initial state is that the light is off
    state = await helper.poll_and_get_state()
    assert state.state == "off"
    assert state.attributes[ATTR_COLOR_MODE] is None
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.COLOR_TEMP]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0

    # Simulate that someone switched on the device in the real world not via HA
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.COLOR_TEMPERATURE: 400,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes["color_temp_kelvin"] == 2500
    assert state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
    assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.COLOR_TEMP]
    assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0


async def test_switch_push_light_state_color_temp(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test that we can read the state of a HomeKit light accessory."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_color_temp
    )

    # Initial state is that the light is off
    state = hass.states.get(LIGHT_BULB_ENTITY_ID)
    assert state.state == "off"

    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.COLOR_TEMPERATURE: 400,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes["color_temp_kelvin"] == 2500


async def test_light_becomes_unavailable_but_recovers(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test transition to and from unavailable state."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_color_temp
    )

    # Initial state is that the light is off
    state = await helper.poll_and_get_state()
    assert state.state == "off"

    # Test device goes offline
    helper.pairing.available = False
    with mock.patch.object(
        FakeController,
        "async_reachable",
        return_value=False,
    ):
        state = await helper.poll_and_get_state()
    assert state.state == "unavailable"

    # Simulate that someone switched on the device in the real world not via HA
    helper.pairing.available = True
    state = await helper.async_update(
        ServicesTypes.LIGHTBULB,
        {
            CharacteristicsTypes.ON: True,
            CharacteristicsTypes.BRIGHTNESS: 100,
            CharacteristicsTypes.COLOR_TEMPERATURE: 400,
        },
    )
    assert state.state == "on"
    assert state.attributes["brightness"] == 255
    assert state.attributes["color_temp_kelvin"] == 2500


async def test_light_unloaded_removed(
    hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
    """Test entity and HKDevice are correctly unloaded and removed."""
    helper = await setup_test_component(
        hass, get_next_aid(), create_lightbulb_service_with_color_temp
    )

    # Initial state is that the light is off
    state = await helper.poll_and_get_state()
    assert state.state == "off"

    unload_result = await hass.config_entries.async_unload(helper.config_entry.entry_id)
    assert unload_result is True

    # Make sure entity is set to unavailable state
    assert hass.states.get(helper.entity_id).state == STATE_UNAVAILABLE

    # Make sure HKDevice is no longer set to poll this accessory
    conn = hass.data[KNOWN_DEVICES]["00:00:00:00:00:00"]
    assert not conn.pollable_characteristics

    await hass.config_entries.async_remove(helper.config_entry.entry_id)
    await hass.async_block_till_done()

    # Make sure entity is removed
    assert hass.states.get(helper.entity_id) is None


async def test_migrate_unique_id(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    get_next_aid: Callable[[], int],
) -> None:
    """Test a we can migrate a light unique id."""
    aid = get_next_aid()
    light_entry = entity_registry.async_get_or_create(
        "light",
        "homekit_controller",
        f"homekit-00:00:00:00:00:00-{aid}-8",
    )
    await setup_test_component(hass, aid, create_lightbulb_service_with_color_temp)

    assert (
        entity_registry.async_get(light_entry.entity_id).unique_id
        == f"00:00:00:00:00:00_{aid}_8"
    )


async def test_only_migrate_once(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    get_next_aid: Callable[[], int],
) -> None:
    """Test handling migration across upgrade/downgrade/upgrade cycle."""
    aid = get_next_aid()
    old_light_entry = entity_registry.async_get_or_create(
        "light",
        "homekit_controller",
        f"homekit-00:00:00:00:00:00-{aid}-8",
    )
    new_light_entry = entity_registry.async_get_or_create(
        "light",
        "homekit_controller",
        f"00:00:00:00:00:00_{aid}_8",
    )
    await setup_test_component(hass, aid, create_lightbulb_service_with_color_temp)

    assert (
        entity_registry.async_get(old_light_entry.entity_id).unique_id
        == f"homekit-00:00:00:00:00:00-{aid}-8"
    )

    assert (
        entity_registry.async_get(new_light_entry.entity_id).unique_id
        == f"00:00:00:00:00:00_{aid}_8"
    )
