"""Define tests for SimpliSafe setup."""

from unittest.mock import AsyncMock, Mock

from freezegun.api import FrozenDateTimeFactory
import pytest
from simplipy.errors import (
    EndpointUnavailableError,
    InvalidCredentialsError,
    RequestError,
    SimplipyError,
)
from simplipy.websocket import WebsocketEvent

from homeassistant.components.simplisafe import DOMAIN
from homeassistant.components.simplisafe.coordinator import DEFAULT_SCAN_INTERVAL
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry, async_fire_time_changed


async def test_base_station_migration(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    api: Mock,
    config: dict[str, str],
    config_entry: MockConfigEntry,
    patch_simplisafe_api,
) -> None:
    """Test that old integer-based device identifiers are migrated to strings."""
    old_identifiers = {(DOMAIN, 12345)}
    new_identifiers = {(DOMAIN, "12345")}

    device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        identifiers=old_identifiers,
        manufacturer="SimpliSafe",
        name="old",
    )

    assert await async_setup_component(hass, DOMAIN, config)
    await hass.async_block_till_done()

    assert device_registry.async_get_device(identifiers=old_identifiers) is None
    assert device_registry.async_get_device(identifiers=new_identifiers) is not None


async def test_coordinator_update_triggers_reauth_on_invalid_credentials(
    hass: HomeAssistant,
    config_entry: MockConfigEntry,
    patch_simplisafe_api,
    system_v3,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test that InvalidCredentialsError triggers a reauth flow."""
    await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    system_v3.async_update = AsyncMock(side_effect=InvalidCredentialsError("fail"))

    freezer.tick(DEFAULT_SCAN_INTERVAL)
    async_fire_time_changed(hass)
    await hass.async_block_till_done(wait_background_tasks=True)

    flows = hass.config_entries.flow.async_progress()
    assert len(flows) == 1
    flow = flows[0]
    assert flow.get("context", {}).get("source") == SOURCE_REAUTH
    assert flow.get("context", {}).get("entry_id") == config_entry.entry_id


@pytest.mark.parametrize(
    "exc",
    [RequestError, EndpointUnavailableError, SimplipyError],
)
async def test_coordinator_update_failure_keeps_entity_available(
    hass: HomeAssistant,
    config_entry: MockConfigEntry,
    patch_simplisafe_api,
    system_v3,
    freezer: FrozenDateTimeFactory,
    exc: type[SimplipyError],
) -> None:
    """Test single coordinator failure doesn't mark entities unavailable."""
    await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    assert hass.states.get("lock.front_door_lock").state != STATE_UNAVAILABLE

    system_v3.async_update = AsyncMock(side_effect=exc("fail"))

    # Trigger one coordinator failure: error_count goes from 0 to 1, below threshold.
    freezer.tick(DEFAULT_SCAN_INTERVAL)
    async_fire_time_changed(hass)
    await hass.async_block_till_done(wait_background_tasks=True)

    assert hass.states.get("lock.front_door_lock").state != STATE_UNAVAILABLE


async def test_websocket_event_updates_entity_state(
    hass: HomeAssistant,
    config_entry: MockConfigEntry,
    patch_simplisafe_api,
    websocket: Mock,
) -> None:
    """Test that a push update from the websocket changes entity state."""
    await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    # Retrieve the event callback that was registered with the mock websocket.
    assert websocket.add_event_callback.called
    event_callback = websocket.add_event_callback.call_args[0][0]

    assert hass.states.get("lock.front_door_lock").state == "locked"

    # Fire an "unlock" websocket event for the test lock
    # (system_id=12345, serial="987").
    # CID 9700 maps to EVENT_LOCK_UNLOCKED in simplipy event mapping.
    event_callback(
        WebsocketEvent(
            event_cid=9700,
            info="Lock unlocked",
            system_id=12345,
            _raw_timestamp=0,
            _video=None,
            _vid=None,
            sensor_serial="987",
        )
    )
    await hass.async_block_till_done()

    assert hass.states.get("lock.front_door_lock").state == "unlocked"
