"""Test the Switch config flow."""

from typing import Any
from unittest.mock import patch

import pytest
from pytest_unordered import unordered

from homeassistant import config_entries
from homeassistant.components.template import DOMAIN, async_setup_entry
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import device_registry as dr

from tests.common import MockConfigEntry, get_schema_suggested_value
from tests.typing import WebSocketGenerator

SWITCH_BEFORE_OPTIONS = {
    "name": "test_template_switch",
    "template_type": "switch",
    "turn_off": [{"event": "test_template_switch", "event_data": {"event": "off"}}],
    "turn_on": [{"event": "test_template_switch", "event_data": {"event": "on"}}],
    "value_template": "{{ now().minute % 2 == 0 }}",
}


SWITCH_AFTER_OPTIONS = {
    "name": "test_template_switch",
    "template_type": "switch",
    "turn_off": [{"event": "test_template_switch", "event_data": {"event": "off"}}],
    "turn_on": [{"event": "test_template_switch", "event_data": {"event": "on"}}],
    "state": "{{ now().minute % 2 == 0 }}",
    "value_template": "{{ now().minute % 2 == 0 }}",
}

SENSOR_OPTIONS = {
    "name": "test_template_sensor",
    "template_type": "sensor",
    "state": "{{ 'a' if now().minute % 2 == 0 else 'b' }}",
}

BINARY_SENSOR_OPTIONS = {
    "name": "test_template_sensor",
    "template_type": "binary_sensor",
    "state": "{{ now().minute % 2 == 0 else }}",
}


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "template_state",
        "input_states",
        "input_attributes",
        "extra_input",
        "extra_options",
        "extra_attrs",
    ),
    [
        (
            "alarm_control_panel",
            {"value_template": "{{ states('alarm_control_panel.one') }}"},
            "armed_away",
            {"one": "armed_away", "two": "disarmed"},
            {},
            {},
            {"code_arm_required": True, "code_format": "number"},
            {},
        ),
        (
            "binary_sensor",
            {
                "state": (
                    "{{ states('binary_sensor.one') == 'on'"
                    " or states('binary_sensor.two') == 'on' }}"
                )
            },
            "on",
            {"one": "on", "two": "off"},
            {},
            {},
            {},
            {},
        ),
        (
            "sensor",
            {
                "state": (
                    "{{ float(states('sensor.one')) + float(states('sensor.two')) }}"
                )
            },
            "50.0",
            {"one": "30.0", "two": "20.0"},
            {},
            {},
            {},
            {},
        ),
        (
            "button",
            {},
            "unknown",
            {"one": "30.0", "two": "20.0"},
            {},
            {
                "device_class": "restart",
                "press": [
                    {
                        "action": "input_boolean.toggle",
                        "target": {"entity_id": "input_boolean.test"},
                        "data": {},
                    }
                ],
            },
            {
                "device_class": "restart",
                "press": [
                    {
                        "action": "input_boolean.toggle",
                        "target": {"entity_id": "input_boolean.test"},
                        "data": {},
                    }
                ],
            },
            {},
        ),
        (
            "cover",
            {"state": "{{ states('cover.one') }}"},
            "open",
            {"one": "open", "two": "closed"},
            {},
            {
                "device_class": "garage",
                "set_cover_position": [
                    {
                        "action": "input_number.set_value",
                        "target": {"entity_id": "input_number.test"},
                        "data": {"position": "{{ position }}"},
                    }
                ],
            },
            {
                "device_class": "garage",
                "set_cover_position": [
                    {
                        "action": "input_number.set_value",
                        "target": {"entity_id": "input_number.test"},
                        "data": {"position": "{{ position }}"},
                    }
                ],
            },
            {},
        ),
        (
            "event",
            {"event_type": "{{ states('event.one') }}"},
            "2024-07-09T00:00:00.000+00:00",
            {"one": "single", "two": "double"},
            {},
            {"event_types": "{{ ['single', 'double'] }}"},
            {"event_types": "{{ ['single', 'double'] }}"},
            {},
        ),
        (
            "fan",
            {"state": "{{ states('fan.one') }}"},
            "on",
            {"one": "on", "two": "off"},
            {},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
            {},
        ),
        (
            "image",
            {"url": "{{ states('sensor.one') }}"},
            "2024-07-09T00:00:00+00:00",
            {"one": "http://www.test.com", "two": ""},
            {},
            {"verify_ssl": True},
            {"verify_ssl": True},
            {},
        ),
        (
            "light",
            {"state": "{{ states('light.one') }}"},
            "on",
            {"one": "on", "two": "off"},
            {},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
            {},
        ),
        (
            "lock",
            {"state": "{{ states('lock.one') }}"},
            "locked",
            {"one": "locked", "two": "unlocked"},
            {},
            {"lock": [], "unlock": []},
            {"lock": [], "unlock": []},
            {},
        ),
        (
            "number",
            {"state": "{{ states('number.one') }}"},
            "30.0",
            {"one": "30.0", "two": "20.0"},
            {},
            {
                "min": "0",
                "max": "100",
                "step": "0.1",
                "unit_of_measurement": "cm",
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "unit_of_measurement": "cm",
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            {},
        ),
        (
            "select",
            {"state": "{{ states('select.one') }}"},
            "on",
            {"one": "on", "two": "off"},
            {},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
            {},
        ),
        (
            "switch",
            {"value_template": "{{ states('switch.one') }}"},
            "on",
            {"one": "on", "two": "off"},
            {},
            {},
            {},
            {},
        ),
        (
            "update",
            {"installed_version": "{{ states('update.one') }}"},
            "off",
            {"one": "2.0", "two": "1.0"},
            {},
            {"latest_version": "{{ '2.0' }}"},
            {"latest_version": "{{ '2.0' }}"},
            {},
        ),
        (
            "vacuum",
            {"state": "{{ states('vacuum.one') }}"},
            "docked",
            {"one": "docked", "two": "cleaning"},
            {},
            {"start": []},
            {"start": []},
            {},
        ),
        (
            "weather",
            {"condition": "{{ states('weather.one') }}"},
            "sunny",
            {"one": "sunny", "two": "cloudy"},
            {},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
            {},
        ),
    ],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
async def test_config_flow(
    hass: HomeAssistant,
    template_type: str,
    state_template: dict[str, Any],
    template_state: str,
    input_states: dict[str, Any],
    input_attributes: dict[str, Any],
    extra_input: dict[str, Any],
    extra_options: dict[str, Any],
    extra_attrs: dict[str, Any],
) -> None:
    """Test the config flow."""
    input_entities = ["one", "two"]
    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}",
            input_states[input_entity],
            input_attributes.get(input_entity, {}),
        )

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type

    availability = {"advanced_options": {"availability": "{{ True }}"}}

    with patch(
        "homeassistant.components.template.async_setup_entry", wraps=async_setup_entry
    ) as mock_setup_entry:
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"name": "My template", **state_template, **extra_input, **availability},
        )
        await hass.async_block_till_done()

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == "My template"
    assert result["data"] == {}
    assert result["options"] == {
        "name": "My template",
        "template_type": template_type,
        **state_template,
        **extra_options,
        **availability,
    }
    assert len(mock_setup_entry.mock_calls) == 1

    config_entry = hass.config_entries.async_entries(DOMAIN)[0]
    assert config_entry.data == {}
    assert config_entry.options == {
        "name": "My template",
        "template_type": template_type,
        **state_template,
        **extra_options,
        **availability,
    }

    state = hass.states.get(f"{template_type}.my_template")
    assert state.state == template_state
    for key, value in extra_attrs.items():
        assert state.attributes[key] == value


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "extra_input",
        "extra_options",
    ),
    [
        (
            "sensor",
            {"state": "{{ 15 }}"},
            {},
            {},
        ),
        (
            "binary_sensor",
            {"state": "{{ false }}"},
            {},
            {},
        ),
        (
            "switch",
            {"value_template": "{{ false }}"},
            {},
            {},
        ),
        (
            "button",
            {},
            {},
            {},
        ),
        (
            "cover",
            {"state": "{{ 'open' }}"},
            {"set_cover_position": []},
            {"set_cover_position": []},
        ),
        (
            "event",
            {"event_type": "{{ 'single' }}"},
            {"event_types": "{{ ['single', 'double'] }}"},
            {"event_types": "{{ ['single', 'double'] }}"},
        ),
        (
            "fan",
            {"state": "{{ states('fan.one') }}"},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
        ),
        (
            "image",
            {
                "url": "{{ states('sensor.one') }}",
            },
            {"verify_ssl": True},
            {"verify_ssl": True},
        ),
        (
            "light",
            {"state": "{{ states('light.one') }}"},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
        ),
        (
            "lock",
            {"state": "{{ states('lock.one') }}"},
            {"lock": [], "unlock": []},
            {"lock": [], "unlock": []},
        ),
        (
            "number",
            {"state": "{{ states('number.one') }}"},
            {
                "min": "0",
                "max": "100",
                "step": "0.1",
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
        ),
        (
            "alarm_control_panel",
            {"value_template": "{{ states('alarm_control_panel.one') }}"},
            {"code_arm_required": True, "code_format": "number"},
            {"code_arm_required": True, "code_format": "number"},
        ),
        (
            "select",
            {"state": "{{ states('select.one') }}"},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
        ),
        (
            "update",
            {"installed_version": "{{ states('update.one') }}"},
            {"latest_version": "{{ '2.0' }}"},
            {"latest_version": "{{ '2.0' }}"},
        ),
        (
            "vacuum",
            {"state": "{{ states('vacuum.one') }}"},
            {"start": []},
            {"start": []},
        ),
        (
            "weather",
            {"condition": "{{ states('weather.one') }}"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
        ),
    ],
)
async def test_config_flow_device(
    hass: HomeAssistant,
    template_type: str,
    state_template: dict[str, Any],
    extra_input: dict[str, Any],
    extra_options: dict[str, Any],
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test remove the device registry configuration entry when the device changes."""

    # Configure a device registry
    entry_device = MockConfigEntry()
    entry_device.add_to_hass(hass)
    device = device_registry.async_get_or_create(
        config_entry_id=entry_device.entry_id,
        identifiers={("test", "identifier_test1")},
        connections={("mac", "20:31:32:33:34:01")},
    )
    await hass.async_block_till_done()

    device_id = device.id
    assert device_id is not None

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type

    with patch(
        "homeassistant.components.template.async_setup_entry", wraps=async_setup_entry
    ) as mock_setup_entry:
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {
                "name": "My template",
                "device_id": device_id,
                **state_template,
                **extra_input,
            },
        )
        await hass.async_block_till_done()

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == "My template"
    assert result["data"] == {}
    assert result["options"] == {
        "name": "My template",
        "template_type": template_type,
        "device_id": device_id,
        **state_template,
        **extra_options,
    }
    assert len(mock_setup_entry.mock_calls) == 1

    config_entry = hass.config_entries.async_entries(DOMAIN)[0]
    assert config_entry.data == {}
    assert config_entry.options == {
        "name": "My template",
        "template_type": template_type,
        "device_id": device_id,
        **state_template,
        **extra_options,
    }


@pytest.mark.parametrize(
    (
        "template_type",
        "old_state_template",
        "new_state_template",
        "template_state",
        "input_states",
        "extra_options",
        "options_options",
        "key_template",
        "suggested_device_class",
    ),
    [
        (
            "binary_sensor",
            {
                "state": (
                    "{{ states('binary_sensor.one') == 'on'"
                    " or states('binary_sensor.two') == 'on' }}"
                )
            },
            {
                "state": (
                    "{{ states('binary_sensor.one') == 'on'"
                    " and states('binary_sensor.two') == 'on' }}"
                )
            },
            ["on", "off"],
            {"one": "on", "two": "off"},
            {"device_class": "motion"},
            {"device_class": "window"},
            "state",
            "motion",
        ),
        (
            "sensor",
            {
                "state": (
                    "{{ float(states('sensor.one')) + float(states('sensor.two')) }}"
                )
            },
            {
                "state": (
                    "{{ float(states('sensor.one')) - float(states('sensor.two')) }}"
                )
            },
            ["50.0", "10.0"],
            {"one": "30.0", "two": "20.0"},
            {},
            {},
            "state",
            None,
        ),
        (
            "button",
            {},
            {},
            ["unknown", "unknown"],
            {"one": "30.0", "two": "20.0"},
            {
                "device_class": "restart",
                "press": [
                    {
                        "action": "input_boolean.toggle",
                        "target": {"entity_id": "input_boolean.test"},
                        "data": {},
                    }
                ],
            },
            {
                "press": [
                    {
                        "action": "input_boolean.toggle",
                        "target": {"entity_id": "input_boolean.test"},
                        "data": {},
                    }
                ],
            },
            "state",
            None,
        ),
        (
            "cover",
            {"state": "{{ states('cover.one') }}"},
            {"state": "{{ states('cover.two') }}"},
            ["open", "closed"],
            {"one": "open", "two": "closed"},
            {"set_cover_position": []},
            {"set_cover_position": []},
            "state",
            None,
        ),
        (
            "event",
            {"event_type": "{{ states('event.one') }}"},
            {"event_type": "{{ states('event.two') }}"},
            ["2024-07-09T00:00:00.000+00:00", "2024-07-09T00:00:00.000+00:00"],
            {"one": "single", "two": "double"},
            {"event_types": "{{ ['single', 'double'] }}"},
            {"event_types": "{{ ['single', 'double'] }}"},
            "event_type",
            None,
        ),
        (
            "fan",
            {"state": "{{ states('fan.one') }}"},
            {"state": "{{ states('fan.two') }}"},
            ["on", "off"],
            {"one": "on", "two": "off"},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
            "state",
            None,
        ),
        (
            "image",
            {
                "url": "{{ states('sensor.one') }}",
            },
            {
                "url": "{{ states('sensor.two') }}",
            },
            ["2024-07-09T00:00:00+00:00", "2024-07-09T00:00:00+00:00"],
            {"one": "http://www.test.com", "two": "http://www.test2.com"},
            {"verify_ssl": True},
            {
                "url": "{{ states('sensor.two') }}",
                "verify_ssl": True,
            },
            "url",
            None,
        ),
        (
            "light",
            {"state": "{{ states('light.one') }}"},
            {"state": "{{ states('light.two') }}"},
            ["on", "off"],
            {"one": "on", "two": "off"},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
            "state",
            None,
        ),
        (
            "lock",
            {"state": "{{ states('lock.one') }}"},
            {"state": "{{ states('lock.two') }}"},
            ["locked", "unlocked"],
            {"one": "locked", "two": "unlocked"},
            {"lock": [], "unlock": []},
            {"lock": [], "unlock": []},
            "state",
            None,
        ),
        (
            "number",
            {"state": "{{ states('number.one') }}"},
            {"state": "{{ states('number.two') }}"},
            ["30.0", "20.0"],
            {"one": "30.0", "two": "20.0"},
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "device_class": "distance",
                "unit_of_measurement": "cm",
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "device_class": "current",
                "unit_of_measurement": "mA",
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            "state",
            "distance",
        ),
        (
            "alarm_control_panel",
            {"value_template": "{{ states('alarm_control_panel.one') }}"},
            {"value_template": "{{ states('alarm_control_panel.two') }}"},
            ["armed_away", "disarmed"],
            {"one": "armed_away", "two": "disarmed"},
            {"code_arm_required": True, "code_format": "number"},
            {"code_arm_required": True, "code_format": "number"},
            "value_template",
            None,
        ),
        (
            "select",
            {"state": "{{ states('select.one') }}"},
            {"state": "{{ states('select.two') }}"},
            ["on", "off"],
            {"one": "on", "two": "off"},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
            "state",
            None,
        ),
        (
            "switch",
            {"value_template": "{{ states('switch.one') }}"},
            {"value_template": "{{ states('switch.two') }}"},
            ["on", "off"],
            {"one": "on", "two": "off"},
            {},
            {},
            "value_template",
            None,
        ),
        (
            "update",
            {"installed_version": "{{ states('update.one') }}"},
            {"installed_version": "{{ states('update.two') }}"},
            ["off", "on"],
            {"one": "2.0", "two": "1.0"},
            {"latest_version": "{{ '2.0' }}"},
            {"latest_version": "{{ '2.0' }}"},
            "installed_version",
            None,
        ),
        (
            "vacuum",
            {"state": "{{ states('vacuum.one') }}"},
            {"state": "{{ states('vacuum.two') }}"},
            ["docked", "cleaning"],
            {"one": "docked", "two": "cleaning"},
            {"start": []},
            {"start": []},
            "state",
            None,
        ),
        (
            "weather",
            {"condition": "{{ states('weather.one') }}"},
            {"condition": "{{ states('weather.two') }}"},
            ["sunny", "cloudy"],
            {"one": "sunny", "two": "cloudy"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
            "condition",
            None,
        ),
    ],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
async def test_options(
    hass: HomeAssistant,
    template_type: str,
    old_state_template: dict[str, Any],
    new_state_template: dict[str, Any],
    template_state: list[str],
    input_states: dict[str, Any],
    extra_options: dict[str, Any],
    options_options: dict[str, Any],
    key_template: str,
    suggested_device_class: str | None,
) -> None:
    """Test reconfiguring."""
    input_entities = ["one", "two"]

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[input_entity], {}
        )

    template_config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            "name": "My template",
            "template_type": template_type,
            **old_state_template,
            **extra_options,
        },
        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(f"{template_type}.my_template")
    assert state.state == template_state[0]

    config_entry = hass.config_entries.async_entries(DOMAIN)[0]

    result = await hass.config_entries.options.async_init(config_entry.entry_id)
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert get_schema_suggested_value(
        result["data_schema"].schema, key_template
    ) == old_state_template.get(key_template)
    assert "name" not in result["data_schema"].schema
    assert (
        get_schema_suggested_value(result["data_schema"].schema, "device_class")
        == suggested_device_class
    )

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            **new_state_template,
            **options_options,
        },
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["data"] == {
        "name": "My template",
        "template_type": template_type,
        **new_state_template,
        **extra_options,
        **options_options,
    }
    assert config_entry.data == {}
    assert config_entry.options == {
        "name": "My template",
        "template_type": template_type,
        **new_state_template,
        **extra_options,
        **options_options,
    }
    assert config_entry.title == "My template"

    # Check config entry is reloaded with new options
    await hass.async_block_till_done()
    state = hass.states.get(f"{template_type}.my_template")
    assert state.state == template_state[1]

    # Check we don't get suggestions from another entry
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type

    assert get_schema_suggested_value(result["data_schema"].schema, "name") is None
    assert (
        get_schema_suggested_value(result["data_schema"].schema, key_template) is None
    )


@pytest.mark.parametrize(
    (
        "template_type",
        "old_state_template",
        "new_state_template",
        "input_states",
        "extra_options",
        "suggested_device_class",
    ),
    [
        (
            "binary_sensor",
            {
                "state": (
                    "{{ states('binary_sensor.one') == 'on'"
                    " or states('binary_sensor.two') == 'on' }}"
                )
            },
            {
                "state": (
                    "{{ states('binary_sensor.one') == 'on'"
                    " and states('binary_sensor.two') == 'on' }}"
                )
            },
            {"one": "on", "two": "off"},
            {"device_class": "motion"},
            "motion",
        ),
        (
            "number",
            {"state": "{{ states('number.one') }}"},
            {"state": "{{ states('number.two') }}"},
            {"one": "30.0", "two": "20.0"},
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "device_class": "distance",
                "unit_of_measurement": "cm",
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            "distance",
        ),
    ],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
async def test_options_remove_device_class(
    hass: HomeAssistant,
    template_type: str,
    old_state_template: dict[str, Any],
    new_state_template: dict[str, Any],
    input_states: dict[str, Any],
    extra_options: dict[str, Any],
    suggested_device_class: str | None,
) -> None:
    """Test removing the device class in options."""

    input_entities = ["one", "two"]

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[input_entity], {}
        )

    config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            "name": "My template",
            "template_type": template_type,
            **old_state_template,
            **extra_options,
        },
        title="My template",
    )
    config_entry.add_to_hass(hass)

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

    result = await hass.config_entries.options.async_init(config_entry.entry_id)
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert (
        get_schema_suggested_value(result["data_schema"].schema, "device_class")
        == suggested_device_class
    )

    extra_options.pop("device_class")
    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            **new_state_template,
            **extra_options,
        },
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["data"] == {
        "name": "My template",
        "template_type": template_type,
        **new_state_template,
        **extra_options,
    }
    assert config_entry.options == {
        "name": "My template",
        "template_type": template_type,
        **new_state_template,
        **extra_options,
    }
    assert "device_class" not in config_entry.options


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "extra_user_input",
        "input_states",
        "template_states",
        "extra_attributes",
        "listeners",
    ),
    [
        (
            "binary_sensor",
            (
                "{{ states.binary_sensor.one.state == 'on'"
                " or states.binary_sensor.two.state == 'on' }}"
            ),
            {},
            {"one": "on", "two": "off"},
            ["off", "on"],
            [{}, {}],
            [["one", "two"], ["one"]],
        ),
        (
            "sensor",
            "{{ float(states('sensor.one'), 0.0) + float(states('sensor.two'), 0.0) }}",
            {},
            {"one": "30.0", "two": "20.0"},
            ["0.0", "30.0", "50.0"],
            [{}, {}],
            [["one", "two"], ["one", "two"]],
        ),
    ],
)
async def test_config_flow_preview(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    state_template: str,
    extra_user_input: dict[str, Any],
    input_states: dict[str, Any],
    template_states: str,
    extra_attributes: list[dict[str, Any]],
    listeners: list[list[str]],
) -> None:
    """Test the config flow preview."""
    client = await hass_ws_client(hass)

    hass.states.async_set("binary_sensor.available", "on")
    await hass.async_block_till_done()

    input_entities = ["one", "two"]

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert result["errors"] is None
    assert result["preview"] == "template"

    availability = {
        "advanced_options": {
            "availability": "{{ is_state('binary_sensor.available', 'on') }}"
        }
    }

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": {
                "name": "My template",
                "state": state_template,
                **availability,
            }
            | extra_user_input,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None

    entities = [f"{template_type}.{_id}" for _id in listeners[0]]
    entities.append("binary_sensor.available")

    msg = await client.receive_json()
    assert msg["event"] == {
        "attributes": {"friendly_name": "My template"} | extra_attributes[0],
        "listeners": {
            "all": False,
            "domains": [],
            "entities": unordered(entities),
            "time": False,
        },
        "state": template_states[0],
    }

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[input_entity], {}
        )
        await hass.async_block_till_done()

    entities = [f"{template_type}.{_id}" for _id in listeners[1]]
    entities.append("binary_sensor.available")

    for template_state in template_states[1:]:
        msg = await client.receive_json()
        assert msg["event"] == {
            "attributes": {"friendly_name": "My template"}
            | extra_attributes[0]
            | extra_attributes[1],
            "listeners": {
                "all": False,
                "domains": [],
                "entities": unordered(entities),
                "time": False,
            },
            "state": template_state,
        }
    assert len(hass.states.async_all()) == 3

    # Test preview availability.
    hass.states.async_set("binary_sensor.available", "off")
    await hass.async_block_till_done()

    msg = await client.receive_json()
    assert msg["event"] == {
        "attributes": {"friendly_name": "My template"}
        | extra_attributes[0]
        | extra_attributes[1],
        "listeners": {
            "all": False,
            "domains": [],
            "entities": unordered(entities),
            "time": False,
        },
        "state": STATE_UNAVAILABLE,
    }

    assert len(hass.states.async_all()) == 3


EARLY_END_ERROR = "invalid template (TemplateSyntaxError: unexpected 'end of template')"


@pytest.mark.parametrize(
    ("template_type", "state_template", "extra_user_input", "error"),
    [
        ("binary_sensor", "{{", {}, {"state": EARLY_END_ERROR}),
        ("sensor", "{{", {}, {"state": EARLY_END_ERROR}),
        (
            "sensor",
            "",
            {"device_class": "aqi", "unit_of_measurement": "cats"},
            {
                "unit_of_measurement": (
                    "'cats' is not a valid unit for device class 'aqi'; "
                    "expected no unit of measurement"
                ),
            },
        ),
        (
            "sensor",
            "",
            {"device_class": "temperature", "unit_of_measurement": "cats"},
            {
                "unit_of_measurement": (
                    "'cats' is not a valid unit for device class 'temperature'; "
                    "expected one of 'K', '°C', '°F'"
                ),
            },
        ),
        (
            "sensor",
            "",
            {"device_class": "timestamp", "state_class": "measurement"},
            {
                "state_class": (
                    "'measurement' is not a valid state class for device class "
                    "'timestamp'; expected no state class"
                ),
            },
        ),
        (
            "sensor",
            "",
            {"device_class": "aqi", "state_class": "total"},
            {
                "state_class": (
                    "'total' is not a valid state class for device class "
                    "'aqi'; expected 'measurement'"
                ),
            },
        ),
        (
            "sensor",
            "",
            {"device_class": "energy", "state_class": "measurement"},
            {
                "state_class": (
                    "'measurement' is not a valid state class for device class "
                    "'energy'; expected one of 'total', 'total_increasing'"
                ),
                "unit_of_measurement": (
                    "'None' is not a valid unit for device class 'energy'; "
                    "expected one of 'cal', 'Gcal', 'GJ', 'GWh', 'J',"
                    " 'kcal', 'kJ', 'kWh', 'Mcal', 'MJ', 'MWh',"
                    " 'mWh', 'TWh', 'Wh'"
                ),
            },
        ),
    ],
)
async def test_config_flow_preview_bad_input(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    state_template: str,
    extra_user_input: dict[str, str],
    error: dict[str, str],
) -> None:
    """Test the config flow preview."""
    client = await hass_ws_client(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": {"name": "My template", "state": state_template}
            | extra_user_input,
        }
    )
    msg = await client.receive_json()
    assert not msg["success"]
    assert msg["error"] == {
        "code": "invalid_user_input",
        "message": error,
    }


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "input_states",
        "template_states",
        "error_events",
    ),
    [
        (
            "sensor",
            "{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
            {"one": "30.0", "two": "20.0"},
            ["50.0"],
            [
                (
                    "ValueError: Template error: float got invalid input 'unknown' "
                    "when rendering template '{{ float(states('sensor.one')) + "
                    "float(states('sensor.two')) }}' but no default was specified"
                )
            ],
        ),
    ],
)
async def test_config_flow_preview_template_startup_error(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    state_template: str,
    input_states: dict[str, str],
    template_states: list[str],
    error_events: list[str],
) -> None:
    """Test the config flow preview."""
    client = await hass_ws_client(hass)

    input_entities = ["one", "two"]

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": {"name": "My template", "state": state_template},
        }
    )
    msg = await client.receive_json()
    assert msg["type"] == "result"
    assert msg["success"]

    for error_event in error_events:
        msg = await client.receive_json()
        assert msg["type"] == "event"
        assert msg["event"] == {"error": error_event}

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[input_entity], {}
        )

    msg = await client.receive_json()
    assert msg["type"] == "event"
    assert msg["event"]["state"] == template_states[0]


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "input_states",
        "template_states",
        "error_events",
    ),
    [
        (
            "sensor",
            "{{ float(states('sensor.one')) > 30 and undefined_function() }}",
            [{"one": "30.0", "two": "20.0"}, {"one": "35.0", "two": "20.0"}],
            ["False"],
            ["UndefinedError: 'undefined_function' is undefined"],
        ),
    ],
)
async def test_config_flow_preview_template_error(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    state_template: str,
    input_states: list[dict[str, str]],
    template_states: list[str],
    error_events: list[str],
) -> None:
    """Test the config flow preview."""
    client = await hass_ws_client(hass)

    input_entities = ["one", "two"]

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[0][input_entity], {}
        )

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": {"name": "My template", "state": state_template},
        }
    )
    msg = await client.receive_json()
    assert msg["type"] == "result"
    assert msg["success"]

    msg = await client.receive_json()
    assert msg["type"] == "event"
    assert msg["event"]["state"] == template_states[0]

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[1][input_entity], {}
        )

    for error_event in error_events:
        msg = await client.receive_json()
        assert msg["type"] == "event"
        assert msg["event"] == {"error": error_event}


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "extra_user_input",
    ),
    [
        (
            "sensor",
            "{{ 1.0 / states('sensor.one') | float(0.0) }}",
            {"unit_of_measurement": "°C"},
        ),
    ],
)
async def test_config_flow_preview_bad_state(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    state_template: str,
    extra_user_input: dict[str, Any],
) -> None:
    """Test the config flow preview."""
    client = await hass_ws_client(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": template_type},
    )
    await hass.async_block_till_done()
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == template_type
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": {"name": "My template", "state": state_template}
            | extra_user_input,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None

    msg = await client.receive_json()
    assert msg["event"] == {"error": "ZeroDivisionError: division by zero"}


@pytest.mark.parametrize(
    (
        "template_type",
        "old_state_template",
        "new_state_template",
        "extra_config_flow_data",
        "extra_user_input",
        "input_states",
        "template_state",
        "extra_attributes",
        "listeners",
    ),
    [
        (
            "binary_sensor",
            (
                "{{ states('binary_sensor.one') == 'on'"
                " or states('binary_sensor.two') == 'on' }}"
            ),
            (
                "{{ states('binary_sensor.one') == 'on'"
                " and states('binary_sensor.two') == 'on' }}"
            ),
            {},
            {},
            {"one": "on", "two": "off"},
            "off",
            {},
            ["one", "two"],
        ),
        (
            "sensor",
            "{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
            "{{ float(states('sensor.one')) - float(states('sensor.two')) }}",
            {},
            {},
            {"one": "30.0", "two": "20.0"},
            "10.0",
            {},
            ["one", "two"],
        ),
    ],
)
async def test_option_flow_preview(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    old_state_template: str,
    new_state_template: str,
    extra_config_flow_data: dict[str, Any],
    extra_user_input: dict[str, Any],
    input_states: dict[str, Any],
    template_state: str,
    extra_attributes: dict[str, Any],
    listeners: list[str],
) -> None:
    """Test the option flow preview."""
    client = await hass_ws_client(hass)

    input_entities = ["one", "two"]

    # Setup the config entry
    config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            "name": "My template",
            "state": old_state_template,
            "template_type": template_type,
        }
        | extra_config_flow_data,
        title="My template",
    )
    config_entry.add_to_hass(hass)
    assert await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    result = await hass.config_entries.options.async_init(config_entry.entry_id)
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] is None
    assert result["preview"] == "template"

    for input_entity in input_entities:
        hass.states.async_set(
            f"{template_type}.{input_entity}", input_states[input_entity], {}
        )

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "options_flow",
            "user_input": {"state": new_state_template} | extra_user_input,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None

    msg = await client.receive_json()
    assert msg["event"] == {
        "attributes": {"friendly_name": "My template"} | extra_attributes,
        "listeners": {
            "all": False,
            "domains": [],
            "entities": unordered([f"{template_type}.{_id}" for _id in listeners]),
            "time": False,
        },
        "state": template_state,
    }
    assert len(hass.states.async_all()) == 3


async def test_option_flow_sensor_preview_config_entry_removed(
    hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
    """Test the option flow preview where the config entry is removed."""
    client = await hass_ws_client(hass)

    # Setup the config entry
    config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            "name": "My template",
            "state": "Hello!",
            "template_type": "sensor",
        },
        title="My template",
    )
    config_entry.add_to_hass(hass)
    assert await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    result = await hass.config_entries.options.async_init(config_entry.entry_id)
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] is None
    assert result["preview"] == "template"

    await hass.config_entries.async_remove(config_entry.entry_id)

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "options_flow",
            "user_input": {"state": "Goodbye!"},
        }
    )
    msg = await client.receive_json()
    assert not msg["success"]
    assert msg["error"] == {"code": "home_assistant_error", "message": "Unknown error"}


@pytest.mark.parametrize(
    (
        "template_type",
        "state_template",
        "extra_input",
        "extra_options",
    ),
    [
        (
            "sensor",
            {"state": "{{ 15 }}"},
            {},
            {},
        ),
        (
            "binary_sensor",
            {"state": "{{ false }}"},
            {},
            {},
        ),
        (
            "button",
            {},
            {},
            {},
        ),
        (
            "cover",
            {"state": "{{ states('cover.one') }}"},
            {"set_cover_position": []},
            {"set_cover_position": []},
        ),
        (
            "event",
            {"event_type": "{{ 'single' }}"},
            {"event_types": "{{ ['single', 'double'] }}"},
            {"event_types": "{{ ['single', 'double'] }}"},
        ),
        (
            "fan",
            {"state": "{{ states('fan.one') }}"},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
        ),
        (
            "image",
            {
                "url": "{{ states('sensor.one') }}",
                "verify_ssl": True,
            },
            {},
            {},
        ),
        (
            "light",
            {"state": "{{ states('light.one') }}"},
            {"turn_on": [], "turn_off": []},
            {"turn_on": [], "turn_off": []},
        ),
        (
            "lock",
            {"state": "{{ states('lock.one') }}"},
            {"lock": [], "unlock": []},
            {"lock": [], "unlock": []},
        ),
        (
            "number",
            {"state": "{{ states('number.one') }}"},
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
            {
                "min": 0,
                "max": 100,
                "step": 0.1,
                "set_value": {
                    "action": "input_number.set_value",
                    "target": {"entity_id": "input_number.test"},
                    "data": {"value": "{{ value }}"},
                },
            },
        ),
        (
            "alarm_control_panel",
            {"value_template": "{{ states('alarm_control_panel.one') }}"},
            {"code_arm_required": True, "code_format": "number"},
            {"code_arm_required": True, "code_format": "number"},
        ),
        (
            "select",
            {"state": "{{ states('select.one') }}"},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
            {"options": "{{ ['off', 'on', 'auto'] }}", "select_option": []},
        ),
        (
            "switch",
            {"value_template": "{{ false }}"},
            {},
            {},
        ),
        (
            "update",
            {"installed_version": "{{ states('update.one') }}"},
            {"latest_version": "{{ '2.0' }}"},
            {"latest_version": "{{ '2.0' }}"},
        ),
        (
            "vacuum",
            {"state": "{{ states('vacuum.one') }}"},
            {"start": []},
            {"start": []},
        ),
        (
            "weather",
            {"condition": "{{ states('weather.one') }}"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
            {"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
        ),
    ],
)
async def test_options_flow_change_device(
    hass: HomeAssistant,
    template_type: str,
    state_template: dict[str, Any],
    extra_input: dict[str, Any],
    extra_options: dict[str, Any],
    device_registry: dr.DeviceRegistry,
) -> None:
    """Test remove the device registry configuration entry when the device changes."""

    # Configure a device registry
    entry_device1 = MockConfigEntry()
    entry_device1.add_to_hass(hass)
    device1 = device_registry.async_get_or_create(
        config_entry_id=entry_device1.entry_id,
        identifiers={("test", "identifier_test1")},
        connections={("mac", "20:31:32:33:34:01")},
    )
    entry_device2 = MockConfigEntry()
    entry_device2.add_to_hass(hass)
    device2 = device_registry.async_get_or_create(
        config_entry_id=entry_device1.entry_id,
        identifiers={("test", "identifier_test2")},
        connections={("mac", "20:31:32:33:34:02")},
    )
    await hass.async_block_till_done()

    device_id1 = device1.id
    assert device_id1 is not None

    device_id2 = device2.id
    assert device_id2 is not None

    # Setup the config entry with device 1
    template_config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            "template_type": template_type,
            "name": "My template",
            "device_id": device_id1,
            **state_template,
            **extra_options,
        },
        title="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()

    # Change to link to device 2
    result = await hass.config_entries.options.async_init(
        template_config_entry.entry_id
    )
    assert result["type"] is FlowResultType.FORM

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "device_id": device_id2,
            **state_template,
            **extra_input,
        },
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["data"] == {
        "template_type": template_type,
        "name": "My template",
        "device_id": device_id2,
        **state_template,
        **extra_input,
    }
    assert template_config_entry.data == {}
    assert template_config_entry.options == {
        "template_type": template_type,
        "name": "My template",
        "device_id": device_id2,
        **state_template,
        **extra_options,
    }

    # Remove link with device
    result = await hass.config_entries.options.async_init(
        template_config_entry.entry_id
    )
    assert result["type"] is FlowResultType.FORM

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            **state_template,
            **extra_input,
        },
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["data"] == {
        "template_type": template_type,
        "name": "My template",
        **state_template,
        **extra_input,
    }
    assert template_config_entry.data == {}
    assert template_config_entry.options == {
        "template_type": template_type,
        "name": "My template",
        **state_template,
        **extra_options,
    }

    # Change to link to device 1
    result = await hass.config_entries.options.async_init(
        template_config_entry.entry_id
    )
    assert result["type"] is FlowResultType.FORM

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "device_id": device_id1,
            **state_template,
            **extra_input,
        },
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["data"] == {
        "template_type": template_type,
        "name": "My template",
        "device_id": device_id1,
        **state_template,
        **extra_input,
    }
    assert template_config_entry.data == {}
    assert template_config_entry.options == {
        "template_type": template_type,
        "name": "My template",
        "device_id": device_id1,
        **state_template,
        **extra_options,
    }


@pytest.mark.parametrize(
    ("step_id", "user_input", "expected_error"),
    [
        (
            "light",
            {
                "name": "",
                "state": "{{ state() }}",
                "level": "{{ statex() }}",
                "turn_on": [],
                "turn_off": [],
                "set_level": [],
            },
            "UndefinedError: 'statex' is undefined",
        ),
        (
            "sensor",
            {
                "name": "",
                "state": "{{ state() }}",
            },
            "UndefinedError: 'state' is undefined",
        ),
        (
            "light",
            {
                "name": "",
                "state": "{{ state() }}",
                "level": "{{ states('sensor.abc') }}",
                "turn_on": [],
                "turn_off": [],
                "set_level": [],
            },
            "UndefinedError: 'state' is undefined",
        ),
    ],
)
async def test_preview_error(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    step_id: str,
    user_input: dict,
    expected_error: str,
) -> None:
    """Test preview will error if any template errors."""
    client = await hass_ws_client(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": step_id},
    )
    await hass.async_block_till_done()

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == step_id
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": user_input,
        }
    )
    msg = await client.receive_json()

    assert msg["success"]
    assert msg["result"] is None

    # Test expected error
    msg = await client.receive_json()
    assert "error" in msg["event"]
    assert msg["event"]["error"] == expected_error

    # Test No preview is created
    with pytest.raises(TimeoutError):
        await client.receive_json(timeout=0.01)


@pytest.mark.parametrize(
    ("step_id", "user_input", "expected_state"),
    [
        (
            "sensor",
            {
                "name": "",
                "state": "{{ this.state }}",
            },
            "unknown",
        ),
        (
            "binary_sensor",
            {
                "name": "",
                "state": "{{ this.state }}",
            },
            "off",
        ),
    ],
)
async def test_preview_this_variable_config_flow(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    step_id: str,
    user_input: dict,
    expected_state: str,
) -> None:
    """Test 'this' variable will not produce an error when rendering a template."""
    client = await hass_ws_client(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"next_step_id": step_id},
    )
    await hass.async_block_till_done()

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == step_id
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "config_flow",
            "user_input": user_input,
        }
    )
    msg = await client.receive_json()

    assert msg["success"]
    assert msg["result"] is None

    # Verify we do not get an error and that we receive a preview state.
    msg = await client.receive_json()
    assert "error" not in msg["event"]
    assert msg["event"]["state"] == expected_state


@pytest.mark.parametrize(
    ("template_type", "extra_config", "set_state", "expected_state"),
    [
        (
            "sensor",
            {},
            "foobar",
            "foobar",
        ),
        (
            "binary_sensor",
            {
                "state": "{{ this.state }}",
            },
            "on",
            "on",
        ),
    ],
)
async def test_preview_this_variable_options_flow(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    template_type: str,
    extra_config: dict,
    set_state: str,
    expected_state: str,
) -> None:
    """Test 'this' variable with options flow."""
    client = await hass_ws_client(hass)

    config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            "name": "My template",
            "template_type": template_type,
            "state": "{{ None }}",
            **extra_config,
        },
        title="My template",
    )
    config_entry.add_to_hass(hass)
    assert await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    entity_id = f"{template_type}.my_template"
    hass.states.async_set(entity_id, set_state)
    await hass.async_block_till_done()

    state = hass.states.get(f"{template_type}.my_template")
    assert state.state == expected_state

    result = await hass.config_entries.options.async_init(config_entry.entry_id)
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] is None
    assert result["preview"] == "template"

    await client.send_json_auto_id(
        {
            "type": "template/start_preview",
            "flow_id": result["flow_id"],
            "flow_type": "options_flow",
            "user_input": {
                "state": "{{ this.state }}",
                **extra_config,
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None

    msg = await client.receive_json()
    assert msg["event"]["state"] == expected_state
