"""The tests for shared code of the MQTT platform."""

from typing import Any
from unittest.mock import call, patch

import pytest

from homeassistant.components import mqtt, sensor
from homeassistant.components.mqtt.const import DOMAIN
from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME
from homeassistant.config_entries import ConfigSubentryData
from homeassistant.const import (
    ATTR_FRIENDLY_NAME,
    EVENT_HOMEASSISTANT_STARTED,
    EVENT_STATE_CHANGED,
)
from homeassistant.core import CoreState, HomeAssistant, callback
from homeassistant.helpers import (
    device_registry as dr,
    entity_registry as er,
    issue_registry as ir,
    json,
)
from homeassistant.util import slugify

from .common import (
    MOCK_NOTIFY_SUBENTRY_DATA,
    MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA,
    MOCK_SUBENTRY_DATA_SET_MIX,
)

from tests.common import MockConfigEntry, async_capture_events, async_fire_mqtt_message
from tests.typing import MqttMockHAClientGenerator


@pytest.mark.parametrize(
    "hass_config",
    [
        {
            DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "test-topic",
                    "availability_topic": "test-topic",
                    "payload_available": True,
                    "payload_not_available": False,
                    "value_template": "{{ int(value) or '' }}",
                    "availability_template": "{{ value != '0' }}",
                }
            }
        }
    ],
)
async def test_availability_with_shared_state_topic(
    hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
) -> None:
    """Test the state is not changed twice.

    When an entity with a shared state_topic and availability_topic becomes available
    The state should only change once.
    """
    await mqtt_mock_entry()

    events = []

    @callback
    def test_callback(event) -> None:
        events.append(event)

    hass.bus.async_listen(EVENT_STATE_CHANGED, test_callback)

    async_fire_mqtt_message(hass, "test-topic", "100")
    await hass.async_block_till_done()
    # Initially the state and the availability change
    assert len(events) == 1

    events.clear()
    async_fire_mqtt_message(hass, "test-topic", "50")
    await hass.async_block_till_done()
    assert len(events) == 1

    events.clear()
    async_fire_mqtt_message(hass, "test-topic", "0")
    await hass.async_block_till_done()
    # Only the availability is changed since the template resukts in an empty payload
    # This does not change the state
    assert len(events) == 1

    events.clear()
    async_fire_mqtt_message(hass, "test-topic", "10")
    await hass.async_block_till_done()
    # The availability is changed but the topic is shared,
    # hence there the state will be written when the value is updated
    assert len(events) == 1


@pytest.mark.parametrize(
    (
        "hass_config",
        "entity_id",
        "friendly_name",
        "device_name",
        "assert_log",
    ),
    [
        (  # default_entity_name_without_device_name
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device": {"identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.mqtt_sensor",
            DEFAULT_SENSOR_NAME,
            None,
            True,
        ),
        (  # default_entity_name_with_device_name
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device": {"name": "Test", "identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.test_mqtt_sensor",
            "Test MQTT Sensor",
            "Test",
            False,
        ),
        (  # name_follows_device_class
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {"name": "Test", "identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.test_humidity",
            "Test Humidity",
            "Test",
            False,
        ),
        (  # name_follows_device_class_without_device_name
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {"identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.humidity",
            "Humidity",
            None,
            True,
        ),
        (  # name_overrides_device_class
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": "MySensor",
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {"name": "Test", "identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.test_mysensor",
            "Test MySensor",
            "Test",
            False,
        ),
        (  # name_set_no_device_name_set
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": "MySensor",
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {"identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.mysensor",
            "MySensor",
            None,
            True,
        ),
        (  # none_entity_name_with_device_name
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": None,
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {"name": "Test", "identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.test",
            "Test",
            "Test",
            False,
        ),
        (  # none_entity_name_without_device_name
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": None,
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {"identifiers": ["helloworld"]},
                    }
                }
            },
            "sensor.mqtt_veryunique",
            "mqtt veryunique",
            None,
            True,
        ),
        (  # entity_name_and_device_name_the_same
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": "Hello world",
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {
                            "identifiers": ["helloworld"],
                            "name": "Hello world",
                        },
                    }
                }
            },
            "sensor.hello_world_hello_world",
            "Hello world Hello world",
            "Hello world",
            False,
        ),
        (  # entity_name_startswith_device_name1
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": "World automation",
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {
                            "identifiers": ["helloworld"],
                            "name": "World",
                        },
                    }
                }
            },
            "sensor.world_world_automation",
            "World World automation",
            "World",
            False,
        ),
        (  # entity_name_startswith_device_name2
            {
                DOMAIN: {
                    sensor.DOMAIN: {
                        "name": "world automation",
                        "state_topic": "test-topic",
                        "unique_id": "veryunique",
                        "device_class": "humidity",
                        "device": {
                            "identifiers": ["helloworld"],
                            "name": "world",
                        },
                    }
                }
            },
            "sensor.world_world_automation",
            "world world automation",
            "world",
            False,
        ),
    ],
    ids=[
        "default_entity_name_without_device_name",
        "default_entity_name_with_device_name",
        "name_follows_device_class",
        "name_follows_device_class_without_device_name",
        "name_overrides_device_class",
        "name_set_no_device_name_set",
        "none_entity_name_with_device_name",
        "none_entity_name_without_device_name",
        "entity_name_and_device_name_the_same",
        "entity_name_startswith_device_name1",
        "entity_name_startswith_device_name2",
    ],
)
@patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0)
@pytest.mark.usefixtures("mqtt_client_mock")
async def test_default_entity_and_device_name(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    caplog: pytest.LogCaptureFixture,
    entity_id: str,
    friendly_name: str,
    device_name: str | None,
    assert_log: bool,
) -> None:
    """Test device name setup with and without a device_class set.

    This is a test helper for the _setup_common_attributes_from_config mixin.
    """

    events = async_capture_events(hass, ir.EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED)
    hass.set_state(CoreState.starting)
    await hass.async_block_till_done()

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={mqtt.CONF_BROKER: "mock-broker", mqtt.CONF_PROTOCOL: "5"},
        version=mqtt.CONFIG_ENTRY_VERSION,
        minor_version=mqtt.CONFIG_ENTRY_MINOR_VERSION,
    )
    entry.add_to_hass(hass)
    assert await hass.config_entries.async_setup(entry.entry_id)
    hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
    await hass.async_block_till_done()

    device = device_registry.async_get_device_by_identifier(
        ("mqtt", "helloworld"), entry.entry_id
    )
    assert device is not None
    assert device.name == device_name

    state = hass.states.get(entity_id)
    assert state is not None
    assert state.name == friendly_name

    assert (
        "MQTT device information always needs to include a name" in caplog.text
    ) is assert_log

    # Assert that no issues ware registered
    assert len(events) == 0
    await hass.async_block_till_done(wait_background_tasks=True)
    # Assert that no issues ware registered
    assert len(events) == 0


async def test_name_attribute_is_set_or_not(
    hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
) -> None:
    """Test frendly name with device_class set.

    This is a test helper for the _setup_common_attributes_from_config mixin.
    """
    await mqtt_mock_entry()
    async_fire_mqtt_message(
        hass,
        "homeassistant/binary_sensor/bla/config",
        '{ "name": "Gate", "state_topic": "test-topic", "device_class": "door", '
        '"default_entity_id": "binary_sensor.gate",'
        '"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}'
        "}",
    )
    await hass.async_block_till_done()

    state = hass.states.get("binary_sensor.gate")

    assert state is not None
    assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Gate"

    # Remove the name in a discovery update
    async_fire_mqtt_message(
        hass,
        "homeassistant/binary_sensor/bla/config",
        '{ "state_topic": "test-topic", "device_class": "door", '
        '"default_entity_id": "binary_sensor.gate",'
        '"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}'
        "}",
    )
    await hass.async_block_till_done()

    state = hass.states.get("binary_sensor.gate")

    assert state is not None
    assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Door"

    # Set the name to `null` in a discovery update
    async_fire_mqtt_message(
        hass,
        "homeassistant/binary_sensor/bla/config",
        '{ "name": null, "state_topic": "test-topic", "device_class": "door", '
        '"default_entity_id": "binary_sensor.gate",'
        '"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}'
        "}",
    )
    await hass.async_block_till_done()

    state = hass.states.get("binary_sensor.gate")

    assert state is not None
    assert state.attributes.get(ATTR_FRIENDLY_NAME) is None


@pytest.mark.parametrize(
    "hass_config",
    [
        {
            DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "availability_topic": "test-topic",
                    "availability_template": "{{ value_json.some_var * 1 }}",
                }
            }
        },
        {
            DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "availability": {
                        "topic": "test-topic",
                        "value_template": "{{ value_json.some_var * 1 }}",
                    },
                }
            }
        },
        {
            DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "json_attributes_topic": "test-topic",
                    "json_attributes_template": "{{ value_json.some_var * 1 }}",
                }
            }
        },
    ],
    ids=[
        "availability_template1",
        "availability_template2",
        "json_attributes_template",
    ],
)
async def test_value_template_fails(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test the rendering of MQTT value template fails."""
    await mqtt_mock_entry()
    async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
    assert (
        "TypeError: unsupported operand type(s) for *:"
        " 'NoneType' and 'int' rendering template" in caplog.text
    )


@pytest.mark.parametrize(
    "hass_config",
    [
        {
            DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "enabled_by_default": True,
                    "unique_id": "very_unique",
                }
            }
        },
    ],
)
async def test_registry_enabled_by_default(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test an entity that is enabled by default or not."""
    await mqtt_mock_entry()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert not entry.disabled


@pytest.mark.parametrize(
    "hass_config",
    [
        {
            DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "enabled_by_default": False,
                    "unique_id": "very_unique",
                }
            }
        },
    ],
)
async def test_registry_not_enabled_by_default(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test an entity that is not enabled by default or not."""
    await mqtt_mock_entry()
    state = hass.states.get("sensor.test")
    assert state is None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert entry.disabled


async def test_registry_enable_not_enabled_or_visible_by_default_entity(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    entity_registry: er.EntityRegistry,
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test enabling an entity that was not enabled or not visible by default."""
    await mqtt_mock_entry()

    discovery_topic = "homeassistant/sensor/bla/config"
    config_disabled = json.json_dumps(
        {
            "name": None,
            "state_topic": "state-topic",
            "enabled_by_default": False,
            "visible_by_default": True,
            "unique_id": "very_unique",
            "default_entity_id": "sensor.test",
            "device": {"identifiers": "very_unique_device", "name": "test"},
        }
    )
    config_enabled = json.json_dumps(
        {
            "name": None,
            "state_topic": "state-topic",
            "enabled_by_default": True,
            "visible_by_default": False,
            "unique_id": "very_unique",
            "default_entity_id": "sensor.test",
            "device": {"identifiers": "very_unique_device", "name": "test"},
        }
    )
    config_enabled_new_entity_name = json.json_dumps(
        {
            "name": None,
            "state_topic": "state-topic",
            "enabled_by_default": True,
            "visible_by_default": True,
            "unique_id": "very_unique",
            "default_entity_id": "sensor.test_new",
            "device": {"identifiers": "very_unique_device", "name": "test"},
        }
    )

    async_fire_mqtt_message(hass, discovery_topic, config_disabled)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test")
    assert state is None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert entry.disabled
    assert entry.hidden is False
    assert (device_id := entry.device_id)
    assert device_registry.async_get(device_id) is not None

    # Remove the entity and device
    # At this stage no entry existed during the initialization
    async_fire_mqtt_message(hass, discovery_topic, "")
    await hass.async_block_till_done(wait_background_tasks=True)
    entry = entity_registry.async_get("sensor.test")
    assert entry is None
    # Assert device is cleaned up
    assert device_registry.async_get(device_id) is None

    # Rediscover the previous deleted entity and allow it to be enabled
    # but not visible by default
    async_fire_mqtt_message(hass, discovery_topic, config_enabled)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert not entry.disabled
    assert entry.hidden is True
    assert device_registry.async_get(device_id) is not None

    # Update entity to not be enabled by default
    # The entity should stay available as it was enabled before
    # Also it should remain hidden
    async_fire_mqtt_message(hass, discovery_topic, config_disabled)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert not entry.disabled
    assert entry.hidden is True
    assert device_registry.async_get(device_id) is not None

    # Delete the entity again
    async_fire_mqtt_message(hass, discovery_topic, "")
    await hass.async_block_till_done(wait_background_tasks=True)
    entry = entity_registry.async_get("sensor.test")
    assert entry is None
    # Assert device is cleaned up
    assert device_registry.async_get(device_id) is None

    # Repeat the re-discovery, with a new entity name
    # The entity should be enabled and visible by default now
    async_fire_mqtt_message(hass, discovery_topic, config_enabled_new_entity_name)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test_new")
    assert state is not None
    entry = entity_registry.async_get("sensor.test_new")
    assert entry is not None
    assert entry.hidden is False
    assert not entry.disabled
    assert device_registry.async_get(device_id) is not None

    # Mock the entry is disabled by the user
    entity_registry.async_update_entity(
        "sensor.test_new", disabled_by=er.RegistryEntryDisabler.USER
    )
    await hass.async_block_till_done(wait_background_tasks=True)
    state = hass.states.get("sensor.test_new")
    assert state is None
    # Remove the disabled entry
    entity_registry.async_remove("sensor.test_new")
    await hass.async_block_till_done(wait_background_tasks=True)
    entry = entity_registry.async_get("sensor.test_new")
    assert entry is None

    # Repeat the re-discovery, and assert the entity remains disabled
    async_fire_mqtt_message(hass, discovery_topic, config_enabled_new_entity_name)
    await hass.async_block_till_done()
    entry = entity_registry.async_get("sensor.test_new")
    assert entry is not None
    assert entry.disabled
    assert entry.hidden is False
    assert device_registry.async_get(device_id) is not None


async def test_visible_by_default_with_user_override(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    entity_registry: er.EntityRegistry,
    device_registry: dr.DeviceRegistry,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test visible_by_default is respected but user hidden_by override is preserved."""
    await mqtt_mock_entry()

    discovery_topic = "homeassistant/sensor/bla/config"
    config_visible = json.json_dumps(
        {
            "name": None,
            "state_topic": "state-topic",
            "visible_by_default": True,
            "unique_id": "very_unique",
            "default_entity_id": "sensor.test",
            "device": {"identifiers": "very_unique_device", "name": "test"},
        }
    )
    config_hidden = json.json_dumps(
        {
            "name": None,
            "state_topic": "state-topic",
            "visible_by_default": False,
            "unique_id": "very_unique",
            "default_entity_id": "sensor.test",
            "device": {"identifiers": "very_unique_device", "name": "test"},
        }
    )

    async_fire_mqtt_message(hass, discovery_topic, config_hidden)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert not entry.disabled
    assert entry.hidden
    assert entry.hidden_by is er.RegistryEntryHider.INTEGRATION
    assert (device_id := entry.device_id)
    assert device_registry.async_get(device_id) is not None

    # Remove the entity and device
    # At this stage no entry existed during the initialization
    async_fire_mqtt_message(hass, discovery_topic, "")
    await hass.async_block_till_done(wait_background_tasks=True)
    entry = entity_registry.async_get("sensor.test")
    assert entry is None
    # Assert device is cleaned up
    assert device_registry.async_get(device_id) is None

    # Rediscover the previous deleted entity, now visible by default
    async_fire_mqtt_message(hass, discovery_topic, config_visible)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert not entry.hidden
    assert entry.hidden_by is None
    assert device_registry.async_get(device_id) is not None

    # Mock the user hides the entity
    entity_registry.async_update_entity(
        "sensor.test", hidden_by=er.RegistryEntryHider.USER
    )
    await hass.async_block_till_done(wait_background_tasks=True)

    # Remove the entity and device,
    # the hidden by user flag should be preserved in the deleted entities registry
    async_fire_mqtt_message(hass, discovery_topic, "")
    await hass.async_block_till_done(wait_background_tasks=True)
    entry = entity_registry.async_get("sensor.test")
    assert entry is None
    # Assert device is cleaned up
    assert device_registry.async_get(device_id) is None

    # Rediscover again and assert the entity remains hidden
    # because it was hidden by the user, even though visible_by_default is True
    async_fire_mqtt_message(hass, discovery_topic, config_visible)
    await hass.async_block_till_done()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert entry is not None
    assert entry.hidden
    assert entry.hidden_by is er.RegistryEntryHider.USER
    assert device_registry.async_get(device_id) is not None
    assert (
        "Restored entity sensor.test was configured as visible by default, "
        "but was hidden by the user before, and will remain hidden" in caplog.text
    )


@pytest.mark.parametrize(
    "hass_config",
    [
        {
            mqtt.DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "unique_id": "very_unique",
                }
            }
        },
        {
            mqtt.DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "visible_by_default": True,
                    "unique_id": "very_unique",
                }
            }
        },
    ],
)
async def test_registry_visible_by_default(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test an entity is visible with visible_by_default set or without."""
    await mqtt_mock_entry()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert not entry.disabled
    assert entry.hidden is False
    assert entry.hidden_by is None


@pytest.mark.parametrize(
    "hass_config",
    [
        {
            mqtt.DOMAIN: {
                sensor.DOMAIN: {
                    "name": "test",
                    "state_topic": "state-topic",
                    "visible_by_default": False,
                    "unique_id": "very_unique",
                }
            }
        },
    ],
)
async def test_registry_not_visible_by_default(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test an entity that is not visible by default."""
    await mqtt_mock_entry()
    state = hass.states.get("sensor.test")
    assert state is not None
    entry = entity_registry.async_get("sensor.test")
    assert entry.hidden is True
    assert entry.hidden_by is er.RegistryEntryHider.INTEGRATION


@pytest.mark.parametrize(
    "mqtt_config_subentries_data",
    [
        (
            ConfigSubentryData(
                data=MOCK_SUBENTRY_DATA_SET_MIX,
                subentry_type="device",
                title="Mock subentry",
            ),
        )
    ],
)
async def test_loading_subentries(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    mqtt_config_subentries_data: tuple[dict[str, Any]],
    device_registry: dr.DeviceRegistry,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test loading subentries."""
    await mqtt_mock_entry()
    entry = hass.config_entries.async_entries(DOMAIN)[0]
    subentry_id = next(iter(entry.subentries))
    # Each subentry has one device
    device = device_registry.async_get_device_by_identifier(
        ("mqtt", subentry_id), entry.entry_id
    )
    assert device is not None
    for object_id, component in mqtt_config_subentries_data[0]["data"][
        "components"
    ].items():
        platform = component["platform"]
        entity_id = f"{platform}.{slugify(device.name)}_{slugify(component['name'])}"
        entity_entry_entity_id = entity_registry.async_get_entity_id(
            platform, DOMAIN, f"{subentry_id}_{object_id}"
        )
        assert entity_entry_entity_id == entity_id
        state = hass.states.get(entity_id)
        assert state is not None
        assert (
            state.attributes.get("entity_picture") == f"https://example.com/{object_id}"
        )
        # Availability was configured, so entities are unavailable
        assert state.state == "unavailable"

    # Make entities available
    async_fire_mqtt_message(hass, "test/availability", '{"availability": "online"}')
    for component in mqtt_config_subentries_data[0]["data"]["components"].values():
        platform = component["platform"]
        entity_id = f"{platform}.{slugify(device.name)}_{slugify(component['name'])}"
        state = hass.states.get(entity_id)
        assert state is not None
        assert state.state in ("off", "unknown")


@pytest.mark.parametrize(
    "mqtt_config_subentries_data",
    [
        (
            ConfigSubentryData(
                data=MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA,
                subentry_type="device",
                title="Mock subentry",
            ),
        )
    ],
)
async def test_loading_subentry_with_bad_component_schema(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    device_registry: dr.DeviceRegistry,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test loading subentries."""
    await mqtt_mock_entry()
    entry = hass.config_entries.async_entries(DOMAIN)[0]
    subentry_id = next(iter(entry.subentries))
    # Each subentry has one device
    device = device_registry.async_get_device_by_identifier(
        ("mqtt", subentry_id), entry.entry_id
    )
    assert device is None
    assert (
        "Schema violation occurred when trying to set up entity from subentry"
        in caplog.text
    )


@pytest.mark.parametrize(
    "mqtt_config_subentries_data",
    [
        (
            ConfigSubentryData(
                data=MOCK_NOTIFY_SUBENTRY_DATA,
                subentry_type="device",
                title="Mock subentry",
            ),
        )
    ],
)
async def test_qos_on_mqtt_device_from_subentry(
    hass: HomeAssistant,
    mqtt_mock_entry: MqttMockHAClientGenerator,
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test QoS is set correctly on entities from MQTT device."""
    mqtt_mock = await mqtt_mock_entry()
    entry = hass.config_entries.async_entries(DOMAIN)[0]
    subentry_id = next(iter(entry.subentries))
    # Each subentry has one device
    device = device_registry.async_get_device_by_identifier(
        ("mqtt", subentry_id), entry.entry_id
    )
    assert device is not None
    assert hass.states.get("notify.milk_notifier_milkman_alert") is not None
    await hass.services.async_call(
        "notify",
        "send_message",
        {"entity_id": "notify.milk_notifier_milkman_alert", "message": "Test message"},
    )
    await hass.async_block_till_done()
    assert len(mqtt_mock.async_publish.mock_calls) == 1
    mqtt_mock.async_publish.mock_calls[0] = call("test-topic", "Test message", 1, False)
