"""The tests for the MQTT discovery."""
import copy
import json

from homeassistant.components.tasmota.const import DEFAULT_PREFIX
from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED

from .conftest import setup_tasmota_helper
from .test_common import DEFAULT_CONFIG

from tests.async_mock import patch
from tests.common import async_fire_mqtt_message


async def test_subscribing_config_topic(hass, mqtt_mock, setup_tasmota):
    """Test setting up discovery."""
    discovery_topic = DEFAULT_PREFIX

    assert mqtt_mock.async_subscribe.called
    call_args = mqtt_mock.async_subscribe.mock_calls[0][1]
    assert call_args[0] == discovery_topic + "/#"
    assert call_args[2] == 0


async def test_valid_discovery_message(hass, mqtt_mock, caplog):
    """Test discovery callback called."""
    config = copy.deepcopy(DEFAULT_CONFIG)

    with patch(
        "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
    ) as mock_tasmota_has_entities:
        await setup_tasmota_helper(hass)

        async_fire_mqtt_message(
            hass, f"{DEFAULT_PREFIX}/00000049A3BC/config", json.dumps(config)
        )
        await hass.async_block_till_done()
        assert mock_tasmota_has_entities.called


async def test_invalid_topic(hass, mqtt_mock):
    """Test receiving discovery message on wrong topic."""
    with patch(
        "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
    ) as mock_tasmota_has_entities:
        await setup_tasmota_helper(hass)

        async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/123456/configuration", "{}")
        await hass.async_block_till_done()
        assert not mock_tasmota_has_entities.called


async def test_invalid_message(hass, mqtt_mock, caplog):
    """Test receiving an invalid message."""
    with patch(
        "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
    ) as mock_tasmota_has_entities:
        await setup_tasmota_helper(hass)

        async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/123456/config", "asd")
        await hass.async_block_till_done()
        assert "Invalid discovery message" in caplog.text
        assert not mock_tasmota_has_entities.called


async def test_invalid_mac(hass, mqtt_mock, caplog):
    """Test topic is not matching device MAC."""
    config = copy.deepcopy(DEFAULT_CONFIG)

    with patch(
        "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
    ) as mock_tasmota_has_entities:
        await setup_tasmota_helper(hass)

        async_fire_mqtt_message(
            hass, f"{DEFAULT_PREFIX}/00000049A3BA/config", json.dumps(config)
        )
        await hass.async_block_till_done()
        assert "MAC mismatch" in caplog.text
        assert not mock_tasmota_has_entities.called


async def test_correct_config_discovery(
    hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
    """Test receiving valid discovery message."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    config["rl"][0] = 1
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device and registry entries are created
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None
    entity_entry = entity_reg.async_get("switch.test")
    assert entity_entry is not None

    state = hass.states.get("switch.test")
    assert state is not None
    assert state.name == "Test"

    assert (mac, "switch", "relay", 0) in hass.data[ALREADY_DISCOVERED]


async def test_device_discover(
    hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
    """Test setting up a device."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device and registry entries are created
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None
    assert device_entry.manufacturer == "Tasmota"
    assert device_entry.model == config["md"]
    assert device_entry.name == config["dn"]
    assert device_entry.sw_version == config["sw"]


async def test_device_update(
    hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
    """Test updating a device."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    config["md"] = "Model 1"
    config["dn"] = "Name 1"
    config["sw"] = "v1.2.3.4"
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device entry is created
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None

    # Update device parameters
    config["md"] = "Another model"
    config["dn"] = "Another name"
    config["sw"] = "v6.6.6"

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device entry is updated
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None
    assert device_entry.model == "Another model"
    assert device_entry.name == "Another name"
    assert device_entry.sw_version == "v6.6.6"


async def test_device_remove(
    hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
    """Test removing a discovered device."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device entry is created
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        "",
    )
    await hass.async_block_till_done()

    # Verify device entry is removed
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is None


async def test_device_remove_stale(hass, mqtt_mock, caplog, device_reg, setup_tasmota):
    """Test removing a stale (undiscovered) device does not throw."""
    mac = "00000049A3BC"

    config_entry = hass.config_entries.async_entries("tasmota")[0]

    # Create a device
    device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={("mac", mac)},
    )

    # Verify device entry was created
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None

    # Remove the device
    device_reg.async_remove_device(device_entry.id)

    # Verify device entry is removed
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is None


async def test_device_rediscover(
    hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
    """Test removing a device."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device entry is created
    device_entry1 = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry1 is not None

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        "",
    )
    await hass.async_block_till_done()

    # Verify device entry is removed
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is None

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    # Verify device entry is created, and id is reused
    device_entry = device_reg.async_get_device(set(), {("mac", mac)})
    assert device_entry is not None
    assert device_entry1.id == device_entry.id


async def test_entity_duplicate_discovery(hass, mqtt_mock, caplog, setup_tasmota):
    """Test entities are not duplicated."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    config["rl"][0] = 1
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()

    state = hass.states.get("switch.test")
    state_duplicate = hass.states.get("binary_sensor.beer1")

    assert state is not None
    assert state.name == "Test"
    assert state_duplicate is None
    assert (
        f"Entity already added, sending update: switch ('{mac}', 'switch', 'relay', 0)"
        in caplog.text
    )


async def test_entity_duplicate_removal(hass, mqtt_mock, caplog, setup_tasmota):
    """Test removing entity twice."""
    config = copy.deepcopy(DEFAULT_CONFIG)
    config["rl"][0] = 1
    mac = config["mac"]

    async_fire_mqtt_message(
        hass,
        f"{DEFAULT_PREFIX}/{mac}/config",
        json.dumps(config),
    )
    await hass.async_block_till_done()
    config["rl"][0] = 0
    async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", json.dumps(config))
    await hass.async_block_till_done()
    assert f"Removing entity: switch ('{mac}', 'switch', 'relay', 0)" in caplog.text

    caplog.clear()
    async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", json.dumps(config))
    await hass.async_block_till_done()
    assert "Removing entity: switch" not in caplog.text