"""Philips Hue switch platform tests for V2 bridge/api."""

from unittest.mock import Mock

import pytest

from homeassistant.components.hue.const import DOMAIN
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util.json import JsonArrayType

from .conftest import setup_platform
from .const import (
    FAKE_BEHAVIOR_INSTANCE,
    FAKE_BEHAVIOR_SCRIPT,
    FAKE_BINARY_SENSOR,
    FAKE_DEVICE,
    FAKE_PRESENCE_MIMICKING_INSTANCE,
    FAKE_PRESENCE_MIMICKING_SCRIPT,
    FAKE_ZIGBEE_CONNECTIVITY,
)


async def test_switch(
    hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
) -> None:
    """Test if (config) switches get created."""
    await mock_bridge_v2.api.load_test_data(v2_resources_test_data)

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)
    # there shouldn't have been any requests at this point
    assert len(mock_bridge_v2.mock_requests) == 0
    # 4 entities should be created from test data
    assert len(hass.states.async_all()) == 4

    # test config switch to enable/disable motion sensor
    test_entity = hass.states.get("switch.hue_motion_sensor_motion_sensor_enabled")
    assert test_entity is not None
    assert test_entity.name == "Hue motion sensor Motion sensor enabled"
    assert test_entity.state == "on"
    assert test_entity.attributes["device_class"] == "switch"

    # test config switch to enable/disable a behavior_instance resource (=builtin
    # automation)
    test_entity = hass.states.get("switch.philips_hue_automation_timer_test")
    assert test_entity is not None
    assert test_entity.name == "Philips hue Automation: Timer Test"
    assert test_entity.state == "on"
    assert test_entity.attributes["device_class"] == "switch"


async def test_switch_turn_on_service(
    hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
) -> None:
    """Test calling the turn on service on a switch."""
    await mock_bridge_v2.api.load_test_data(v2_resources_test_data)

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    test_entity_id = "switch.hue_motion_sensor_motion_sensor_enabled"

    # call the HA turn_on service
    await hass.services.async_call(
        "switch",
        "turn_on",
        {"entity_id": test_entity_id},
        blocking=True,
    )

    # PUT request should have been sent to device with correct params
    assert len(mock_bridge_v2.mock_requests) == 1
    assert mock_bridge_v2.mock_requests[0]["method"] == "put"
    assert mock_bridge_v2.mock_requests[0]["json"]["enabled"] is True


async def test_switch_turn_off_service(
    hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
) -> None:
    """Test calling the turn off service on a switch."""
    await mock_bridge_v2.api.load_test_data(v2_resources_test_data)

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    test_entity_id = "switch.hue_motion_sensor_motion_sensor_enabled"

    # verify the switch is on before we start
    assert hass.states.get(test_entity_id).state == "on"

    # now call the HA turn_off service
    await hass.services.async_call(
        "switch",
        "turn_off",
        {"entity_id": test_entity_id},
        blocking=True,
    )

    # PUT request should have been sent to device with correct params
    assert len(mock_bridge_v2.mock_requests) == 1
    assert mock_bridge_v2.mock_requests[0]["method"] == "put"
    assert mock_bridge_v2.mock_requests[0]["json"]["enabled"] is False

    # Now generate update event by emitting the json we've sent as incoming event
    event = {
        "id": "b6896534-016d-4052-8cb4-ef04454df62c",
        "type": "motion",
        **mock_bridge_v2.mock_requests[0]["json"],
    }
    mock_bridge_v2.api.emit_event("update", event)
    await hass.async_block_till_done()

    # the switch should now be off
    test_entity = hass.states.get(test_entity_id)
    assert test_entity is not None
    assert test_entity.state == "off"


async def test_switch_added(hass: HomeAssistant, mock_bridge_v2: Mock) -> None:
    """Test new switch added to bridge."""
    await mock_bridge_v2.api.load_test_data([FAKE_DEVICE, FAKE_ZIGBEE_CONNECTIVITY])

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    test_entity_id = "switch.hue_mocked_device_motion_sensor_enabled"

    # verify entity does not exist before we start
    assert hass.states.get(test_entity_id) is None

    # Add new fake entity (and attached device and zigbee_connectivity) by emitting
    # events
    mock_bridge_v2.api.emit_event("add", FAKE_BINARY_SENSOR)
    await hass.async_block_till_done()

    # the entity should now be available
    test_entity = hass.states.get(test_entity_id)
    assert test_entity is not None
    assert test_entity.state == "on"

    # test update
    updated_resource = {**FAKE_BINARY_SENSOR, "enabled": False}
    mock_bridge_v2.api.emit_event("update", updated_resource)
    await hass.async_block_till_done()
    test_entity = hass.states.get(test_entity_id)
    assert test_entity is not None
    assert test_entity.state == "off"


@pytest.mark.parametrize(
    "metadata",
    [
        pytest.param(
            {"name": "Hue Accessories", "category": "accessory"}, id="accessory"
        ),
        pytest.param(
            {"name": "Light state after streaming", "category": "entertainment"},
            id="entertainment",
        ),
        pytest.param({"name": "Old bridge script"}, id="no_category"),
    ],
)
async def test_internal_behavior_instance_not_added(
    hass: HomeAssistant,
    mock_bridge_v2: Mock,
    v2_resources_test_data: JsonArrayType,
    metadata: dict,
) -> None:
    """Test internal behavior instances are not exposed as switches.

    The bridge accepts a change to `enabled` on these but keeps running them,
    so a switch for them would silently do nothing. Bridges that do not report
    a category at all are skipped for the same reason.
    """
    internal_script = {**FAKE_BEHAVIOR_SCRIPT, "metadata": metadata}
    await mock_bridge_v2.api.load_test_data(
        [*v2_resources_test_data, internal_script, FAKE_BEHAVIOR_INSTANCE]
    )

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    assert hass.states.get("switch.philips_hue_automation_wall_switch_hallway") is None
    assert hass.states.get("switch.philips_hue_automation_timer_test") is not None
    assert len(hass.states.async_all()) == 4


async def test_internal_behavior_instance_entity_removed(
    hass: HomeAssistant,
    mock_bridge_v2: Mock,
    v2_resources_test_data: JsonArrayType,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test a previously created entity for an internal instance is removed."""
    # Simulate an entity created with a previous version of the integration
    stale_entity = entity_registry.async_get_or_create(
        Platform.SWITCH, DOMAIN, FAKE_BEHAVIOR_INSTANCE["id"]
    )
    await mock_bridge_v2.api.load_test_data(
        [*v2_resources_test_data, FAKE_BEHAVIOR_SCRIPT, FAKE_BEHAVIOR_INSTANCE]
    )

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    assert entity_registry.async_get(stale_entity.entity_id) is None


@pytest.mark.parametrize(
    ("pm_state", "expected_state"), [("started", "on"), ("stopped", "off")]
)
async def test_presence_mimicking_switch_state(
    hass: HomeAssistant,
    mock_bridge_v2: Mock,
    v2_resources_test_data: JsonArrayType,
    pm_state: str,
    expected_state: str,
) -> None:
    """Test the switch follows the run state instead of enabled."""
    instance = {**FAKE_PRESENCE_MIMICKING_INSTANCE, "state": {"pm_state": pm_state}}
    await mock_bridge_v2.api.load_test_data(
        [*v2_resources_test_data, FAKE_PRESENCE_MIMICKING_SCRIPT, instance]
    )

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    test_entity = hass.states.get("switch.philips_hue_automation_mimic_presence")
    assert test_entity is not None
    assert test_entity.state == expected_state


@pytest.mark.parametrize(
    ("service", "expected_trigger"),
    [("turn_on", {"start": {}}), ("turn_off", {"stop": {}})],
)
async def test_presence_mimicking_switch_services(
    hass: HomeAssistant,
    mock_bridge_v2: Mock,
    v2_resources_test_data: JsonArrayType,
    service: str,
    expected_trigger: dict,
) -> None:
    """Test the switch starts and stops instead of touching enabled."""
    await mock_bridge_v2.api.load_test_data(
        [
            *v2_resources_test_data,
            FAKE_PRESENCE_MIMICKING_SCRIPT,
            FAKE_PRESENCE_MIMICKING_INSTANCE,
        ]
    )

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    await hass.services.async_call(
        "switch",
        service,
        {"entity_id": "switch.philips_hue_automation_mimic_presence"},
        blocking=True,
    )

    assert len(mock_bridge_v2.mock_requests) == 1
    assert mock_bridge_v2.mock_requests[0]["method"] == "put"
    assert mock_bridge_v2.mock_requests[0]["json"] == {"trigger": expected_trigger}


async def test_regular_automation_switch_uses_enabled(
    hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
) -> None:
    """Test an automation without a run state still toggles enabled."""
    await mock_bridge_v2.api.load_test_data(v2_resources_test_data)

    await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)

    await hass.services.async_call(
        "switch",
        "turn_off",
        {"entity_id": "switch.philips_hue_automation_timer_test"},
        blocking=True,
    )

    assert len(mock_bridge_v2.mock_requests) == 1
    assert mock_bridge_v2.mock_requests[0]["json"] == {"enabled": False}
