"""Tests for the ness_alarm component."""

from types import MappingProxyType
from unittest.mock import AsyncMock, patch

from nessclient import ArmingMode, ArmingState
import pytest

from homeassistant.components import alarm_control_panel
from homeassistant.components.alarm_control_panel import (
    AlarmControlPanelEntityFeature,
    AlarmControlPanelState,
)
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.ness_alarm.const import (
    ATTR_OUTPUT_ID,
    CONF_SHOW_HOME_MODE,
    CONF_ZONE_NUMBER,
    DOMAIN,
    SERVICE_AUX,
    SERVICE_PANIC,
    SUBENTRY_TYPE_ZONE,
)
from homeassistant.config_entries import ConfigEntryState, ConfigSubentry
from homeassistant.const import (
    ATTR_CODE,
    ATTR_ENTITY_ID,
    ATTR_STATE,
    CONF_HOST,
    CONF_PORT,
    CONF_TYPE,
    SERVICE_ALARM_ARM_AWAY,
    SERVICE_ALARM_ARM_HOME,
    SERVICE_ALARM_DISARM,
    SERVICE_ALARM_TRIGGER,
    STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry


async def test_config_entry_setup(hass: HomeAssistant, mock_nessclient) -> None:
    """Test config entry setup."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Services should be registered
    assert hass.services.has_service(DOMAIN, SERVICE_PANIC)
    assert hass.services.has_service(DOMAIN, SERVICE_AUX)

    # Alarm panel should be created
    assert hass.states.get("alarm_control_panel.alarm_panel")

    # Client keepalive and update should be called after startup
    assert mock_nessclient.keepalive.call_count == 1
    # update is called once during setup (connection test) and once after startup
    assert mock_nessclient.update.call_count == 2


async def test_config_entry_unload(hass: HomeAssistant, mock_nessclient) -> None:
    """Test config entry unload."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    assert await hass.config_entries.async_unload(entry.entry_id)
    await hass.async_block_till_done()

    # Client should be closed
    mock_nessclient.close.assert_called_once()


async def test_config_entry_not_ready(hass: HomeAssistant, mock_nessclient) -> None:
    """Test config entry raises ConfigEntryNotReady on connection failure."""
    mock_nessclient.update.side_effect = OSError("Connection refused")

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    assert entry.state is ConfigEntryState.SETUP_RETRY
    mock_nessclient.close.assert_called_once()


async def test_config_entry_with_zones(hass: HomeAssistant, mock_nessclient) -> None:
    """Test config entry setup with zones as subentries."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    # Add zone subentries
    entry.subentries = {
        "zone_1_id": ConfigSubentry(
            subentry_type=SUBENTRY_TYPE_ZONE,
            subentry_id="zone_1_id",
            unique_id="zone_1",
            title="Zone 1",
            data={
                CONF_ZONE_NUMBER: 1,
                CONF_TYPE: BinarySensorDeviceClass.MOTION,
            },
        ),
        "zone_2_id": ConfigSubentry(
            subentry_type=SUBENTRY_TYPE_ZONE,
            subentry_id="zone_2_id",
            unique_id="zone_2",
            title="Zone 2",
            data={
                CONF_ZONE_NUMBER: 2,
                CONF_TYPE: BinarySensorDeviceClass.DOOR,
            },
        ),
    }

    assert await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Binary sensors should be created for each zone
    assert hass.states.get("binary_sensor.zone_1")
    assert hass.states.get("binary_sensor.zone_2")


async def test_config_entry_reload_on_subentry_add(
    hass: HomeAssistant, mock_nessclient
) -> None:
    """Test config entry with subentries."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    # Add a zone subentry
    entry.subentries = {
        "zone_1_id": ConfigSubentry(
            subentry_type=SUBENTRY_TYPE_ZONE,
            subentry_id="zone_1_id",
            unique_id="zone_1",
            title="Zone 1",
            data={
                CONF_ZONE_NUMBER: 1,
                CONF_TYPE: BinarySensorDeviceClass.MOTION,
            },
        ),
    }

    assert await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Zone entity should be created
    assert hass.states.get("binary_sensor.zone_1")


async def test_panic_service_with_config_entry(
    hass: HomeAssistant, mock_nessclient
) -> None:
    """Test calling panic service with config entry."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        DOMAIN, SERVICE_PANIC, blocking=True, service_data={ATTR_CODE: "1234"}
    )
    mock_nessclient.panic.assert_awaited_once_with("1234")


async def test_aux_service_with_config_entry(
    hass: HomeAssistant, mock_nessclient
) -> None:
    """Test calling aux service with config entry."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        DOMAIN, SERVICE_AUX, blocking=True, service_data={ATTR_OUTPUT_ID: 1}
    )
    mock_nessclient.aux.assert_awaited_once_with(1, True)


async def test_aux_service_with_state_false(
    hass: HomeAssistant, mock_nessclient
) -> None:
    """Test calling aux service with state=False."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        DOMAIN,
        SERVICE_AUX,
        blocking=True,
        service_data={ATTR_OUTPUT_ID: 2, ATTR_STATE: False},
    )
    mock_nessclient.aux.assert_awaited_once_with(2, False)


async def test_alarm_panel_disarm(hass: HomeAssistant, mock_nessclient) -> None:
    """Test alarm panel disarm."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        alarm_control_panel.DOMAIN,
        SERVICE_ALARM_DISARM,
        blocking=True,
        service_data={
            ATTR_ENTITY_ID: "alarm_control_panel.alarm_panel",
            ATTR_CODE: "1234",
        },
    )
    mock_nessclient.disarm.assert_called_once_with("1234")


async def test_alarm_panel_arm_away(hass: HomeAssistant, mock_nessclient) -> None:
    """Test alarm panel arm away."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        alarm_control_panel.DOMAIN,
        SERVICE_ALARM_ARM_AWAY,
        blocking=True,
        service_data={
            ATTR_ENTITY_ID: "alarm_control_panel.alarm_panel",
            ATTR_CODE: "1234",
        },
    )
    mock_nessclient.arm_away.assert_called_once_with("1234")


async def test_alarm_panel_arm_home(hass: HomeAssistant, mock_nessclient) -> None:
    """Test alarm panel arm home."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        alarm_control_panel.DOMAIN,
        SERVICE_ALARM_ARM_HOME,
        blocking=True,
        service_data={
            ATTR_ENTITY_ID: "alarm_control_panel.alarm_panel",
            ATTR_CODE: "1234",
        },
    )
    mock_nessclient.arm_home.assert_called_once_with("1234")


async def test_alarm_panel_trigger(hass: HomeAssistant, mock_nessclient) -> None:
    """Test alarm panel trigger."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        alarm_control_panel.DOMAIN,
        SERVICE_ALARM_TRIGGER,
        blocking=True,
        service_data={
            ATTR_ENTITY_ID: "alarm_control_panel.alarm_panel",
            ATTR_CODE: "1234",
        },
    )
    mock_nessclient.panic.assert_called_once_with("1234")


async def test_zone_state_change(hass: HomeAssistant, mock_nessclient) -> None:
    """Test zone state change events."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    # Add zone subentries
    entry.subentries = {
        "zone_1_id": ConfigSubentry(
            subentry_type=SUBENTRY_TYPE_ZONE,
            subentry_id="zone_1_id",
            unique_id="zone_1",
            title="Zone 1",
            data={
                CONF_ZONE_NUMBER: 1,
                CONF_TYPE: BinarySensorDeviceClass.MOTION,
            },
        ),
        "zone_2_id": ConfigSubentry(
            subentry_type=SUBENTRY_TYPE_ZONE,
            subentry_id="zone_2_id",
            unique_id="zone_2",
            title="Zone 2",
            data={
                CONF_ZONE_NUMBER: 2,
                CONF_TYPE: BinarySensorDeviceClass.DOOR,
            },
        ),
    }

    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Get the zone change callback
    on_zone_change = mock_nessclient.on_zone_change.call_args[0][0]

    # Trigger zone 1
    on_zone_change(1, True)
    await hass.async_block_till_done()
    assert hass.states.is_state("binary_sensor.zone_1", "on")

    # Trigger zone 2
    on_zone_change(2, True)
    await hass.async_block_till_done()
    assert hass.states.is_state("binary_sensor.zone_2", "on")

    # Clear zone 1
    on_zone_change(1, False)
    await hass.async_block_till_done()
    assert hass.states.is_state("binary_sensor.zone_1", "off")


async def test_arming_state_changes(hass: HomeAssistant, mock_nessclient) -> None:
    """Test all arming state changes."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Get the state change callback
    on_state_change = mock_nessclient.on_state_change.call_args[0][0]

    states = [
        (ArmingState.UNKNOWN, None, STATE_UNKNOWN),
        (ArmingState.DISARMED, None, AlarmControlPanelState.DISARMED),
        (ArmingState.ARMING, None, AlarmControlPanelState.ARMING),
        (ArmingState.EXIT_DELAY, None, AlarmControlPanelState.ARMING),
        (ArmingState.ARMED, None, AlarmControlPanelState.ARMED_AWAY),
        (
            ArmingState.ARMED,
            ArmingMode.ARMED_AWAY,
            AlarmControlPanelState.ARMED_AWAY,
        ),
        (
            ArmingState.ARMED,
            ArmingMode.ARMED_HOME,
            AlarmControlPanelState.ARMED_HOME,
        ),
        (
            ArmingState.ARMED,
            ArmingMode.ARMED_NIGHT,
            AlarmControlPanelState.ARMED_NIGHT,
        ),
        (
            ArmingState.ARMED,
            ArmingMode.ARMED_VACATION,
            AlarmControlPanelState.ARMED_VACATION,
        ),
        (
            ArmingState.ARMED,
            ArmingMode.ARMED_DAY,
            AlarmControlPanelState.ARMED_AWAY,
        ),
        (
            ArmingState.ARMED,
            ArmingMode.ARMED_HIGHEST,
            AlarmControlPanelState.ARMED_AWAY,
        ),
        (ArmingState.ENTRY_DELAY, None, AlarmControlPanelState.PENDING),
        (ArmingState.TRIGGERED, None, AlarmControlPanelState.TRIGGERED),
    ]

    for arming_state, arming_mode, expected_state in states:
        on_state_change(arming_state, arming_mode)
        await hass.async_block_till_done()
        assert hass.states.is_state("alarm_control_panel.alarm_panel", expected_state)


async def test_arming_state_unknown_mode(hass: HomeAssistant, mock_nessclient) -> None:
    """Test arming state with unknown arming mode (for coverage)."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Get the state change callback
    on_state_change = mock_nessclient.on_state_change.call_args[0][0]

    # Test with unhandled arming state (for coverage of warning log)
    on_state_change(999, None)  # Invalid state
    await hass.async_block_till_done()


async def test_homeassistant_stop_event(hass: HomeAssistant, mock_nessclient) -> None:
    """Test client is closed on homeassistant_stop event."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Fire the homeassistant_stop event
    hass.bus.async_fire("homeassistant_stop")
    await hass.async_block_till_done()

    # Client should be closed
    mock_nessclient.close.assert_called()


async def test_entry_reload_on_update(hass: HomeAssistant, mock_nessclient) -> None:
    """Test config entry reload when update listener is triggered."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    # Add a zone subentry which should trigger the update listener and reload
    zone_subentry = ConfigSubentry(
        subentry_type=SUBENTRY_TYPE_ZONE,
        subentry_id="zone_1_id",
        unique_id="zone_1",
        title="Zone 1",
        data=MappingProxyType(
            {
                CONF_ZONE_NUMBER: 1,
                CONF_TYPE: BinarySensorDeviceClass.MOTION,
            }
        ),
    )
    hass.config_entries.async_add_subentry(entry, zone_subentry)
    await hass.async_block_till_done()

    # Entry should have the new zone subentry
    assert len(entry.subentries) == 1


async def test_alarm_panel_home_mode_disabled(
    hass: HomeAssistant, mock_nessclient
) -> None:
    """Test alarm panel with home mode disabled via options."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
        options={CONF_SHOW_HOME_MODE: False},
    )
    entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get("alarm_control_panel.alarm_panel")
    assert state is not None

    # ARM_HOME should not be in supported features
    supported = state.attributes["supported_features"]
    assert not supported & AlarmControlPanelEntityFeature.ARM_HOME
    assert supported & AlarmControlPanelEntityFeature.ARM_AWAY
    assert supported & AlarmControlPanelEntityFeature.TRIGGER


async def test_alarm_panel_home_mode_enabled_by_default(
    hass: HomeAssistant, mock_nessclient
) -> None:
    """Test alarm panel has home mode enabled by default."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_HOST: "192.168.1.100",
            CONF_PORT: 1992,
        },
    )
    entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get("alarm_control_panel.alarm_panel")
    assert state is not None

    # ARM_HOME should be in supported features by default
    supported = state.attributes["supported_features"]
    assert supported & AlarmControlPanelEntityFeature.ARM_HOME
    assert supported & AlarmControlPanelEntityFeature.ARM_AWAY
    assert supported & AlarmControlPanelEntityFeature.TRIGGER


@pytest.mark.usefixtures("mock_setup_entry")
async def test_yaml_import_triggers_flow(
    hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
    """Test that YAML configuration triggers import flow."""
    with patch(
        "homeassistant.components.ness_alarm.config_flow.Client",
        return_value=AsyncMock(),
    ):
        config = {
            DOMAIN: {
                CONF_HOST: "192.168.1.100",
                CONF_PORT: 1992,
            }
        }
        assert await async_setup_component(hass, DOMAIN, config)
        await hass.async_block_till_done()

        # Check that a config entry was created from the import
        entries = hass.config_entries.async_entries(DOMAIN)
        assert len(entries) == 1
        assert entries[0].data[CONF_HOST] == "192.168.1.100"
        assert entries[0].data[CONF_PORT] == 1992

        # Check that a deprecation repair issue was created
        issue = issue_registry.async_get_issue(
            "homeassistant", f"deprecated_yaml_{DOMAIN}"
        )
        assert issue is not None
        assert issue.severity == "warning"
