"""Tests for the SmartThings component init module."""

from unittest.mock import AsyncMock, MagicMock, patch

from pysmartthings import (
    Attribute,
    Capability,
    DeviceResponse,
    DeviceStatus,
    Lifecycle,
    SmartThingsConnectionError,
    SmartThingsSinkError,
    Subscription,
)
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, HVACMode
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.smartthings import EVENT_BUTTON, OLD_DATA
from homeassistant.components.smartthings.const import (
    CONF_INSTALLED_APP_ID,
    CONF_LOCATION_ID,
    CONF_SUBSCRIPTION_ID,
    DOMAIN,
    SCOPES,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import (
    OAuth2TokenRequestReauthError,
    OAuth2TokenRequestTransientError,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.config_entry_oauth2_flow import (
    ImplementationUnavailableError,
)

from . import (
    DEVICE_FIXTURES,
    get_device_response,
    get_fixture_name,
    setup_integration,
    trigger_update,
)

from tests.common import MockConfigEntry, async_load_fixture


async def test_fixtures() -> None:
    """Test all fixtures."""
    device_ids = set()
    device_labels = set()
    for fixture_name in DEVICE_FIXTURES:
        for device_details in get_device_response(fixture_name).items:
            assert device_details.device_id not in device_ids, (
                f"Duplicate device ID {device_details.device_id}"
                f" found in fixture {fixture_name}"
            )
            device_ids.add(device_details.device_id)
            assert (label := device_details.label.lower()) not in device_labels, (
                f"Duplicate device label {device_details.label}"
                f" found in fixture {fixture_name}"
            )
            device_labels.add(label)


async def test_devices(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test all entities."""
    await setup_integration(hass, mock_config_entry)

    for specs in devices.get_devices.return_value:
        device_id = specs.device_id

        device = device_registry.async_get_device({(DOMAIN, device_id)})

        assert device is not None
        assert device == snapshot(name=get_fixture_name(device_id))


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_device_not_resetting_area(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test device not resetting area."""
    await setup_integration(hass, mock_config_entry)

    device_id = devices.get_devices.return_value[0].device_id

    device = device_registry.async_get_device({(DOMAIN, device_id)})

    assert device.area_id == "theater"

    device_registry.async_update_device(device_id=device.id, area_id=None)
    await hass.async_block_till_done()

    device = device_registry.async_get_device({(DOMAIN, device_id)})

    assert device.area_id is None

    await hass.config_entries.async_reload(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    device = device_registry.async_get_device({(DOMAIN, device_id)})
    assert device.area_id is None


@pytest.mark.parametrize("device_fixture", ["button"])
async def test_button_event(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test button event."""
    await setup_integration(hass, mock_config_entry)
    events = []

    def capture_event(event: Event) -> None:
        events.append(event)

    hass.bus.async_listen_once(EVENT_BUTTON, capture_event)

    await trigger_update(
        hass,
        devices,
        "c4bdd19f-85d1-4d58-8f9c-e75ac3cf113b",
        Capability.BUTTON,
        Attribute.BUTTON,
        "pushed",
    )

    assert len(events) == 1
    assert events[0] == snapshot


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_create_subscription(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test creating a subscription."""
    assert CONF_SUBSCRIPTION_ID not in mock_config_entry.data

    await setup_integration(hass, mock_config_entry)

    devices.create_subscription.assert_called_once()

    assert (
        mock_config_entry.data[CONF_SUBSCRIPTION_ID]
        == "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )

    devices.subscribe.assert_called_once_with(
        "397678e5-9995-4a39-9d9f-ae6ba310236c",
        "5aaaa925-2be1-4e40-b257-e4ef59083324",
        Subscription.from_json(
            await async_load_fixture(hass, "subscription.json", DOMAIN)
        ),
    )


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_create_subscription_sink_error(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test handling an error when creating a subscription."""
    assert CONF_SUBSCRIPTION_ID not in mock_config_entry.data

    devices.create_subscription.side_effect = SmartThingsSinkError("Sink error")

    await setup_integration(hass, mock_config_entry)

    devices.subscribe.assert_not_called()

    assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
    assert CONF_SUBSCRIPTION_ID not in mock_config_entry.data


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_update_subscription_identifier(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test updating the subscription identifier."""
    await setup_integration(hass, mock_config_entry)

    assert (
        mock_config_entry.data[CONF_SUBSCRIPTION_ID]
        == "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )

    devices.new_subscription_id_callback("abc")

    await hass.async_block_till_done()

    assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] == "abc"


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_stale_subscription_id(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test updating the subscription identifier."""
    mock_config_entry.add_to_hass(hass)

    hass.config_entries.async_update_entry(
        mock_config_entry,
        data={**mock_config_entry.data, CONF_SUBSCRIPTION_ID: "test"},
    )

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

    assert (
        mock_config_entry.data[CONF_SUBSCRIPTION_ID]
        == "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )
    devices.delete_subscription.assert_called_once_with("test")


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_remove_subscription_identifier(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test removing the subscription identifier."""
    await setup_integration(hass, mock_config_entry)

    assert (
        mock_config_entry.data[CONF_SUBSCRIPTION_ID]
        == "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )

    devices.new_subscription_id_callback(None)

    await hass.async_block_till_done()

    assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] is None


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_max_connections_handling(
    hass: HomeAssistant, devices: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
    """Test handling reaching max connections."""
    await setup_integration(hass, mock_config_entry)

    assert (
        mock_config_entry.data[CONF_SUBSCRIPTION_ID]
        == "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )

    devices.create_subscription.side_effect = SmartThingsSinkError("Sink error")

    devices.max_connections_reached_callback()

    await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_unloading(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test unloading the integration."""
    await setup_integration(hass, mock_config_entry)

    await hass.config_entries.async_unload(mock_config_entry.entry_id)
    devices.delete_subscription.assert_called_once_with(
        "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )
    # Deleting the subscription automatically deletes the subscription ID
    devices.new_subscription_id_callback(None)

    assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
    assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] is None


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_shutdown(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test shutting down Home Assistant."""
    await setup_integration(hass, mock_config_entry)

    hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
    devices.delete_subscription.assert_called_once_with(
        "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
    )
    # Deleting the subscription automatically deletes the subscription ID
    devices.new_subscription_id_callback(None)

    assert mock_config_entry.state is ConfigEntryState.LOADED
    assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] is None


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_removing_stale_devices(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test removing stale devices."""
    mock_config_entry.add_to_hass(hass)
    device_registry.async_get_or_create(
        config_entry_id=mock_config_entry.entry_id,
        identifiers={(DOMAIN, "aaa-bbb-ccc")},
    )

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

    assert not device_registry.async_get_device({(DOMAIN, "aaa-bbb-ccc")})


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_refreshing_expired_token(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test removing stale devices."""
    with patch(
        "homeassistant.components.smartthings.OAuth2Session.async_ensure_token_valid",
        side_effect=OAuth2TokenRequestReauthError(
            request_info=MagicMock(),
            domain=DOMAIN,
        ),
    ):
        await setup_integration(hass, mock_config_entry)

    assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
    assert len(hass.config_entries.flow.async_progress()) == 1


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_error_refreshing_token(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test retrying setup after a transient token refresh error."""
    with patch(
        "homeassistant.components.smartthings.OAuth2Session.async_ensure_token_valid",
        side_effect=OAuth2TokenRequestTransientError(
            request_info=MagicMock(),
            domain=DOMAIN,
        ),
    ):
        await setup_integration(hass, mock_config_entry)

    assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_hub_via_device(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    mock_config_entry: MockConfigEntry,
    device_registry: dr.DeviceRegistry,
    mock_smartthings: AsyncMock,
) -> None:
    """Test hub with child devices."""
    mock_smartthings.get_devices.return_value = DeviceResponse.from_json(
        await async_load_fixture(hass, "devices/hub.json", DOMAIN)
    ).items
    mock_smartthings.get_device_status.side_effect = [
        DeviceStatus.from_json(
            await async_load_fixture(hass, f"device_status/{fixture}.json", DOMAIN)
        ).components
        for fixture in ("hub", "multipurpose_sensor")
    ]
    await setup_integration(hass, mock_config_entry)

    hub_device = device_registry.async_get_device(
        {(DOMAIN, "074fa784-8be8-4c70-8e22-6f5ed6f81b7e")}
    )
    assert hub_device == snapshot
    assert (
        device_registry.async_get_device(
            {(DOMAIN, "374ba6fa-5a08-4ea2-969c-1fa43d86e21f")}
        ).via_device_id
        == hub_device.id
    )


@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
async def test_deleted_device_runtime(
    hass: HomeAssistant,
    devices: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test devices that are deleted in runtime."""
    await setup_integration(hass, mock_config_entry)

    assert hass.states.get("climate.ac_office_granit").state == HVACMode.OFF

    for call in devices.add_device_lifecycle_event_listener.call_args_list:
        if call[0][0] == Lifecycle.DELETE:
            call[0][1]("96a5ef74-5832-a84b-f1f7-ca799957065d")
    await hass.async_block_till_done()

    assert hass.states.get("climate.ac_office_granit") is None


@pytest.mark.parametrize(
    (
        "device_fixture",
        "domain",
        "old_unique_id",
        "suggested_object_id",
        "new_unique_id",
    ),
    [
        (
            "multipurpose_sensor",
            BINARY_SENSOR_DOMAIN,
            "7d246592-93db-4d72-a10d-5a51793ece8c.contact",
            "deck_door",
            "7d246592-93db-4d72-a10d-5a51793ece8c_main_contactSensor_contact_contact",
        ),
        (
            "multipurpose_sensor",
            SENSOR_DOMAIN,
            "7d246592-93db-4d72-a10d-5a51793ece8c Y Coordinate",
            "deck_door_y_coordinate",
            "7d246592-93db-4d72-a10d-5a51793ece8c_main_threeAxis_threeAxis_y_coordinate",
        ),
        (
            "da_ac_rac_000001",
            SENSOR_DOMAIN,
            "7d246592-93db-4d72-a10d-ca799957065d.energy_meter",
            "ac_office_granit_energy",
            "7d246592-93db-4d72-a10d-ca799957065d_main_powerConsumptionReport_powerConsumption_energy_meter",
        ),
        (
            "da_ac_rac_000001",
            CLIMATE_DOMAIN,
            "7d246592-93db-4d72-a10d-ca799957065d",
            "ac_office_granit",
            "7d246592-93db-4d72-a10d-ca799957065d_main",
        ),
        (
            "c2c_shade",
            COVER_DOMAIN,
            "571af102-15db-4030-b76b-245a691f74a5",
            "curtain_1a",
            "571af102-15db-4030-b76b-245a691f74a5_main",
        ),
        (
            "generic_fan_3_speed",
            FAN_DOMAIN,
            "6d95a8b7-4ee3-429a-a13a-00ec9354170c",
            "bedroom_fan",
            "6d95a8b7-4ee3-429a-a13a-00ec9354170c_main",
        ),
        (
            "hue_rgbw_color_bulb",
            LIGHT_DOMAIN,
            "cb958955-b015-498c-9e62-fc0c51abd054",
            "standing_light",
            "cb958955-b015-498c-9e62-fc0c51abd054_main",
        ),
        (
            "yale_push_button_deadbolt_lock",
            LOCK_DOMAIN,
            "a9f587c5-5d8b-4273-8907-e7f609af5158",
            "basement_door_lock",
            "a9f587c5-5d8b-4273-8907-e7f609af5158_main",
        ),
        (
            "smart_plug",
            SWITCH_DOMAIN,
            "550a1c72-65a0-4d55-b97b-75168e055398",
            "arlo_beta_basestation",
            "550a1c72-65a0-4d55-b97b-75168e055398_main_switch_switch_switch",
        ),
    ],
)
async def test_entity_unique_id_migration(
    hass: HomeAssistant,
    devices: AsyncMock,
    expires_at: int,
    entity_registry: er.EntityRegistry,
    domain: str,
    old_unique_id: str,
    suggested_object_id: str,
    new_unique_id: str,
) -> None:
    """Test entity unique ID migration."""
    mock_config_entry = MockConfigEntry(
        domain=DOMAIN,
        title="My home",
        unique_id="397678e5-9995-4a39-9d9f-ae6ba310236c",
        data={
            "auth_implementation": DOMAIN,
            "token": {
                "access_token": "mock-access-token",
                "refresh_token": "mock-refresh-token",
                "expires_at": expires_at,
                "scope": " ".join(SCOPES),
                "access_tier": 0,
                "installed_app_id": "5aaaa925-2be1-4e40-b257-e4ef59083324",
            },
            CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
            CONF_INSTALLED_APP_ID: "123",
        },
        version=3,
        minor_version=1,
    )
    mock_config_entry.add_to_hass(hass)
    entry = entity_registry.async_get_or_create(
        domain,
        DOMAIN,
        old_unique_id,
        config_entry=mock_config_entry,
        suggested_object_id=suggested_object_id,
    )

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

    entry = entity_registry.async_get(entry.entity_id)

    assert entry.unique_id == new_unique_id


@pytest.mark.parametrize(
    (
        "device_fixture",
        "domain",
        "other_unique_id",
        "old_unique_id",
        "suggested_object_id",
        "new_unique_id",
    ),
    [
        (
            "da_ks_microwave_0101x",
            SENSOR_DOMAIN,
            "2bad3237-4886-e699-1b90-4a51a3d55c8a.ovenJobState",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a.machineState",
            "microwave_machine_state",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState_machineState_machineState",
        ),
        (
            "da_ks_microwave_0101x",
            SENSOR_DOMAIN,
            "2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState_ovenJobState_ovenJobState",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a.machineState",
            "microwave_machine_state",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState_machineState_machineState",
        ),
        (
            "da_ks_microwave_0101x",
            SENSOR_DOMAIN,
            "2bad3237-4886-e699-1b90-4a51a3d55c8a.ovenJobState",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a.completionTime",
            "microwave_completion_time",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState_completionTime_completionTime",
        ),
        (
            "da_ks_microwave_0101x",
            SENSOR_DOMAIN,
            "2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState_ovenJobState_ovenJobState",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a.completionTime",
            "microwave_completion_time",
            "2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState_completionTime_completionTime",
        ),
        (
            "da_wm_dw_000001",
            SENSOR_DOMAIN,
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676.dishwasherJobState",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676.machineState",
            "dishwasher_machine_state",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_machineState_machineState",
        ),
        (
            "da_wm_dw_000001",
            SENSOR_DOMAIN,
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_dishwasherJobState_dishwasherJobState",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676.machineState",
            "dishwasher_machine_state",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_machineState_machineState",
        ),
        (
            "da_wm_dw_000001",
            SENSOR_DOMAIN,
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676.dishwasherJobState",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676.completionTime",
            "dishwasher_completion_time",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_completionTime_completionTime",
        ),
        (
            "da_wm_dw_000001",
            SENSOR_DOMAIN,
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_dishwasherJobState_dishwasherJobState",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676.completionTime",
            "dishwasher_completion_time",
            "f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_completionTime_completionTime",
        ),
        (
            "da_wm_wd_000001",
            SENSOR_DOMAIN,
            "02f7256e-8353-5bdd-547f-bd5b1647e01b.dryerJobState",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b.machineState",
            "dryer_machine_state",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b_main_dryerOperatingState_machineState_machineState",
        ),
        (
            "da_wm_wd_000001",
            SENSOR_DOMAIN,
            "02f7256e-8353-5bdd-547f-bd5b1647e01b_main_dryerOperatingState_dryerJobState_dryerJobState",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b.machineState",
            "dryer_machine_state",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b_main_dryerOperatingState_machineState_machineState",
        ),
        (
            "da_wm_wd_000001",
            SENSOR_DOMAIN,
            "02f7256e-8353-5bdd-547f-bd5b1647e01b.dryerJobState",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b.completionTime",
            "dryer_completion_time",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b_main_dryerOperatingState_completionTime_completionTime",
        ),
        (
            "da_wm_wd_000001",
            SENSOR_DOMAIN,
            "02f7256e-8353-5bdd-547f-bd5b1647e01b_main_dryerOperatingState_dryerJobState_dryerJobState",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b.completionTime",
            "dryer_completion_time",
            "02f7256e-8353-5bdd-547f-bd5b1647e01b_main_dryerOperatingState_completionTime_completionTime",
        ),
        (
            "da_wm_wm_000001",
            SENSOR_DOMAIN,
            "f984b91d-f250-9d42-3436-33f09a422a47.washerJobState",
            "f984b91d-f250-9d42-3436-33f09a422a47.machineState",
            "washer_machine_state",
            "f984b91d-f250-9d42-3436-33f09a422a47_main_washerOperatingState_machineState_machineState",
        ),
        (
            "da_wm_wm_000001",
            SENSOR_DOMAIN,
            "f984b91d-f250-9d42-3436-33f09a422a47_main_washerOperatingState_washerJobState_washerJobState",
            "f984b91d-f250-9d42-3436-33f09a422a47.machineState",
            "washer_machine_state",
            "f984b91d-f250-9d42-3436-33f09a422a47_main_washerOperatingState_machineState_machineState",
        ),
        (
            "da_wm_wm_000001",
            SENSOR_DOMAIN,
            "f984b91d-f250-9d42-3436-33f09a422a47.washerJobState",
            "f984b91d-f250-9d42-3436-33f09a422a47.completionTime",
            "washer_completion_time",
            "f984b91d-f250-9d42-3436-33f09a422a47_main_washerOperatingState_completionTime_completionTime",
        ),
        (
            "da_wm_wm_000001",
            SENSOR_DOMAIN,
            "f984b91d-f250-9d42-3436-33f09a422a47_main_washerOperatingState_washerJobState_washerJobState",
            "f984b91d-f250-9d42-3436-33f09a422a47.completionTime",
            "washer_completion_time",
            "f984b91d-f250-9d42-3436-33f09a422a47_main_washerOperatingState_completionTime_completionTime",
        ),
    ],
)
async def test_entity_unique_id_migration_machine_state(
    hass: HomeAssistant,
    devices: AsyncMock,
    expires_at: int,
    entity_registry: er.EntityRegistry,
    domain: str,
    other_unique_id: str,
    old_unique_id: str,
    suggested_object_id: str,
    new_unique_id: str,
) -> None:
    """Test entity unique ID migration."""
    mock_config_entry = MockConfigEntry(
        domain=DOMAIN,
        title="My home",
        unique_id="397678e5-9995-4a39-9d9f-ae6ba310236c",
        data={
            "auth_implementation": DOMAIN,
            "token": {
                "access_token": "mock-access-token",
                "refresh_token": "mock-refresh-token",
                "expires_at": expires_at,
                "scope": " ".join(SCOPES),
                "access_tier": 0,
                "installed_app_id": "5aaaa925-2be1-4e40-b257-e4ef59083324",
            },
            CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
            CONF_INSTALLED_APP_ID: "123",
        },
        version=3,
        minor_version=1,
    )
    mock_config_entry.add_to_hass(hass)
    entity_registry.async_get_or_create(
        domain,
        DOMAIN,
        other_unique_id,
        config_entry=mock_config_entry,
        suggested_object_id="job_state",
    )
    entry = entity_registry.async_get_or_create(
        domain,
        DOMAIN,
        old_unique_id,
        config_entry=mock_config_entry,
        suggested_object_id=suggested_object_id,
    )

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

    entry = entity_registry.async_get(entry.entity_id)

    assert entry.unique_id == new_unique_id


async def test_oauth_implementation_not_available(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test that unavailable OAuth implementation raises ConfigEntryNotReady."""
    mock_config_entry.add_to_hass(hass)

    with patch(
        "homeassistant.components.smartthings.async_get_config_entry_implementation",
        side_effect=ImplementationUnavailableError,
    ):
        await hass.config_entries.async_setup(mock_config_entry.entry_id)
        await hass.async_block_till_done()

    assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY


@pytest.mark.usefixtures("mock_setup_entry")
async def test_3_3_migration(
    hass: HomeAssistant,
    mock_migrated_config_entry: MockConfigEntry,
    mock_smartthings: AsyncMock,
) -> None:
    """Test migration from minor version 2 to 3."""
    mock_migrated_config_entry.add_to_hass(hass)

    assert OLD_DATA in mock_migrated_config_entry.data

    await hass.config_entries.async_setup(mock_migrated_config_entry.entry_id)
    await hass.async_block_till_done()
    assert mock_migrated_config_entry.minor_version == 3

    assert OLD_DATA not in mock_migrated_config_entry.data
    mock_smartthings.get_installed_app.assert_called_once_with(
        "mock-access-token",
        "123aa123-2be1-4e40-b257-e4ef59083324",
    )
    mock_smartthings.delete_installed_app.assert_called_once_with(
        "mock-access-token",
        "123aa123-2be1-4e40-b257-e4ef59083324",
    )
    mock_smartthings.delete_smart_app.assert_called_once_with(
        "mock-access-token",
        "c6cde2b0-203e-44cf-a510-3b3ed4706996",
    )


@pytest.mark.usefixtures("mock_setup_entry")
async def test_3_3_migration_fail(
    hass: HomeAssistant,
    mock_migrated_config_entry: MockConfigEntry,
    mock_smartthings: AsyncMock,
) -> None:
    """Test that unavailable OAuth implementation raises ConfigEntryNotReady."""
    mock_migrated_config_entry.add_to_hass(hass)

    mock_smartthings.get_installed_app.side_effect = SmartThingsConnectionError("Boom")

    assert OLD_DATA in mock_migrated_config_entry.data

    await hass.config_entries.async_setup(mock_migrated_config_entry.entry_id)
    await hass.async_block_till_done()
    assert mock_migrated_config_entry.minor_version == 3

    assert OLD_DATA not in mock_migrated_config_entry.data
    mock_smartthings.get_installed_app.assert_called_once_with(
        "mock-access-token",
        "123aa123-2be1-4e40-b257-e4ef59083324",
    )
    mock_smartthings.delete_installed_app.assert_not_called()
    mock_smartthings.delete_smart_app.assert_not_called()


@pytest.mark.parametrize("old_data", [({})])
@pytest.mark.usefixtures("mock_setup_entry")
async def test_3_3_migration_no_old_data(
    hass: HomeAssistant,
    mock_migrated_config_entry: MockConfigEntry,
    mock_smartthings: AsyncMock,
) -> None:
    """Test migration from minor version 2 to 3 when no old data is present."""
    mock_migrated_config_entry.add_to_hass(hass)

    assert OLD_DATA not in mock_migrated_config_entry.data

    await hass.config_entries.async_setup(mock_migrated_config_entry.entry_id)
    await hass.async_block_till_done()
    assert mock_migrated_config_entry.minor_version == 3

    assert OLD_DATA not in mock_migrated_config_entry.data
    mock_smartthings.get_installed_app.assert_not_called()
    mock_smartthings.delete_installed_app.assert_not_called()
    mock_smartthings.delete_smart_app.assert_not_called()
