"""Test for Nest sensors platform for the Smart Device Management API.

These tests fake out the subscriber/devicemanager, and are not using a real
pubsub subscriber.
"""

from typing import Any

from google_nest_sdm.event import EventMessage
import pytest

from homeassistant.components.sensor import (
    ATTR_STATE_CLASS,
    SensorDeviceClass,
    SensorStateClass,
)
from homeassistant.const import (
    ATTR_DEVICE_CLASS,
    ATTR_FRIENDLY_NAME,
    ATTR_UNIT_OF_MEASUREMENT,
    PERCENTAGE,
    STATE_UNAVAILABLE,
    UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er

from .common import DEVICE_ID, CreateDevice, FakeSubscriber, PlatformSetup


@pytest.fixture
def platforms() -> list[str]:
    """Fixture to setup the platforms to test."""
    return ["sensor"]


@pytest.fixture
def device_traits() -> dict[str, Any]:
    """Fixture that sets default traits used for devices."""
    return {"sdm.devices.traits.Info": {"customName": "My Sensor"}}


async def test_thermostat_device(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test a thermostat with temperature and humidity sensors."""
    create_device.create(
        {
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.1,
            },
            "sdm.devices.traits.Humidity": {
                "ambientHumidityPercent": 35.0,
            },
        }
    )
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is not None
    assert temperature.state == "25.1"
    assert (
        temperature.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
        == UnitOfTemperature.CELSIUS
    )
    assert (
        temperature.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
    )
    assert temperature.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
    assert temperature.attributes.get(ATTR_FRIENDLY_NAME) == "My Sensor Temperature"

    humidity = hass.states.get("sensor.my_sensor_humidity")
    assert humidity is not None
    assert humidity.state == "35"
    assert humidity.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
    assert humidity.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.HUMIDITY
    assert humidity.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
    assert humidity.attributes.get(ATTR_FRIENDLY_NAME) == "My Sensor Humidity"

    registry = er.async_get(hass)
    entry = registry.async_get("sensor.my_sensor_temperature")
    assert entry.unique_id == f"{DEVICE_ID}-temperature"
    assert entry.domain == "sensor"

    entry = registry.async_get("sensor.my_sensor_humidity")
    assert entry.unique_id == f"{DEVICE_ID}-humidity"
    assert entry.domain == "sensor"

    device_registry = dr.async_get(hass)
    device = device_registry.async_get(entry.device_id)
    assert device.name == "My Sensor"
    assert device.model == "Thermostat"
    assert device.identifiers == {("nest", DEVICE_ID)}


async def test_thermostat_device_available(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test a thermostat with temperature and humidity sensors that is Online."""
    create_device.create(
        {
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.1,
            },
            "sdm.devices.traits.Humidity": {
                "ambientHumidityPercent": 35.0,
            },
            "sdm.devices.traits.Connectivity": {"status": "ONLINE"},
        }
    )
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is not None
    assert temperature.state == "25.1"

    humidity = hass.states.get("sensor.my_sensor_humidity")
    assert humidity is not None
    assert humidity.state == "35"


async def test_thermostat_device_unavailable(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test a thermostat with temperature and humidity sensors that is Offline."""
    create_device.create(
        {
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.1,
            },
            "sdm.devices.traits.Humidity": {
                "ambientHumidityPercent": 35.0,
            },
            "sdm.devices.traits.Connectivity": {"status": "OFFLINE"},
        }
    )
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is not None
    assert temperature.state == STATE_UNAVAILABLE

    humidity = hass.states.get("sensor.my_sensor_humidity")
    assert humidity is not None
    assert humidity.state == STATE_UNAVAILABLE


async def test_no_devices(hass: HomeAssistant, setup_platform: PlatformSetup) -> None:
    """Test no devices returned by the api."""
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is None

    humidity = hass.states.get("sensor.my_sensor_humidity")
    assert humidity is None


async def test_device_no_sensor_traits(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test a device with applicable sensor traits."""
    create_device.create({})
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is None

    humidity = hass.states.get("sensor.my_sensor_humidity")
    assert humidity is None


@pytest.mark.parametrize("device_traits", [{}])  # Disable default name
async def test_device_name_from_structure(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test a device without a custom name, inferring name from structure."""
    create_device.create(
        raw_traits={
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.2,
            },
        },
        raw_data={
            "parentRelations": [
                {"parent": "some-structure-id", "displayName": "Some Room"}
            ],
        },
    )
    await setup_platform()

    temperature = hass.states.get("sensor.some_room_temperature")
    assert temperature is not None
    assert temperature.state == "25.2"


async def test_event_updates_sensor(
    hass: HomeAssistant,
    subscriber: FakeSubscriber,
    create_device: CreateDevice,
    setup_platform: PlatformSetup,
) -> None:
    """Test a pubsub message received by subscriber to update temperature."""
    create_device.create(
        {
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.1,
            },
        }
    )
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is not None
    assert temperature.state == "25.1"

    # Simulate a pubsub message received by the subscriber with a trait update
    event = EventMessage(
        {
            "eventId": "some-event-id",
            "timestamp": "2019-01-01T00:00:01Z",
            "resourceUpdate": {
                "name": DEVICE_ID,
                "traits": {
                    "sdm.devices.traits.Temperature": {
                        "ambientTemperatureCelsius": 26.2,
                    },
                },
            },
        },
        auth=None,
    )
    await subscriber.async_receive_event(event)
    await hass.async_block_till_done()  # Process dispatch/update signal

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is not None
    assert temperature.state == "26.2"


@pytest.mark.parametrize("device_type", ["some-unknown-type"])
async def test_device_with_unknown_type(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test a device without a custom name, inferring name from structure."""
    create_device.create(
        {
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.1,
            },
        }
    )
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature is not None
    assert temperature.state == "25.1"
    assert temperature.attributes.get(ATTR_FRIENDLY_NAME) == "My Sensor Temperature"

    registry = er.async_get(hass)
    entry = registry.async_get("sensor.my_sensor_temperature")
    assert entry.unique_id == f"{DEVICE_ID}-temperature"
    assert entry.domain == "sensor"

    device_registry = dr.async_get(hass)
    device = device_registry.async_get(entry.device_id)
    assert device.name == "My Sensor"
    assert device.model is None
    assert device.identifiers == {("nest", DEVICE_ID)}


async def test_temperature_rounding(
    hass: HomeAssistant, create_device: CreateDevice, setup_platform: PlatformSetup
) -> None:
    """Test the rounding of overly precise temperatures."""
    create_device.create(
        {
            "sdm.devices.traits.Temperature": {
                "ambientTemperatureCelsius": 25.15678,
            },
        }
    )
    await setup_platform()

    temperature = hass.states.get("sensor.my_sensor_temperature")
    assert temperature.state == "25.2"
