"""Tests for the Shelly integration."""

from collections.abc import Mapping, Sequence
from contextlib import contextmanager
from copy import deepcopy
from datetime import timedelta
from typing import Any
from unittest.mock import Mock, patch

from aioshelly.const import MODEL_25
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from syrupy.filters import props

from homeassistant.components.shelly import (
    BLOCK_SLEEPING_PLATFORMS,
    PLATFORMS,
    RPC_SLEEPING_PLATFORMS,
)
from homeassistant.components.shelly.const import (
    CONF_GEN,
    CONF_SLEEP_PERIOD,
    DOMAIN,
    MAX_PUSH_UPDATE_FAILURES,
    REST_SENSORS_UPDATE_INTERVAL,
    RPC_SENSORS_POLLING_INTERVAL,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MODEL, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.device_registry import (
    CONNECTION_NETWORK_MAC,
    DeviceEntry,
    DeviceRegistry,
)

from tests.common import MockConfigEntry, async_fire_time_changed

MOCK_MAC = "123456789ABC"


async def init_integration(
    hass: HomeAssistant,
    gen: int | None,
    model=MODEL_25,
    sleep_period=0,
    options: dict[str, Any] | None = None,
    skip_setup: bool = False,
    data: dict[str, Any] | None = None,
) -> MockConfigEntry:
    """Set up the Shelly integration in Home Assistant."""
    if data is None:
        data = {
            CONF_HOST: "192.168.1.37",
            CONF_SLEEP_PERIOD: sleep_period,
            CONF_MODEL: model,
        }
    if gen is not None:
        data[CONF_GEN] = gen

    entry = MockConfigEntry(
        domain=DOMAIN, data=data, unique_id=MOCK_MAC, options=options, title="Test name"
    )
    entry.add_to_hass(hass)

    if not skip_setup:
        await hass.config_entries.async_setup(entry.entry_id)
        await hass.async_block_till_done(wait_background_tasks=True)

    return entry


async def mock_block_device_push_update_failure(
    hass: HomeAssistant, mock_block_device: Mock
) -> None:
    """Create COAP_REPLY updates indicating push update failure."""
    for _ in range(MAX_PUSH_UPDATE_FAILURES):
        mock_block_device.mock_update_reply()
        await hass.async_block_till_done()
    await hass.async_block_till_done(wait_background_tasks=True)


def mutate_rpc_device_status(
    monkeypatch: pytest.MonkeyPatch,
    mock_rpc_device: Mock,
    top_level_key: str,
    key: str,
    value: Any,
) -> None:
    """Mutate status for rpc device."""
    new_status = deepcopy(mock_rpc_device.status)
    new_status[top_level_key][key] = value
    monkeypatch.setattr(mock_rpc_device, "status", new_status)


def inject_rpc_device_event(
    monkeypatch: pytest.MonkeyPatch,
    mock_rpc_device: Mock,
    event: Mapping[str, list[dict[str, Any]] | float],
) -> None:
    """Inject event for rpc device."""
    monkeypatch.setattr(mock_rpc_device, "event", event)
    mock_rpc_device.mock_event()


async def mock_rest_update(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    seconds=REST_SENSORS_UPDATE_INTERVAL,
) -> None:
    """Move time to create REST sensors update event."""
    freezer.tick(timedelta(seconds=seconds))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()


async def mock_polling_rpc_update(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    seconds: float = RPC_SENSORS_POLLING_INTERVAL,
) -> None:
    """Move time to create polling RPC sensors update event."""
    freezer.tick(timedelta(seconds=seconds))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()


def register_entity(
    hass: HomeAssistant,
    domain: str,
    object_id: str,
    unique_id: str,
    config_entry: ConfigEntry | None = None,
    capabilities: Mapping[str, Any] | None = None,
    device_id: str | None = None,
) -> str:
    """Register enabled entity, return entity_id."""
    entity_registry = er.async_get(hass)
    entity_registry.async_get_or_create(
        domain,
        DOMAIN,
        f"{MOCK_MAC}-{unique_id}",
        suggested_object_id=object_id,
        disabled_by=None,
        config_entry=config_entry,
        capabilities=capabilities,
        device_id=device_id,
    )
    return f"{domain}.{object_id}"


def get_entity(
    hass: HomeAssistant,
    domain: str,
    unique_id: str,
) -> str | None:
    """Get Shelly entity."""
    entity_registry = er.async_get(hass)
    return entity_registry.async_get_entity_id(
        domain, DOMAIN, f"{MOCK_MAC}-{unique_id}"
    )


def register_device(
    device_registry: DeviceRegistry, config_entry: ConfigEntry
) -> DeviceEntry:
    """Register Shelly device."""
    return device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(CONNECTION_NETWORK_MAC, MOCK_MAC)},
    )


def register_sub_device(
    device_registry: DeviceRegistry, config_entry: ConfigEntry, unique_id: str
) -> DeviceEntry:
    """Register Shelly sub-device."""
    return device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        identifiers={(DOMAIN, f"{MOCK_MAC}-{unique_id}")},
        via_device=(DOMAIN, MOCK_MAC),
    )


async def snapshot_device_entities(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    snapshot: SnapshotAssertion,
    config_entry_id: str,
) -> None:
    """Snapshot all device entities."""

    def sort_event_types(data: Any, path: Sequence[tuple[str, Any]]) -> Any:
        """Sort the event_types list for event entity."""
        if path and path[-1][0] == "event_types" and isinstance(data, list):
            return sorted(data)

        return data

    entity_entries = er.async_entries_for_config_entry(entity_registry, config_entry_id)
    assert entity_entries

    for entity_entry in entity_entries:
        assert entity_entry == snapshot(
            name=f"{entity_entry.entity_id}-entry", exclude=props("event_types")
        )
        assert entity_entry.disabled_by is None, "Please enable all entities."
        state = hass.states.get(entity_entry.entity_id)
        assert state, f"State not found for {entity_entry.entity_id}"
        assert state == snapshot(
            name=f"{entity_entry.entity_id}-state", matcher=sort_event_types
        )


async def force_uptime_value(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Force time to a specific point."""
    await hass.config.async_set_time_zone("UTC")
    freezer.move_to("2025-05-26 16:04:00+00:00")


@contextmanager
def patch_platforms(platforms: list[Platform]):
    """Only allow given platforms to be loaded."""
    with (
        patch(
            "homeassistant.components.shelly.PLATFORMS",
            list(set(PLATFORMS) & set(platforms)),
        ),
        patch(
            "homeassistant.components.shelly.BLOCK_SLEEPING_PLATFORMS",
            list(set(BLOCK_SLEEPING_PLATFORMS) & set(platforms)),
        ),
        patch(
            "homeassistant.components.shelly.RPC_SLEEPING_PLATFORMS",
            list(set(RPC_SLEEPING_PLATFORMS) & set(platforms)),
        ),
    ):
        yield
