"""Test Satel Integra alarm panel."""

from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, patch

import pytest
from satel_integra.satel_integra import AlarmState
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.alarm_control_panel import (
    DOMAIN as ALARM_DOMAIN,
    AlarmControlPanelState,
)
from homeassistant.components.satel_integra.const import DOMAIN
from homeassistant.const import (
    ATTR_CODE,
    ATTR_ENTITY_ID,
    SERVICE_ALARM_ARM_AWAY,
    SERVICE_ALARM_ARM_HOME,
    SERVICE_ALARM_DISARM,
    STATE_UNAVAILABLE,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceRegistry
from homeassistant.helpers.entity_registry import EntityRegistry

from . import (
    MOCK_CODE,
    MOCK_ENTRY_ID,
    get_monitor_callbacks,
    setup_integration,
    trigger_connection_status_update,
)

from tests.common import (
    MockConfigEntry,
    async_capture_events,
    async_fire_time_changed,
    snapshot_platform,
)


@pytest.fixture(autouse=True)
async def alarm_control_panel_only() -> AsyncGenerator[None]:
    """Enable only the alarm panel platform."""
    with patch(
        "homeassistant.components.satel_integra.PLATFORMS",
        [Platform.ALARM_CONTROL_PANEL],
    ):
        yield


@pytest.mark.usefixtures("mock_satel")
async def test_alarm_control_panel(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    mock_config_entry_with_subentries: MockConfigEntry,
    entity_registry: EntityRegistry,
    device_registry: DeviceRegistry,
) -> None:
    """Test alarm control panel correctly being set up."""
    await setup_integration(hass, mock_config_entry_with_subentries)

    await snapshot_platform(hass, entity_registry, snapshot, MOCK_ENTRY_ID)

    device_entry = device_registry.async_get_device(
        identifiers={(DOMAIN, "1234567890_alarm_panel_1")}
    )

    assert device_entry == snapshot(name="device")


async def test_alarm_control_panel_initial_state(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
) -> None:
    """Test alarm control panel has a correct initial state after initialization."""
    mock_satel.partition_states = {AlarmState.ARMED_MODE0: [1]}

    await setup_integration(hass, mock_config_entry_with_subentries)

    assert (
        hass.states.get("alarm_control_panel.home").state
        == AlarmControlPanelState.ARMED_AWAY
    )


@pytest.mark.parametrize(
    ("source_state", "resulting_state"),
    [
        (AlarmState.TRIGGERED, AlarmControlPanelState.TRIGGERED),
        (AlarmState.TRIGGERED_FIRE, AlarmControlPanelState.TRIGGERED),
        (AlarmState.ENTRY_TIME, AlarmControlPanelState.PENDING),
        (AlarmState.ARMED_MODE3, AlarmControlPanelState.ARMED_HOME),
        (AlarmState.ARMED_MODE2, AlarmControlPanelState.ARMED_HOME),
        (AlarmState.ARMED_MODE1, AlarmControlPanelState.ARMED_HOME),
        (AlarmState.ARMED_MODE0, AlarmControlPanelState.ARMED_AWAY),
        (AlarmState.EXIT_COUNTDOWN_OVER_10, AlarmControlPanelState.ARMING),
        (AlarmState.EXIT_COUNTDOWN_UNDER_10, AlarmControlPanelState.ARMING),
    ],
)
async def test_alarm_status_callback(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
    source_state: AlarmState,
    resulting_state: AlarmControlPanelState,
) -> None:
    """Test alarm panel state changes after a panel callback."""
    await setup_integration(hass, mock_config_entry_with_subentries)

    assert (
        hass.states.get("alarm_control_panel.home").state
        == AlarmControlPanelState.DISARMED
    )

    alarm_panel_update_method, _, _ = get_monitor_callbacks(mock_satel)

    mock_satel.partition_states = {source_state: [1]}

    alarm_panel_update_method()

    # Trigger coordinator debounce
    async_fire_time_changed(hass)

    assert hass.states.get("alarm_control_panel.home").state == resulting_state


async def test_alarm_status_callback_debounce(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
) -> None:
    """Test that rapid partition state callbacks are debounced."""
    await setup_integration(hass, mock_config_entry_with_subentries)

    assert (
        hass.states.get("alarm_control_panel.home").state
        == AlarmControlPanelState.DISARMED
    )

    alarm_panel_update_method, _, _ = get_monitor_callbacks(mock_satel)

    # Simulate rapid state changes from the alarm panel
    mock_satel.partition_states = {AlarmState.EXIT_COUNTDOWN_OVER_10: [1]}
    alarm_panel_update_method()

    mock_satel.partition_states = {AlarmState.EXIT_COUNTDOWN_UNDER_10: [1]}
    alarm_panel_update_method()

    mock_satel.partition_states = {AlarmState.ARMED_MODE0: [1]}
    alarm_panel_update_method()

    mock_satel.partition_states = {AlarmState.ARMED_MODE1: [1]}
    alarm_panel_update_method()

    # State should still be DISARMED because updates are debounced
    assert (
        hass.states.get("alarm_control_panel.home").state
        == AlarmControlPanelState.DISARMED
    )

    # Trigger coordinator debounce
    async_fire_time_changed(hass)

    assert (
        hass.states.get("alarm_control_panel.home").state
        == AlarmControlPanelState.ARMED_HOME
    )


async def test_alarm_control_panel_arming(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
) -> None:
    """Test alarm control panel correctly sending arming commands to the panel."""
    await setup_integration(hass, mock_config_entry_with_subentries)

    # Test Arm home
    await hass.services.async_call(
        ALARM_DOMAIN,
        SERVICE_ALARM_ARM_HOME,
        {ATTR_ENTITY_ID: "alarm_control_panel.home", ATTR_CODE: MOCK_CODE},
        blocking=True,
    )

    mock_satel.arm.assert_awaited_once_with(MOCK_CODE, [1], 1)

    mock_satel.arm.reset_mock()

    # Test Arm away
    await hass.services.async_call(
        ALARM_DOMAIN,
        SERVICE_ALARM_ARM_AWAY,
        {ATTR_ENTITY_ID: "alarm_control_panel.home", ATTR_CODE: MOCK_CODE},
        blocking=True,
    )

    mock_satel.arm.assert_awaited_once_with(MOCK_CODE, [1])


async def test_alarm_control_panel_disarming(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
) -> None:
    """Test alarm panel correctly disarms and dismisses alarm."""
    mock_satel.partition_states = {AlarmState.TRIGGERED: [1]}

    await setup_integration(hass, mock_config_entry_with_subentries)

    await hass.services.async_call(
        ALARM_DOMAIN,
        SERVICE_ALARM_DISARM,
        {ATTR_ENTITY_ID: "alarm_control_panel.home", ATTR_CODE: MOCK_CODE},
        blocking=True,
    )

    mock_satel.disarm.assert_awaited_once_with(MOCK_CODE, [1])

    mock_satel.clear_alarm.assert_awaited_once_with(MOCK_CODE, [1])


async def test_alarm_panel_last_reported(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
) -> None:
    """Test alarm panels update last_reported if same state is reported."""
    events = async_capture_events(hass, "state_changed")
    await setup_integration(hass, mock_config_entry_with_subentries)

    first_reported = hass.states.get("alarm_control_panel.home").last_reported
    assert first_reported is not None
    # Initial state change event
    assert len(events) == 1

    # Run callbacks with same payload
    alarm_panel_update_method, _, _ = get_monitor_callbacks(mock_satel)
    alarm_panel_update_method()

    # Trigger coordinator debounce
    async_fire_time_changed(hass)

    assert first_reported != hass.states.get("alarm_control_panel.home").last_reported
    assert len(events) == 1  # last_reported shall not fire state_changed


async def test_availability(
    hass: HomeAssistant,
    mock_satel: AsyncMock,
    mock_config_entry_with_subentries: MockConfigEntry,
) -> None:
    """Test availability."""
    entity_id = "alarm_control_panel.home"

    await setup_integration(hass, mock_config_entry_with_subentries)

    assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED

    await trigger_connection_status_update(hass, mock_satel, False)

    assert hass.states.get(entity_id).state == STATE_UNAVAILABLE

    await trigger_connection_status_update(hass, mock_satel, True)

    assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
