"""Tests for the Minecraft Server integration."""
from unittest.mock import patch

from mcstatus import JavaServer
import pytest

from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.minecraft_server.const import DEFAULT_NAME, DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er

from .const import (
    TEST_ADDRESS,
    TEST_CONFIG_ENTRY_ID,
    TEST_HOST,
    TEST_JAVA_STATUS_RESPONSE,
    TEST_PORT,
)

from tests.common import MockConfigEntry

TEST_UNIQUE_ID = f"{TEST_HOST}-{TEST_PORT}"

SENSOR_KEYS = [
    {"v1": "Latency Time", "v2": "latency"},
    {"v1": "Players Max", "v2": "players_max"},
    {"v1": "Players Online", "v2": "players_online"},
    {"v1": "Protocol Version", "v2": "protocol_version"},
    {"v1": "Version", "v2": "version"},
    {"v1": "World Message", "v2": "motd"},
]

BINARY_SENSOR_KEYS = {"v1": "Status", "v2": "status"}


@pytest.fixture
def v1_mock_config_entry() -> MockConfigEntry:
    """Create mock config entry with version 1."""
    return MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_UNIQUE_ID,
        entry_id=TEST_CONFIG_ENTRY_ID,
        data={
            CONF_NAME: DEFAULT_NAME,
            CONF_HOST: TEST_HOST,
            CONF_PORT: TEST_PORT,
        },
        version=1,
    )


def create_v1_mock_device_entry(hass: HomeAssistant, config_entry_id: str) -> str:
    """Create mock device entry with version 1."""
    device_registry = dr.async_get(hass)
    device_entry_v1 = device_registry.async_get_or_create(
        config_entry_id=config_entry_id,
        identifiers={(DOMAIN, TEST_UNIQUE_ID)},
    )
    device_entry_id = device_entry_v1.id

    assert device_entry_v1
    assert device_entry_id

    return device_entry_id


def create_v1_mock_sensor_entity_entries(
    hass: HomeAssistant, config_entry_id: str, device_entry_id: str
) -> list[dict]:
    """Create mock sensor entity entries with version 1."""
    sensor_entity_id_key_mapping_list = []
    config_entry = hass.config_entries.async_get_entry(config_entry_id)
    entity_registry = er.async_get(hass)

    for sensor_key in SENSOR_KEYS:
        entity_unique_id = f"{TEST_UNIQUE_ID}-{sensor_key['v1']}"
        entity_entry_v1 = entity_registry.async_get_or_create(
            SENSOR_DOMAIN,
            DOMAIN,
            unique_id=entity_unique_id,
            config_entry=config_entry,
            device_id=device_entry_id,
        )
        assert entity_entry_v1.unique_id == entity_unique_id
        sensor_entity_id_key_mapping_list.append(
            {"entity_id": entity_entry_v1.entity_id, "key": sensor_key["v2"]}
        )

    return sensor_entity_id_key_mapping_list


def create_v1_mock_binary_sensor_entity_entry(
    hass: HomeAssistant, config_entry_id: str, device_entry_id: str
) -> dict:
    """Create mock binary sensor entity entry with version 1."""
    config_entry = hass.config_entries.async_get_entry(config_entry_id)
    entity_registry = er.async_get(hass)
    entity_unique_id = f"{TEST_UNIQUE_ID}-{BINARY_SENSOR_KEYS['v1']}"
    entity_entry = entity_registry.async_get_or_create(
        BINARY_SENSOR_DOMAIN,
        DOMAIN,
        unique_id=entity_unique_id,
        config_entry=config_entry,
        device_id=device_entry_id,
    )
    assert entity_entry.unique_id == entity_unique_id
    binary_sensor_entity_id_key_mapping = {
        "entity_id": entity_entry.entity_id,
        "key": BINARY_SENSOR_KEYS["v2"],
    }

    return binary_sensor_entity_id_key_mapping


async def test_setup_and_unload_entry(
    hass: HomeAssistant, java_mock_config_entry: MockConfigEntry
) -> None:
    """Test successful entry setup and unload."""
    java_mock_config_entry.add_to_hass(hass)

    with patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_lookup",
        return_value=JavaServer(host=TEST_HOST, port=TEST_PORT),
    ), patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_status",
        return_value=TEST_JAVA_STATUS_RESPONSE,
    ):
        assert await hass.config_entries.async_setup(java_mock_config_entry.entry_id)
        await hass.async_block_till_done()
        assert java_mock_config_entry.state == ConfigEntryState.LOADED

    assert await hass.config_entries.async_unload(java_mock_config_entry.entry_id)
    await hass.async_block_till_done()
    assert not hass.data.get(DOMAIN)
    assert java_mock_config_entry.state == ConfigEntryState.NOT_LOADED


async def test_setup_entry_lookup_failure(
    hass: HomeAssistant, java_mock_config_entry: MockConfigEntry
) -> None:
    """Test lookup failure in entry setup."""
    java_mock_config_entry.add_to_hass(hass)

    with patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_lookup",
        side_effect=ValueError,
    ):
        assert not await hass.config_entries.async_setup(
            java_mock_config_entry.entry_id
        )

    await hass.async_block_till_done()
    assert java_mock_config_entry.state == ConfigEntryState.SETUP_ERROR


async def test_setup_entry_init_failure(
    hass: HomeAssistant, java_mock_config_entry: MockConfigEntry
) -> None:
    """Test init failure in entry setup."""
    java_mock_config_entry.add_to_hass(hass)

    with patch(
        "homeassistant.components.minecraft_server.api.MinecraftServer.async_initialize",
        side_effect=None,
    ):
        assert not await hass.config_entries.async_setup(
            java_mock_config_entry.entry_id
        )

    await hass.async_block_till_done()
    assert java_mock_config_entry.state == ConfigEntryState.SETUP_RETRY


async def test_setup_entry_not_ready(
    hass: HomeAssistant, java_mock_config_entry: MockConfigEntry
) -> None:
    """Test entry setup not ready."""
    java_mock_config_entry.add_to_hass(hass)

    with patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_lookup",
        return_value=JavaServer(host=TEST_HOST, port=TEST_PORT),
    ), patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_status",
        return_value=OSError,
    ):
        assert not await hass.config_entries.async_setup(
            java_mock_config_entry.entry_id
        )

    await hass.async_block_till_done()
    assert java_mock_config_entry.state == ConfigEntryState.SETUP_RETRY


async def test_entry_migration(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    entity_registry: er.EntityRegistry,
    v1_mock_config_entry: MockConfigEntry,
) -> None:
    """Test entry migration from version 1 to 3, where host and port is required for the connection to the server."""
    v1_mock_config_entry.add_to_hass(hass)

    device_entry_id = create_v1_mock_device_entry(hass, v1_mock_config_entry.entry_id)
    sensor_entity_id_key_mapping_list = create_v1_mock_sensor_entity_entries(
        hass, v1_mock_config_entry.entry_id, device_entry_id
    )
    binary_sensor_entity_id_key_mapping = create_v1_mock_binary_sensor_entity_entry(
        hass, v1_mock_config_entry.entry_id, device_entry_id
    )

    # Trigger migration.
    with patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_lookup",
        side_effect=[
            ValueError,  # async_migrate_entry
            JavaServer(host=TEST_HOST, port=TEST_PORT),  # async_migrate_entry
            JavaServer(host=TEST_HOST, port=TEST_PORT),  # async_setup_entry
        ],
    ), patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_status",
        return_value=TEST_JAVA_STATUS_RESPONSE,
    ):
        assert await hass.config_entries.async_setup(v1_mock_config_entry.entry_id)
        await hass.async_block_till_done()

    migrated_config_entry = v1_mock_config_entry

    # Test migrated config entry.
    assert migrated_config_entry.unique_id is None
    assert migrated_config_entry.data == {
        CONF_NAME: DEFAULT_NAME,
        CONF_ADDRESS: TEST_ADDRESS,
    }
    assert migrated_config_entry.version == 3
    assert migrated_config_entry.state == ConfigEntryState.LOADED

    # Test migrated device entry.
    device_entry = device_registry.async_get(device_entry_id)
    assert device_entry.identifiers == {(DOMAIN, migrated_config_entry.entry_id)}

    # Test migrated sensor entity entries.
    for mapping in sensor_entity_id_key_mapping_list:
        entity_entry = entity_registry.async_get(mapping["entity_id"])
        assert (
            entity_entry.unique_id
            == f"{migrated_config_entry.entry_id}-{mapping['key']}"
        )

    # Test migrated binary sensor entity entry.
    entity_entry = entity_registry.async_get(
        binary_sensor_entity_id_key_mapping["entity_id"]
    )
    assert (
        entity_entry.unique_id
        == f"{migrated_config_entry.entry_id}-{binary_sensor_entity_id_key_mapping['key']}"
    )


async def test_entry_migration_host_only(
    hass: HomeAssistant, v1_mock_config_entry: MockConfigEntry
) -> None:
    """Test entry migration from version 1 to 3, where host alone is sufficient for the connection to the server."""
    v1_mock_config_entry.add_to_hass(hass)

    device_entry_id = create_v1_mock_device_entry(hass, v1_mock_config_entry.entry_id)
    create_v1_mock_sensor_entity_entries(
        hass, v1_mock_config_entry.entry_id, device_entry_id
    )
    create_v1_mock_binary_sensor_entity_entry(
        hass, v1_mock_config_entry.entry_id, device_entry_id
    )

    # Trigger migration.
    with patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_lookup",
        return_value=JavaServer(host=TEST_HOST, port=TEST_PORT),
    ), patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_status",
        return_value=TEST_JAVA_STATUS_RESPONSE,
    ):
        assert await hass.config_entries.async_setup(v1_mock_config_entry.entry_id)
        await hass.async_block_till_done()

    # Test migrated config entry.
    assert v1_mock_config_entry.unique_id is None
    assert v1_mock_config_entry.data == {
        CONF_NAME: DEFAULT_NAME,
        CONF_ADDRESS: TEST_HOST,
    }
    assert v1_mock_config_entry.version == 3
    assert v1_mock_config_entry.state == ConfigEntryState.LOADED


async def test_entry_migration_v3_failure(
    hass: HomeAssistant, v1_mock_config_entry: MockConfigEntry
) -> None:
    """Test failed entry migration from version 2 to 3."""
    v1_mock_config_entry.add_to_hass(hass)

    device_entry_id = create_v1_mock_device_entry(hass, v1_mock_config_entry.entry_id)
    create_v1_mock_sensor_entity_entries(
        hass, v1_mock_config_entry.entry_id, device_entry_id
    )
    create_v1_mock_binary_sensor_entity_entry(
        hass, v1_mock_config_entry.entry_id, device_entry_id
    )

    # Trigger migration.
    with patch(
        "homeassistant.components.minecraft_server.api.JavaServer.async_lookup",
        side_effect=[
            ValueError,  # async_migrate_entry
            ValueError,  # async_migrate_entry
        ],
    ):
        assert not await hass.config_entries.async_setup(v1_mock_config_entry.entry_id)
        await hass.async_block_till_done()

    # Test config entry.
    assert v1_mock_config_entry.version == 2
    assert v1_mock_config_entry.state == ConfigEntryState.MIGRATION_ERROR