"""The tests for the Template vacuum platform."""

from dataclasses import asdict
from typing import Any

import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components import template, vacuum
from homeassistant.components.template.vacuum import CONF_CLEAN_SEGMENTS, CONF_SEGMENTS
from homeassistant.components.vacuum import (
    ATTR_FAN_SPEED,
    Segment,
    VacuumActivity,
    VacuumEntityFeature,
)
from homeassistant.const import (
    CONF_UNIQUE_ID,
    STATE_OFF,
    STATE_ON,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.helpers.entity_component import async_update_entity
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
from homeassistant.helpers.typing import ConfigType

from .conftest import (
    ConfigurationStyle,
    TemplatePlatformSetup,
    assert_action,
    assert_state_and_attributes,
    async_get_flow_preview_state,
    async_trigger,
    make_test_action,
    make_test_trigger,
    setup_and_test_nested_unique_id,
    setup_and_test_unique_id,
    setup_entity,
    setup_mock_template_entity_restore_state,
    setup_restore_template_entity,
)

from tests.common import MockConfigEntry, async_mock_restore_state_shutdown_restart
from tests.components.vacuum import common
from tests.typing import WebSocketGenerator

TEST_STATE_ENTITY_ID = "sensor.test_state"
TEST_ATTRIBUTE_ENTITY_ID = "sensor.test_attribute"
TEST_AVAILABILITY_ENTITY = "binary_sensor.availability"

TEST_VACUUM = TemplatePlatformSetup(
    vacuum.DOMAIN,
    "test_vacuum",
    make_test_trigger(
        TEST_STATE_ENTITY_ID,
        TEST_ATTRIBUTE_ENTITY_ID,
        TEST_AVAILABILITY_ENTITY,
    ),
)

CLEAN_SPOT_ACTION = make_test_action("clean_spot")
LOCATE_ACTION = make_test_action("locate")
PAUSE_ACTION = make_test_action("pause")
RETURN_TO_BASE_ACTION = make_test_action("return_to_base")
SET_FAN_SPEED_ACTION = make_test_action(
    "set_fan_speed", {"fan_speed": "{{ fan_speed }}"}
)
START_ACTION = make_test_action("start")
STOP_ACTION = make_test_action("stop")
CLEAN_SEGMENTS_ACTION = make_test_action(
    "clean_segments", {"segment_ids": "{{ segment_ids }}"}
)

TEMPLATE_VACUUM_ACTIONS = {
    **START_ACTION,
    **PAUSE_ACTION,
    **STOP_ACTION,
    **RETURN_TO_BASE_ACTION,
    **CLEAN_SPOT_ACTION,
    **LOCATE_ACTION,
    **SET_FAN_SPEED_ACTION,
}


def _verify(
    hass: HomeAssistant,
    expected_state: str,
    expected_fan_speed: str | None = None,
) -> None:
    """Verify vacuum's state and speed."""
    state = hass.states.get(TEST_VACUUM.entity_id)
    attributes = state.attributes
    assert state.state == expected_state
    assert attributes.get(ATTR_FAN_SPEED) == expected_fan_speed


@pytest.fixture
async def setup_vacuum(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    vacuum_config: dict[str, Any],
) -> None:
    """Do setup of number integration."""
    await setup_entity(hass, TEST_VACUUM, style, count, vacuum_config)


@pytest.fixture
async def setup_test_vacuum_with_extra_config(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    vacuum_config: dict[str, Any],
    extra_config: dict[str, Any],
) -> None:
    """Do setup of number integration."""
    await setup_entity(
        hass, TEST_VACUUM, style, count, vacuum_config, extra_config=extra_config
    )


@pytest.fixture
async def setup_state_vacuum(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str,
):
    """Do setup of vacuum integration using a state template."""
    await setup_entity(
        hass, TEST_VACUUM, style, count, TEMPLATE_VACUUM_ACTIONS, state_template
    )


@pytest.fixture
async def setup_base_vacuum(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str | None,
    extra_config: dict,
):
    """Do setup of vacuum integration using a state template."""
    await setup_entity(hass, TEST_VACUUM, style, count, extra_config, state_template)


@pytest.fixture
async def setup_single_attribute_state_vacuum(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str | None,
    attribute: str,
    attribute_template: str,
    extra_config: dict,
) -> None:
    """Do setup of vacuum integration testing a single attribute."""
    config = {attribute: attribute_template} if attribute and attribute_template else {}
    await setup_entity(
        hass,
        TEST_VACUUM,
        style,
        count,
        {**config, **TEMPLATE_VACUUM_ACTIONS},
        state_template,
        extra_config,
    )


@pytest.fixture
async def setup_attributes_state_vacuum(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str | None,
    attributes: dict,
) -> None:
    """Do setup of vacuum integration testing a single attribute."""
    await setup_entity(
        hass,
        TEST_VACUUM,
        style,
        count,
        TEMPLATE_VACUUM_ACTIONS,
        state_template,
        attributes=attributes,
    )


@pytest.mark.parametrize("count", [1])
@pytest.mark.parametrize(
    ("style", "state_template", "extra_config", "parm1"),
    [
        (
            ConfigurationStyle.MODERN,
            None,
            {"start": {"service": "script.vacuum_start"}},
            STATE_UNKNOWN,
        ),
        (
            ConfigurationStyle.TRIGGER,
            None,
            {"start": {"service": "script.vacuum_start"}},
            STATE_UNKNOWN,
        ),
        (
            ConfigurationStyle.MODERN,
            "{{ 'cleaning' }}",
            {
                "start": {"service": "script.vacuum_start"},
            },
            VacuumActivity.CLEANING,
        ),
        (
            ConfigurationStyle.TRIGGER,
            "{{ 'cleaning' }}",
            {
                "start": {"service": "script.vacuum_start"},
            },
            VacuumActivity.CLEANING,
        ),
        (
            ConfigurationStyle.MODERN,
            "{{ 'abc' }}",
            {
                "start": {"service": "script.vacuum_start"},
            },
            STATE_UNKNOWN,
        ),
        (
            ConfigurationStyle.TRIGGER,
            "{{ 'abc' }}",
            {
                "start": {"service": "script.vacuum_start"},
            },
            STATE_UNKNOWN,
        ),
        (
            ConfigurationStyle.MODERN,
            "{{ this_function_does_not_exist() }}",
            {
                "fan_speed": "{{ this_function_does_not_exist() }}",
                "start": {"service": "script.vacuum_start"},
            },
            STATE_UNAVAILABLE,
        ),
        (
            ConfigurationStyle.TRIGGER,
            "{{ this_function_does_not_exist() }}",
            {
                "fan_speed": "{{ this_function_does_not_exist() }}",
                "start": {"service": "script.vacuum_start"},
            },
            STATE_UNAVAILABLE,
        ),
    ],
)
@pytest.mark.usefixtures("setup_base_vacuum")
async def test_valid_configs(hass: HomeAssistant, count, parm1) -> None:
    """Test: configs."""

    # Ensure trigger entity templates are rendered
    hass.states.async_set(TEST_STATE_ENTITY_ID, None)
    await hass.async_block_till_done()

    assert len(hass.states.async_all("vacuum")) == count
    _verify(hass, parm1)


@pytest.mark.parametrize("count", [0])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("state_template", "extra_config"),
    [
        ("{{ 'on' }}", {}),
        (None, {"nothingburger": {"service": "script.vacuum_start"}}),
    ],
)
@pytest.mark.usefixtures("setup_base_vacuum")
async def test_invalid_configs(hass: HomeAssistant, count) -> None:
    """Test: configs."""
    assert len(hass.states.async_all("vacuum")) == count


@pytest.mark.parametrize(
    ("count", "state_template", "extra_config"),
    [
        (
            1,
            "{{ states('sensor.test_state') }}",
            {
                "fan_speeds": ["low", "medium", "high"],
            },
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "fan_speed"),
        (ConfigurationStyle.TRIGGER, "fan_speed"),
    ],
)
@pytest.mark.parametrize(
    ("attribute_template", "expected"),
    [
        ("{{ 'low' }}", "low"),
        ("{{ 'medium' }}", "medium"),
        ("{{ 'high' }}", "high"),
        ("{{ 'invalid' }}", None),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_fan_speed_template(hass: HomeAssistant, expected: str | None) -> None:
    """Test templates with values from other entities."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID)
    _verify(hass, STATE_UNKNOWN, expected)


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template", "extra_config", "attribute"),
    [
        (
            1,
            "{{ 'on' }}",
            "{% if states.sensor.test_state.state %}mdi:check{% endif %}",
            {},
            "icon",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "expected"),
    [
        (ConfigurationStyle.MODERN, ""),
        (ConfigurationStyle.TRIGGER, None),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_icon_template(hass: HomeAssistant, expected: int) -> None:
    """Test icon template."""
    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes.get("icon") == expected

    hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes["icon"] == "mdi:check"


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template", "extra_config", "attribute"),
    [
        (
            1,
            "{{ 'on' }}",
            "{% if states.sensor.test_state.state %}local/vacuum.png{% endif %}",
            {},
            "picture",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "expected"),
    [
        (ConfigurationStyle.MODERN, ""),
        (ConfigurationStyle.TRIGGER, None),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_picture_template(hass: HomeAssistant, expected: int) -> None:
    """Test picture template."""
    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes.get("entity_picture") == expected

    hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes["entity_picture"] == "local/vacuum.png"


@pytest.mark.parametrize("extra_config", [{}])
@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            None,
            "{{ is_state('binary_sensor.availability', 'on') }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "availability"),
        (ConfigurationStyle.TRIGGER, "availability"),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_available_template_with_entities(hass: HomeAssistant) -> None:
    """Test availability templates with values from other entities."""

    # When template returns true..
    hass.states.async_set(TEST_AVAILABILITY_ENTITY, STATE_ON)
    await hass.async_block_till_done()

    # Device State should not be unavailable
    assert hass.states.get(TEST_VACUUM.entity_id).state != STATE_UNAVAILABLE

    # When Availability template returns false
    hass.states.async_set(TEST_AVAILABILITY_ENTITY, STATE_OFF)
    await hass.async_block_till_done()

    # device state should be unavailable
    assert hass.states.get(TEST_VACUUM.entity_id).state == STATE_UNAVAILABLE


@pytest.mark.parametrize("extra_config", [{}])
@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            None,
            "{{ x - 12 }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "availability"),
        (ConfigurationStyle.TRIGGER, "availability"),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_invalid_availability_template_keeps_component_available(
    hass: HomeAssistant, caplog_setup_text, caplog: pytest.LogCaptureFixture
) -> None:
    """Test that an invalid availability keeps the device available."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID)
    assert hass.states.get(TEST_VACUUM.entity_id) != STATE_UNAVAILABLE
    err = "'x' is undefined"
    assert err in caplog_setup_text or err in caplog.text


@pytest.mark.parametrize("style", [ConfigurationStyle.MODERN])
@pytest.mark.parametrize(
    ("count", "state_template", "attributes"),
    [
        (
            1,
            "{{ 'cleaning' }}",
            {"test_attribute": "It {{ states.sensor.test_state.state }}."},
        )
    ],
)
@pytest.mark.usefixtures("setup_attributes_state_vacuum")
async def test_attribute_templates(hass: HomeAssistant) -> None:
    """Test attribute_templates template."""
    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes["test_attribute"] == "It ."

    hass.states.async_set(TEST_STATE_ENTITY_ID, "Works")
    await hass.async_block_till_done()
    await async_update_entity(hass, TEST_VACUUM.entity_id)
    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes["test_attribute"] == "It Works."


@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("count", "state_template", "attributes"),
    [
        (
            1,
            "{{ states('sensor.test_state') }}",
            {"test_attribute": "{{ this_function_does_not_exist() }}"},
        )
    ],
)
@pytest.mark.usefixtures("setup_attributes_state_vacuum")
async def test_invalid_attribute_template(
    hass: HomeAssistant, caplog_setup_text, caplog: pytest.LogCaptureFixture
) -> None:
    """Test that errors are logged if rendering template fails."""

    hass.states.async_set(TEST_STATE_ENTITY_ID, "Works")
    await hass.async_block_till_done()

    assert len(hass.states.async_all("vacuum")) == 1
    err = "'this_function_does_not_exist' is undefined"
    assert err in caplog_setup_text or err in caplog.text


@pytest.mark.parametrize("config", [TEMPLATE_VACUUM_ACTIONS])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
async def test_unique_id(
    hass: HomeAssistant, style: ConfigurationStyle, config: ConfigType
) -> None:
    """Test unique_id option only creates one vacuum per id."""
    await setup_and_test_unique_id(hass, TEST_VACUUM, style, config)


@pytest.mark.parametrize("config", [TEMPLATE_VACUUM_ACTIONS])
@pytest.mark.parametrize(
    "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_nested_unique_id(
    hass: HomeAssistant,
    style: ConfigurationStyle,
    config: ConfigType,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test a template unique_id propagates to vacuum unique_ids."""
    await setup_and_test_nested_unique_id(
        hass, TEST_VACUUM, style, entity_registry, config
    )


@pytest.mark.parametrize(
    ("count", "state_template", "extra_config"), [(1, None, START_ACTION)]
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_base_vacuum")
async def test_unused_services(hass: HomeAssistant) -> None:
    """Test calling unused services raises."""
    # Pause vacuum
    with pytest.raises(HomeAssistantError):
        await common.async_pause(hass, TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # Stop vacuum
    with pytest.raises(HomeAssistantError):
        await common.async_stop(hass, TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # Return vacuum to base
    with pytest.raises(HomeAssistantError):
        await common.async_return_to_base(hass, TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # Spot cleaning
    with pytest.raises(HomeAssistantError):
        await common.async_clean_spot(hass, TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # Locate vacuum
    with pytest.raises(HomeAssistantError):
        await common.async_locate(hass, TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # Set fan's speed
    with pytest.raises(HomeAssistantError):
        await common.async_set_fan_speed(hass, "medium", TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    _verify(hass, STATE_UNKNOWN, None)


@pytest.mark.parametrize(
    ("count", "state_template"),
    [(1, "{{ states('sensor.test_state') }}")],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    "action",
    [
        "start",
        "pause",
        "stop",
        "clean_spot",
        "return_to_base",
        "locate",
    ],
)
@pytest.mark.usefixtures("setup_state_vacuum")
async def test_state_services(
    hass: HomeAssistant, action: str, calls: list[ServiceCall]
) -> None:
    """Test locate service."""

    await hass.services.async_call(
        "vacuum",
        action,
        {"entity_id": TEST_VACUUM.entity_id},
        blocking=True,
    )
    await hass.async_block_till_done()

    # verify
    assert_action(TEST_VACUUM, calls, 1, action)


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template", "extra_config"),
    [
        (
            1,
            "{{ states('sensor.test_state') }}",
            "{{ states('sensor.test_attribute') }}",
            {
                "fan_speeds": ["low", "medium", "high"],
            },
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "fan_speed"),
        (ConfigurationStyle.TRIGGER, "fan_speed"),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_set_fan_speed(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
    """Test set valid fan speed."""

    # Set vacuum's fan speed to high
    await common.async_set_fan_speed(hass, "high", TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # verify
    assert_action(TEST_VACUUM, calls, 1, "set_fan_speed", fan_speed="high")

    # Set fan's speed to medium
    await common.async_set_fan_speed(hass, "medium", TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # verify
    assert_action(TEST_VACUUM, calls, 2, "set_fan_speed", fan_speed="medium")


@pytest.mark.parametrize(
    "extra_config",
    [
        {
            "fan_speeds": ["low", "medium", "high"],
        }
    ],
)
@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ states('sensor.test_state') }}",
            "{{ states('sensor.test_attribute') }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "fan_speed"),
        (ConfigurationStyle.TRIGGER, "fan_speed"),
    ],
)
@pytest.mark.usefixtures("setup_single_attribute_state_vacuum")
async def test_set_invalid_fan_speed(
    hass: HomeAssistant, calls: list[ServiceCall]
) -> None:
    """Test set invalid fan speed when fan has valid speed."""

    # Set vacuum's fan speed to high
    await common.async_set_fan_speed(hass, "high", TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # verify
    assert_action(TEST_VACUUM, calls, 1, "set_fan_speed", fan_speed="high")

    # Set vacuum's fan speed to 'invalid'
    await common.async_set_fan_speed(hass, "invalid", TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    # verify fan speed is unchanged
    assert_action(TEST_VACUUM, calls, 1, "set_fan_speed", fan_speed="high")


@pytest.mark.parametrize(("count", "vacuum_config"), [(1, {"start": []})])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("extra_config", "supported_features"),
    [
        (
            {
                "pause": [],
            },
            VacuumEntityFeature.PAUSE,
        ),
        (
            {
                "stop": [],
            },
            VacuumEntityFeature.STOP,
        ),
        (
            {
                "return_to_base": [],
            },
            VacuumEntityFeature.RETURN_HOME,
        ),
        (
            {
                "clean_spot": [],
            },
            VacuumEntityFeature.CLEAN_SPOT,
        ),
        (
            {
                "locate": [],
            },
            VacuumEntityFeature.LOCATE,
        ),
        (
            {
                "set_fan_speed": [],
            },
            VacuumEntityFeature.FAN_SPEED,
        ),
    ],
)
@pytest.mark.usefixtures("setup_test_vacuum_with_extra_config")
async def test_empty_action_config(
    hass: HomeAssistant,
    supported_features: VacuumEntityFeature,
) -> None:
    """Test configuration with empty script."""
    await common.async_start(hass, TEST_VACUUM.entity_id)
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.attributes["supported_features"] == (
        VacuumEntityFeature.STATE | VacuumEntityFeature.START | supported_features
    )


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {"name": TEST_VACUUM.object_id, "start": [], **TEMPLATE_VACUUM_ACTIONS},
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("service", "expected"),
    [
        (vacuum.SERVICE_START, VacuumActivity.CLEANING),
        (vacuum.SERVICE_PAUSE, VacuumActivity.PAUSED),
        (vacuum.SERVICE_STOP, VacuumActivity.IDLE),
        (vacuum.SERVICE_RETURN_TO_BASE, VacuumActivity.RETURNING),
        (vacuum.SERVICE_CLEAN_SPOT, VacuumActivity.CLEANING),
    ],
)
@pytest.mark.usefixtures("setup_vacuum")
async def test_assumed_optimistic(
    hass: HomeAssistant,
    service: str,
    expected: VacuumActivity,
    calls: list[ServiceCall],
) -> None:
    """Test assumed optimistic."""

    await hass.services.async_call(
        vacuum.DOMAIN,
        service,
        {"entity_id": TEST_VACUUM.entity_id},
        blocking=True,
    )
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.state == expected


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {
                "name": TEST_VACUUM.object_id,
                "state": "{{ states('sensor.test_state') }}",
                "start": [],
                **TEMPLATE_VACUUM_ACTIONS,
                "optimistic": True,
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("service", "expected"),
    [
        (vacuum.SERVICE_START, VacuumActivity.CLEANING),
        (vacuum.SERVICE_PAUSE, VacuumActivity.PAUSED),
        (vacuum.SERVICE_STOP, VacuumActivity.IDLE),
        (vacuum.SERVICE_RETURN_TO_BASE, VacuumActivity.RETURNING),
        (vacuum.SERVICE_CLEAN_SPOT, VacuumActivity.CLEANING),
    ],
)
@pytest.mark.usefixtures("setup_vacuum")
async def test_optimistic_option(
    hass: HomeAssistant,
    service: str,
    expected: VacuumActivity,
    calls: list[ServiceCall],
) -> None:
    """Test optimistic yaml option."""
    hass.states.async_set(TEST_STATE_ENTITY_ID, VacuumActivity.DOCKED)
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.state == VacuumActivity.DOCKED

    await hass.services.async_call(
        vacuum.DOMAIN,
        service,
        {"entity_id": TEST_VACUUM.entity_id},
        blocking=True,
    )
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.state == expected

    hass.states.async_set(TEST_STATE_ENTITY_ID, VacuumActivity.RETURNING)
    await hass.async_block_till_done()

    hass.states.async_set(TEST_STATE_ENTITY_ID, VacuumActivity.DOCKED)
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.state == VacuumActivity.DOCKED


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {
                "name": TEST_VACUUM.object_id,
                "state": "{{ states('sensor.test_state') }}",
                "start": [],
                **TEMPLATE_VACUUM_ACTIONS,
                "optimistic": False,
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    "service",
    [
        vacuum.SERVICE_START,
        vacuum.SERVICE_PAUSE,
        vacuum.SERVICE_STOP,
        vacuum.SERVICE_RETURN_TO_BASE,
        vacuum.SERVICE_CLEAN_SPOT,
    ],
)
@pytest.mark.usefixtures("setup_vacuum")
async def test_not_optimistic(
    hass: HomeAssistant,
    service: str,
    calls: list[ServiceCall],
) -> None:
    """Test optimistic yaml option set to false."""
    await hass.services.async_call(
        vacuum.DOMAIN,
        service,
        {"entity_id": TEST_VACUUM.entity_id},
        blocking=True,
    )
    await hass.async_block_till_done()

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state.state == STATE_UNKNOWN


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {
                "unique_id": TEST_VACUUM.entity_id,
                "start": [],
                **CLEAN_SEGMENTS_ACTION,
                "segments": (
                    "{{ [{'id': '1', 'name': 'Livingroom'},"
                    " {'id': '2', 'name': 'Kitchen'}] }}"
                ),
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_vacuum")
async def test_clean_area(
    hass: HomeAssistant,
    calls: list[ServiceCall],
    entity_registry: er.EntityRegistry,
) -> None:
    """Test clean area passes segment IDs to action."""
    entity_registry.async_update_entity_options(
        TEST_VACUUM.entity_id,
        vacuum.DOMAIN,
        {
            "area_mapping": {"area_1": ["1", "2"]},
            "last_seen_segments": [
                {"id": "1", "name": "Livingroom"},
                {"id": "2", "name": "Kitchen"},
            ],
        },
    )

    await common.async_clean_area(hass, ["area_1"], TEST_VACUUM.entity_id)
    await hass.async_block_till_done()
    assert_action(TEST_VACUUM, calls, 1, "clean_segments", segment_ids=["1", "2"])

    state = hass.states.get(TEST_VACUUM.entity_id)
    assert state is not None
    assert state.attributes["supported_features"] & VacuumEntityFeature.CLEAN_AREA


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {
                "start": [],
                **CLEAN_SEGMENTS_ACTION,
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("extra_config", "expected_segments"),
    [
        (
            {
                "unique_id": TEST_VACUUM.entity_id,
                "segments": "{{ ["
                "{'id': '1', 'name': 'Kitchen'}, "
                "{'id': '2', 'name': 'Bedroom', 'group': 'Upstairs'}"
                "] }}",
            },
            [
                Segment(id="1", name="Kitchen"),
                Segment(id="2", name="Bedroom", group="Upstairs"),
            ],
        ),
    ],
)
@pytest.mark.usefixtures("setup_test_vacuum_with_extra_config")
async def test_get_segments(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    expected_segments: list[Segment],
) -> None:
    """Test get_segments returns segments from template."""

    await async_trigger(hass, TEST_STATE_ENTITY_ID, VacuumActivity.DOCKED)

    client = await hass_ws_client(hass)
    await client.send_json_auto_id(
        {"type": "vacuum/get_segments", "entity_id": TEST_VACUUM.entity_id}
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] == {
        "segments": [asdict(segment) for segment in expected_segments]
    }


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {
                "start": [],
                **CLEAN_SEGMENTS_ACTION,
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("extra_config", "err_msg"),
    [
        (
            {
                "unique_id": TEST_VACUUM.entity_id,
                "segments": "{{ [ {'id': '1'} ] }}",
            },
            "expected dictionary with keys id, name and optional"
            " group and string values",
        ),
        (
            {
                "unique_id": TEST_VACUUM.entity_id,
                "segments": "{{ [ {'name': 'kitchen'} ] }}",
            },
            "expected dictionary with keys id, name and optional"
            " group and string values",
        ),
        (
            {
                "unique_id": TEST_VACUUM.entity_id,
                "segments": "{{ [ {} ] }}",
            },
            "expected dictionary with keys id, name and optional"
            " group and string values",
        ),
        (
            {
                "unique_id": TEST_VACUUM.entity_id,
                "segments": (
                    "{{ [ {'id': '1', 'name': 'Kitchen', 'extra_key': 'value'} ] }}"
                ),
            },
            "expected dictionary with keys id, name and optional"
            " group and string values",
        ),
        (
            {"unique_id": TEST_VACUUM.entity_id, "segments": "{{ [[]] }}"},
            "expected dictionary with keys id, name and optional"
            " group and string values",
        ),
        (
            {
                "unique_id": TEST_VACUUM.entity_id,
                "segments": "{{ [ {'id': '1', 'name': 'Kitchen'}, [] ] }}",
            },
            "expected dictionary with keys id, name and optional"
            " group and string values",
        ),
    ],
)
@pytest.mark.usefixtures("setup_test_vacuum_with_extra_config")
async def test_invalid_segments(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    caplog_setup_text,
    caplog: pytest.LogCaptureFixture,
    err_msg: str,
) -> None:
    """Test that errors are logged if parsing segment template fails."""
    hass.states.async_set(TEST_STATE_ENTITY_ID, "Works")
    await hass.async_block_till_done()

    assert err_msg in caplog_setup_text or err_msg in caplog.text

    client = await hass_ws_client(hass)
    await client.send_json_auto_id(
        {"type": "vacuum/get_segments", "entity_id": TEST_VACUUM.entity_id}
    )
    msg = await client.receive_json()
    assert msg["result"] == {"segments": []}


@pytest.mark.parametrize(
    ("count", "vacuum_config"),
    [
        (
            1,
            {
                "unique_id": TEST_VACUUM.entity_id,
                "start": [],
                **CLEAN_SEGMENTS_ACTION,
                "segments": (
                    "{{ [ {'id': '1', 'name': 'Kitchen'},"
                    " {'id': '2', 'name':"
                    " states('sensor.test_attribute')}] }}"
                ),
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_vacuum")
async def test_raise_segments_changed_issue(
    hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
    """Test that issue is raised on segments change."""
    hass.states.async_set(TEST_ATTRIBUTE_ENTITY_ID, "Bedroom")
    await hass.async_block_till_done()

    entity_registry.async_update_entity_options(
        TEST_VACUUM.entity_id,
        vacuum.DOMAIN,
        {
            "last_seen_segments": [
                {"id": "1", "name": "Kitchen"},
                {"id": "2", "name": "Bedroom"},
            ],
        },
    )
    hass.states.async_set(TEST_ATTRIBUTE_ENTITY_ID, "Bathroom")
    await hass.async_block_till_done()

    issue_registry = ir.async_get(hass)  # pylint: disable=home-assistant-tests-registry-fixtures
    assert len(issue_registry.issues) != 0


@pytest.mark.parametrize(
    ("vacuum_config", "err_msg"),
    [
        (
            {**START_ACTION},
            f"Options `{CONF_SEGMENTS}` and `{CONF_CLEAN_SEGMENTS}` must both exist",
        ),
    ],
)
@pytest.mark.parametrize(
    ("count", "extra_config"),
    [
        (
            0,
            {
                "segments": "{{ [{'id': '1', 'name': 'Kitchen'}] }}",
            },
        ),
        (
            0,
            {**CLEAN_SEGMENTS_ACTION},
        ),
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_test_vacuum_with_extra_config")
async def test_segments_part_config(
    hass: HomeAssistant,
    caplog_setup_text,
    caplog: pytest.LogCaptureFixture,
    count: int,
    err_msg: str,
) -> None:
    """Test creating vacuum with segments, missing required options."""
    assert len(hass.states.async_all(vacuum.DOMAIN)) == count
    assert err_msg in caplog_setup_text or err_msg in caplog.text


@pytest.mark.parametrize(
    ("vacuum_config"),
    [
        {
            **START_ACTION,
            **CLEAN_SEGMENTS_ACTION,
            "segments": "{{ [{'id': '1', 'name': 'Kitchen'}] }}",
        },
    ],
)
@pytest.mark.parametrize(
    ("count", "extra_config", "err_msg"),
    [
        (
            0,
            {},
            f'key "{CONF_SEGMENTS}" requires key "{CONF_UNIQUE_ID}" to exist',
        ),
        (1, {"unique_id": TEST_VACUUM.entity_id}, ""),
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_test_vacuum_with_extra_config")
async def test_segments_unique_id(
    hass: HomeAssistant,
    caplog_setup_text,
    caplog: pytest.LogCaptureFixture,
    count: int,
    err_msg: str,
) -> None:
    """Test creating vacuum with segments, missing required options."""
    assert len(hass.states.async_all(vacuum.DOMAIN)) == count
    assert err_msg in caplog_setup_text or err_msg in caplog.text


async def test_setup_config_entry(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
) -> None:
    """Tests creating a vacuum from a config entry."""

    hass.states.async_set(
        "sensor.test_sensor",
        "docked",
        {},
    )

    template_config_entry = MockConfigEntry(
        data={},
        domain=template.DOMAIN,
        options={
            "name": "My template",
            "state": "{{ states('sensor.test_sensor') }}",
            "start": [],
            "template_type": vacuum.DOMAIN,
        },
        title="My template",
    )
    template_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(template_config_entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get("vacuum.my_template")
    assert state is not None
    assert state == snapshot


async def test_flow_preview(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test the config flow preview."""

    state = await async_get_flow_preview_state(
        hass,
        hass_ws_client,
        vacuum.DOMAIN,
        {
            "name": "My template",
            "state": "{{ 'cleaning' }}",
            "start": [],
        },
    )

    assert state["state"] == VacuumActivity.CLEANING


@pytest.mark.parametrize(
    "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
@pytest.mark.parametrize(
    (
        "saved_state",
        "saved_extra_data",
        "initial_state",
        "initial_attributes",
    ),
    [
        (
            "some_value",
            {
                "activity": VacuumActivity.DOCKED,
                "fan_speed": "high",
            },
            VacuumActivity.DOCKED,
            {
                "fan_speed": "high",
            },
        ),
        (
            "some_value",
            {
                "activity": "do",
            },
            STATE_UNKNOWN,
            {
                "fan_speed": None,
            },
        ),
        (
            "some_value",
            {
                "activity": VacuumActivity.DOCKED,
            },
            STATE_UNKNOWN,
            {
                "fan_speed": None,
            },
        ),
        (
            "some_value",
            {
                "fan_speed": "high",
            },
            STATE_UNKNOWN,
            {
                "fan_speed": None,
            },
        ),
        (
            STATE_UNAVAILABLE,
            {
                "activity": VacuumActivity.DOCKED,
                "fan_speed": "high",
            },
            STATE_UNKNOWN,
            {
                "fan_speed": None,
            },
        ),
        (
            STATE_UNKNOWN,
            {
                "activity": VacuumActivity.DOCKED,
                "fan_speed": "high",
            },
            STATE_UNKNOWN,
            {
                "fan_speed": None,
            },
        ),
    ],
)
async def test_restore_state(
    hass: HomeAssistant,
    style: ConfigurationStyle,
    saved_state: str,
    saved_extra_data: dict | None,
    initial_state: str,
    initial_attributes: ConfigType,
) -> None:
    """Test restoring trigger template vacuum."""

    setup_mock_template_entity_restore_state(
        hass,
        TEST_VACUUM,
        saved_state,
        saved_extra_data=saved_extra_data,
    )

    await setup_restore_template_entity(
        hass,
        TEST_VACUUM,
        style,
        {
            "state": "{{ state_attr('sensor.test_state', 'activity') }}",
            "start": [],
            "fan_speed": "{{ state_attr('sensor.test_state', 'fan_speed') }}",
            "fan_speeds": ["low", "high"],
            "set_fan_speed": [],
        },
        "state_attr('sensor.test_state', 'activity') == 'cleaning'",
    )

    assert_state_and_attributes(
        hass,
        TEST_VACUUM,
        initial_state,
        initial_attributes,
    )

    await async_trigger(
        hass,
        "sensor.test_state",
        "anything",
        {"activity": VacuumActivity.CLEANING, "fan_speed": "low"},
    )

    assert_state_and_attributes(
        hass,
        TEST_VACUUM,
        VacuumActivity.CLEANING,
        {
            "fan_speed": "low",
        },
    )


@pytest.mark.parametrize(
    "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_saving_state(
    hass: HomeAssistant,
    style: ConfigurationStyle,
    hass_storage: dict[str, Any],
) -> None:
    """Test restore saved state."""

    await setup_entity(
        hass,
        TEST_VACUUM,
        style,
        1,
        config={
            "state": "{{ state_attr('sensor.test_state', 'activity') }}",
            "start": [],
            "fan_speed": "{{ state_attr('sensor.test_state', 'fan_speed') }}",
            "fan_speeds": ["low", "high"],
            "set_fan_speed": [],
        },
    )

    await async_trigger(
        hass,
        TEST_STATE_ENTITY_ID,
        "anything",
        {"activity": VacuumActivity.DOCKED, "fan_speed": "high"},
    )

    assert_state_and_attributes(
        hass,
        TEST_VACUUM,
        VacuumActivity.DOCKED,
        {
            "fan_speed": "high",
        },
    )

    await async_mock_restore_state_shutdown_restart(hass)

    assert len(hass_storage[RESTORE_STATE_KEY]["data"]) == 1
    state = hass_storage[RESTORE_STATE_KEY]["data"][0]["state"]
    assert state["entity_id"] == TEST_VACUUM.entity_id

    extra_data = hass_storage[RESTORE_STATE_KEY]["data"][0]["extra_data"]
    assert extra_data == {
        "activity": "docked",
        "fan_speed": "high",
    }
