"""Tests for ScreenLogic integration init."""
from dataclasses import dataclass
from unittest.mock import DEFAULT, patch

import pytest
from screenlogicpy import ScreenLogicGateway

from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
from homeassistant.components.screenlogic import DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import slugify

from . import (
    DATA_MIN_MIGRATION,
    DATA_MISSING_VALUES_CHEM_CHLOR,
    GATEWAY_DISCOVERY_IMPORT_PATH,
    MOCK_ADAPTER_MAC,
    MOCK_ADAPTER_NAME,
    stub_async_connect,
)

from tests.common import MockConfigEntry


@dataclass
class EntityMigrationData:
    """Class to organize minimum entity data."""

    old_name: str
    old_key: str
    new_name: str
    new_key: str
    domain: str


TEST_MIGRATING_ENTITIES = [
    EntityMigrationData(
        "Chemistry Alarm",
        "chem_alarm",
        "Active Alert",
        "active_alert",
        BINARY_SENSOR_DOMAIN,
    ),
    EntityMigrationData(
        "Pool Low Pump Current Watts",
        "currentWatts_0",
        "Pool Low Pump Watts Now",
        "pump_0_watts_now",
        SENSOR_DOMAIN,
    ),
    EntityMigrationData(
        "SCG Status",
        "scg_status",
        "Chlorinator",
        "scg_state",
        BINARY_SENSOR_DOMAIN,
    ),
    EntityMigrationData(
        "Non-Migrating Sensor",
        "nonmigrating",
        "Non-Migrating Sensor",
        "nonmigrating",
        SENSOR_DOMAIN,
    ),
    EntityMigrationData(
        "Cyanuric Acid",
        "chem_cya",
        "Cyanuric Acid",
        "chem_cya",
        SENSOR_DOMAIN,
    ),
    EntityMigrationData(
        "Old Sensor",
        "old_sensor",
        "Old Sensor",
        "old_sensor",
        SENSOR_DOMAIN,
    ),
    EntityMigrationData(
        "Pump Sensor Missing Index",
        "currentWatts",
        "Pump Sensor Missing Index",
        "currentWatts",
        SENSOR_DOMAIN,
    ),
]

MIGRATION_CONNECT = lambda *args, **kwargs: stub_async_connect(
    DATA_MIN_MIGRATION, *args, **kwargs
)


@pytest.mark.parametrize(
    ("entity_def", "ent_data"),
    [
        (
            {
                "domain": ent_data.domain,
                "platform": DOMAIN,
                "unique_id": f"{MOCK_ADAPTER_MAC}_{ent_data.old_key}",
                "suggested_object_id": f"{MOCK_ADAPTER_NAME} {ent_data.old_name}",
                "disabled_by": None,
                "has_entity_name": True,
                "original_name": ent_data.old_name,
            },
            ent_data,
        )
        for ent_data in TEST_MIGRATING_ENTITIES
    ],
    ids=[ent_data.old_name for ent_data in TEST_MIGRATING_ENTITIES],
)
async def test_async_migrate_entries(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    entity_def: dict,
    ent_data: EntityMigrationData,
) -> None:
    """Test migration to new entity names."""

    mock_config_entry.add_to_hass(hass)

    entity_registry = er.async_get(hass)
    device_registry = dr.async_get(hass)

    device: dr.DeviceEntry = device_registry.async_get_or_create(
        config_entry_id=mock_config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, MOCK_ADAPTER_MAC)},
    )

    TEST_EXISTING_ENTRY = {
        "domain": SENSOR_DOMAIN,
        "platform": DOMAIN,
        "unique_id": f"{MOCK_ADAPTER_MAC}_cya",
        "suggested_object_id": f"{MOCK_ADAPTER_NAME} CYA",
        "disabled_by": None,
        "has_entity_name": True,
        "original_name": "CYA",
    }

    entity_registry.async_get_or_create(
        **TEST_EXISTING_ENTRY, device_id=device.id, config_entry=mock_config_entry
    )

    entity: er.RegistryEntry = entity_registry.async_get_or_create(
        **entity_def, device_id=device.id, config_entry=mock_config_entry
    )

    old_eid = f"{ent_data.domain}.{slugify(f'{MOCK_ADAPTER_NAME} {ent_data.old_name}')}"
    old_uid = f"{MOCK_ADAPTER_MAC}_{ent_data.old_key}"
    new_eid = f"{ent_data.domain}.{slugify(f'{MOCK_ADAPTER_NAME} {ent_data.new_name}')}"
    new_uid = f"{MOCK_ADAPTER_MAC}_{ent_data.new_key}"

    assert entity.unique_id == old_uid
    assert entity.entity_id == old_eid

    with patch(
        GATEWAY_DISCOVERY_IMPORT_PATH,
        return_value={},
    ), patch.multiple(
        ScreenLogicGateway,
        async_connect=MIGRATION_CONNECT,
        is_connected=True,
        _async_connected_request=DEFAULT,
    ):
        assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
        await hass.async_block_till_done()

    entity_migrated = entity_registry.async_get(new_eid)
    assert entity_migrated
    assert entity_migrated.entity_id == new_eid
    assert entity_migrated.unique_id == new_uid
    assert entity_migrated.original_name == ent_data.new_name


async def test_entity_migration_data(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test ENTITY_MIGRATION data guards."""

    mock_config_entry.add_to_hass(hass)

    entity_registry = er.async_get(hass)
    device_registry = dr.async_get(hass)

    device: dr.DeviceEntry = device_registry.async_get_or_create(
        config_entry_id=mock_config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, MOCK_ADAPTER_MAC)},
    )

    TEST_EXISTING_ENTRY = {
        "domain": SENSOR_DOMAIN,
        "platform": DOMAIN,
        "unique_id": f"{MOCK_ADAPTER_MAC}_missing_device",
        "suggested_object_id": f"{MOCK_ADAPTER_NAME} Missing Migration Device",
        "disabled_by": None,
        "has_entity_name": True,
        "original_name": "EMissing Migration Device",
    }

    original_entity: er.RegistryEntry = entity_registry.async_get_or_create(
        **TEST_EXISTING_ENTRY, device_id=device.id, config_entry=mock_config_entry
    )

    old_eid = original_entity.entity_id
    old_uid = original_entity.unique_id

    assert old_uid == f"{MOCK_ADAPTER_MAC}_missing_device"
    assert (
        old_eid
        == f"{SENSOR_DOMAIN}.{slugify(f'{MOCK_ADAPTER_NAME} Missing Migration Device')}"
    )

    # This patch simulates bad data being added to ENTITY_MIGRATIONS
    with patch.dict(
        "homeassistant.components.screenlogic.data.ENTITY_MIGRATIONS",
        {
            "missing_device": {
                "new_key": "state",
                "old_name": "Missing Migration Device",
                "new_name": "Bad ENTITY_MIGRATIONS Entry",
            },
        },
    ), patch(
        GATEWAY_DISCOVERY_IMPORT_PATH,
        return_value={},
    ), patch.multiple(
        ScreenLogicGateway,
        async_connect=MIGRATION_CONNECT,
        is_connected=True,
        _async_connected_request=DEFAULT,
    ):
        assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
        await hass.async_block_till_done()

    entity_migrated = entity_registry.async_get(
        slugify(f"{MOCK_ADAPTER_NAME} Bad ENTITY_MIGRATIONS Entry")
    )
    assert entity_migrated is None

    entity_not_migrated = entity_registry.async_get(old_eid)
    assert entity_not_migrated == original_entity


async def test_platform_setup(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Test setup for platforms that define expected data."""
    stub_connect = lambda *args, **kwargs: stub_async_connect(
        DATA_MISSING_VALUES_CHEM_CHLOR, *args, **kwargs
    )

    device_prefix = slugify(MOCK_ADAPTER_NAME)

    tested_entity_ids = [
        f"{BINARY_SENSOR_DOMAIN}.{device_prefix}_active_alert",
        f"{SENSOR_DOMAIN}.{device_prefix}_air_temperature",
        f"{NUMBER_DOMAIN}.{device_prefix}_pool_chlorinator_setpoint",
    ]

    mock_config_entry.add_to_hass(hass)

    with patch(
        GATEWAY_DISCOVERY_IMPORT_PATH,
        return_value={},
    ), patch.multiple(
        ScreenLogicGateway,
        async_connect=stub_connect,
        is_connected=True,
        _async_connected_request=DEFAULT,
    ):
        assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
        await hass.async_block_till_done()

        for entity_id in tested_entity_ids:
            assert hass.states.get(entity_id) is not None