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

from typing import Any

import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant import setup
from homeassistant.components import lock, template
from homeassistant.components.lock import LockEntityFeature, LockState
from homeassistant.const import (
    ATTR_CODE,
    ATTR_ENTITY_ID,
    STATE_OFF,
    STATE_ON,
    STATE_OPEN,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.typing import ConfigType

from .conftest import (
    ConfigurationStyle,
    TemplatePlatformSetup,
    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
from tests.typing import WebSocketGenerator

TEST_STATE_ENTITY_ID = "sensor.test_state"
TEST_AVAILABILITY_ENTITY_ID = "availability_state.state"
TEST_LOCK = TemplatePlatformSetup(
    lock.DOMAIN,
    "test_template_lock",
    make_test_trigger(
        TEST_AVAILABILITY_ENTITY_ID,
        TEST_STATE_ENTITY_ID,
    ),
)


CODE_DATA = {
    "code": "{{ code if code is defined else None }}",
}
LOCK_ACTION = make_test_action("lock", CODE_DATA)
UNLOCK_ACTION = make_test_action("unlock", CODE_DATA)
OPEN_ACTION = make_test_action("open")


OPTIMISTIC_LOCK = {
    **LOCK_ACTION,
    **UNLOCK_ACTION,
}


@pytest.fixture
async def setup_lock(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    config: dict[str, Any],
) -> None:
    """Do setup of lock integration."""
    await setup_entity(hass, TEST_LOCK, style, count, config)


@pytest.fixture
async def setup_base_lock(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str,
    extra_config: dict,
):
    """Do setup of lock integration using a state template."""
    await setup_entity(hass, TEST_LOCK, style, count, extra_config, state_template)


@pytest.fixture
async def setup_state_lock(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str,
):
    """Do setup of lock integration using a state template."""
    await setup_entity(hass, TEST_LOCK, style, count, OPTIMISTIC_LOCK, state_template)


@pytest.fixture
async def setup_state_lock_with_extra_config(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str,
    extra_config: dict,
):
    """Do setup of lock integration using a state template."""
    await setup_entity(
        hass, TEST_LOCK, style, count, OPTIMISTIC_LOCK, state_template, extra_config
    )


@pytest.fixture
async def setup_state_lock_with_attribute(
    hass: HomeAssistant,
    count: int,
    style: ConfigurationStyle,
    state_template: str,
    attribute: str,
    attribute_template: str,
):
    """Do setup of lock integration using a state template."""
    await setup_entity(
        hass,
        TEST_LOCK,
        style,
        count,
        OPTIMISTIC_LOCK,
        state_template,
        {attribute: attribute_template} if attribute else {},
    )


@pytest.mark.parametrize(
    ("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_template_state(hass: HomeAssistant) -> None:
    """Test template."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED

    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED

    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OPEN)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.OPEN

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

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


@pytest.mark.parametrize(
    ("count", "state_template", "extra_config"),
    [(1, "{{ states.sensor.test_state.state }}", {"optimistic": True, **OPEN_ACTION})],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock_with_extra_config")
async def test_open_lock_optimistic(
    hass: HomeAssistant, calls: list[ServiceCall]
) -> None:
    """Test optimistic open."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_OPEN,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == "open"
    assert calls[0].data["caller"] == TEST_LOCK.entity_id

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.OPEN


@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 1 }}")])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_template_state_boolean_on(hass: HomeAssistant) -> None:
    """Test the setting of the state with boolean on."""
    # Ensure the trigger executes for trigger configurations
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED


@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 2 }}")])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_template_state_boolean_off(hass: HomeAssistant) -> None:
    """Test the setting of the state with off."""
    # Ensure the trigger executes for trigger configurations
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED


@pytest.mark.parametrize("count", [0])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("state_template", "extra_config"),
    [
        ("{% if rubbish %}", OPTIMISTIC_LOCK),
        ("{{ rubbish }", OPTIMISTIC_LOCK),
        ("Invalid", {}),
        (
            "{{ 1==1 }}",
            {
                "not_value_template": "{{ states.sensor.test_state.state }}",
                **OPTIMISTIC_LOCK,
            },
        ),
    ],
)
@pytest.mark.usefixtures("setup_base_lock")
async def test_template_syntax_error(hass: HomeAssistant) -> None:
    """Test templating syntax errors don't create entities."""
    assert hass.states.async_all("lock") == []


@pytest.mark.parametrize(("count", "state_template"), [(0, "{{ 1==1 }}")])
@pytest.mark.parametrize("attribute_template", ["{{ rubbish }", "{% if rubbish %}"])
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_template_code_template_syntax_error(hass: HomeAssistant) -> None:
    """Test templating code_format syntax errors don't create entities."""
    assert hass.states.async_all("lock") == []


@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 + 1 }}")])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_template_static(hass: HomeAssistant) -> None:
    """Test that we allow static templates."""
    hass.states.async_set(TEST_LOCK.entity_id, LockState.LOCKED)
    await hass.async_block_till_done()
    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED


@pytest.mark.parametrize("count", [1])
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    ("state_template", "expected"),
    [
        ("{{ True }}", LockState.LOCKED),
        ("{{ False }}", LockState.UNLOCKED),
        ("{{ x - 12 }}", STATE_UNAVAILABLE),
        ("{{ None }}", STATE_UNKNOWN),
    ],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_state_template(hass: HomeAssistant, expected: str) -> None:
    """Test state and value_template template."""
    # Ensure the trigger executes for trigger configurations
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

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


@pytest.mark.parametrize(
    ("count", "state_template", "attribute"), [(1, "{{ 1==1 }}", "picture")]
)
@pytest.mark.parametrize(
    "attribute_template",
    ["{% if states.sensor.test_state.state %}/local/switch.png{% endif %}"],
)
@pytest.mark.parametrize(
    ("style", "initial_state"),
    [
        (ConfigurationStyle.MODERN, ""),
        (ConfigurationStyle.TRIGGER, None),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_picture_template(hass: HomeAssistant, initial_state: str) -> None:
    """Test entity_picture template."""
    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.attributes.get("entity_picture") == initial_state

    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

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


@pytest.mark.parametrize(
    ("count", "state_template", "attribute"), [(1, "{{ 1==1 }}", "icon")]
)
@pytest.mark.parametrize(
    "attribute_template",
    ["{% if states.sensor.test_state.state %}mdi:eye{% endif %}"],
)
@pytest.mark.parametrize(
    ("style", "initial_state"),
    [
        (ConfigurationStyle.MODERN, ""),
        (ConfigurationStyle.TRIGGER, None),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_icon_template(hass: HomeAssistant, initial_state: str) -> None:
    """Test icon template."""
    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.attributes.get("icon") == initial_state

    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

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


@pytest.mark.parametrize(
    ("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_lock_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
    """Test lock action."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_LOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == "lock"
    assert calls[0].data["caller"] == TEST_LOCK.entity_id


@pytest.mark.parametrize(
    ("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_unlock_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
    """Test unlock action."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_UNLOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == "unlock"
    assert calls[0].data["caller"] == TEST_LOCK.entity_id


@pytest.mark.parametrize(
    ("count", "state_template", "extra_config"),
    [(1, "{{ states.sensor.test_state.state }}", OPEN_ACTION)],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_state_lock_with_extra_config")
async def test_open_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
    """Test open action."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_OPEN,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == "open"
    assert calls[0].data["caller"] == TEST_LOCK.entity_id


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ states.sensor.test_state.state }}",
            "{{ '.+' }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_lock_action_with_code(
    hass: HomeAssistant, calls: list[ServiceCall]
) -> None:
    """Test lock action with defined code format and supplied lock code."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_LOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "LOCK_CODE"},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == "lock"
    assert calls[0].data["caller"] == TEST_LOCK.entity_id
    assert calls[0].data["code"] == "LOCK_CODE"


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ states.sensor.test_state.state }}",
            "{{ '.+' }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_unlock_action_with_code(
    hass: HomeAssistant, calls: list[ServiceCall]
) -> None:
    """Test unlock action with code format and supplied unlock code."""
    await setup.async_setup_component(hass, "switch", {})
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_UNLOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "UNLOCK_CODE"},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == "unlock"
    assert calls[0].data["caller"] == TEST_LOCK.entity_id
    assert calls[0].data["code"] == "UNLOCK_CODE"


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ 1 == 1 }}",
            "{{ '\\\\d+' }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.parametrize(
    "test_action",
    [
        lock.SERVICE_LOCK,
        lock.SERVICE_UNLOCK,
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_lock_actions_fail_with_invalid_code(
    hass: HomeAssistant, calls: list[ServiceCall], test_action
) -> None:
    """Test invalid lock codes."""
    # Ensure trigger entities updated
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    await hass.services.async_call(
        lock.DOMAIN,
        test_action,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "non-number-value"},
    )
    await hass.services.async_call(
        lock.DOMAIN,
        test_action,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.async_block_till_done()

    assert len(calls) == 0


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ 1 == 1 }}",
            "{{ 1/0 }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_lock_actions_dont_execute_with_code_template_rendering_error(
    hass: HomeAssistant, calls: list[ServiceCall]
) -> None:
    """Test lock code format rendering fails block lock/unlock actions."""

    # Ensure trigger entities updated
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_LOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_UNLOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "any-value"},
    )
    await hass.async_block_till_done()

    assert len(calls) == 0


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ states.sensor.test_state.state }}",
            "{{ None }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.parametrize("action", [lock.SERVICE_LOCK, lock.SERVICE_UNLOCK])
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_actions_with_none_as_codeformat_ignores_code(
    hass: HomeAssistant, action, calls: list[ServiceCall]
) -> None:
    """Test lock actions with supplied lock code."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        action,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "any code"},
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data["action"] == action
    assert calls[0].data["caller"] == TEST_LOCK.entity_id
    assert calls[0].data["code"] == "any code"


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ states.sensor.test_state.state }}",
            "[12]{1",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "code_format"),
        (ConfigurationStyle.TRIGGER, "code_format"),
    ],
)
@pytest.mark.parametrize("action", [lock.SERVICE_LOCK, lock.SERVICE_UNLOCK])
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
async def test_actions_with_invalid_regexp_as_codeformat_never_execute(
    hass: HomeAssistant, action, calls: list[ServiceCall]
) -> None:
    """Test lock actions don't execute with invalid regexp."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        action,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "1"},
    )
    await hass.services.async_call(
        lock.DOMAIN,
        action,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "x"},
    )
    await hass.services.async_call(
        lock.DOMAIN,
        action,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
    )
    await hass.async_block_till_done()

    assert len(calls) == 0


@pytest.mark.parametrize(
    ("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.parametrize(
    "test_state",
    [
        LockState.LOCKED,
        LockState.UNLOCKED,
        LockState.OPEN,
        LockState.UNLOCKING,
        LockState.LOCKING,
        LockState.JAMMED,
        LockState.OPENING,
    ],
)
@pytest.mark.usefixtures("setup_state_lock")
async def test_lock_state(hass: HomeAssistant, test_state) -> None:
    """Test value template."""
    await async_trigger(hass, TEST_STATE_ENTITY_ID, test_state)

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == test_state


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ states('sensor.test_state') }}",
            "{{ is_state('availability_state.state', 'on') }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "availability"),
        (ConfigurationStyle.TRIGGER, "availability"),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
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_ID, STATE_ON)
    await hass.async_block_till_done()

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

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

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


@pytest.mark.parametrize(
    ("count", "state_template", "attribute_template"),
    [
        (
            1,
            "{{ 1 + 1 }}",
            "{{ x - 12 }}",
        )
    ],
)
@pytest.mark.parametrize(
    ("style", "attribute"),
    [
        (ConfigurationStyle.MODERN, "availability"),
        (ConfigurationStyle.TRIGGER, "availability"),
    ],
)
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
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, STATE_ON)

    assert hass.states.get(TEST_LOCK.entity_id).state != STATE_UNAVAILABLE
    err = "'x' is undefined"
    assert err in caplog_setup_text or err in caplog.text


@pytest.mark.parametrize("config", [OPTIMISTIC_LOCK])
@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 entity per id."""
    await setup_and_test_unique_id(hass, TEST_LOCK, style, config, "{{ 'on' }}")


@pytest.mark.parametrize("config", [OPTIMISTIC_LOCK])
@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 unique_ids."""
    await setup_and_test_nested_unique_id(
        hass, TEST_LOCK, style, entity_registry, config, "{{ 'on' }}"
    )


@pytest.mark.parametrize(
    ("count", "config"),
    [
        (
            1,
            {
                "state": "{{ 0 == 1 }}",
                "lock": [],
                "unlock": [],
                "open": [],
                "optimistic": True,
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
@pytest.mark.usefixtures("setup_lock")
async def test_empty_action_config(hass: HomeAssistant) -> None:
    """Test configuration with empty script."""
    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.attributes["supported_features"] == LockEntityFeature.OPEN

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_UNLOCK,
        {ATTR_ENTITY_ID: "lock.test_template_lock"},
    )
    await hass.async_block_till_done()

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_LOCK,
        {ATTR_ENTITY_ID: "lock.test_template_lock"},
    )
    await hass.async_block_till_done()

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED


@pytest.mark.parametrize(
    ("count", "config"),
    [
        (
            1,
            {
                "lock": [],
                "unlock": [],
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_lock")
async def test_optimistic(hass: HomeAssistant) -> None:
    """Test configuration with optimistic state."""

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

    # Ensure Trigger template entities update.
    await async_trigger(hass, TEST_STATE_ENTITY_ID, "anything")

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_LOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
        blocking=True,
    )

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.LOCKED

    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_UNLOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
        blocking=True,
    )

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED


@pytest.mark.parametrize(
    ("count", "config"),
    [
        (
            1,
            {
                "state": "{{ is_state('sensor.test_state', 'on') }}",
                "lock": [],
                "unlock": [],
                "optimistic": False,
            },
        )
    ],
)
@pytest.mark.parametrize(
    "style",
    [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
)
@pytest.mark.usefixtures("setup_lock")
async def test_not_optimistic(hass: HomeAssistant) -> None:
    """Test optimistic yaml option set to false."""
    await hass.services.async_call(
        lock.DOMAIN,
        lock.SERVICE_LOCK,
        {ATTR_ENTITY_ID: TEST_LOCK.entity_id},
        blocking=True,
    )

    # Ensure Trigger template entities update.
    hass.states.async_set(TEST_AVAILABILITY_ENTITY_ID, "anything")
    await hass.async_block_till_done()

    state = hass.states.get(TEST_LOCK.entity_id)
    assert state.state == LockState.UNLOCKED


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

    hass.states.async_set(
        "sensor.test_state",
        LockState.LOCKED,
        {},
    )

    template_config_entry = MockConfigEntry(
        data={},
        domain=template.DOMAIN,
        options={
            "name": "My template",
            "state": "{{ states('sensor.test_state') }}",
            "lock": [],
            "unlock": [],
            "template_type": lock.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("lock.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,
        lock.DOMAIN,
        {
            "name": "My template",
            "state": "{{ 'locked' }}",
            "lock": [],
            "unlock": [],
        },
    )

    assert state["state"] == LockState.LOCKED


@pytest.mark.parametrize(
    "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
@pytest.mark.parametrize(
    (
        "saved_state",
        "saved_extra_data",
        "initial_state",
        "initial_attributes",
    ),
    [
        (
            LockState.JAMMED,
            {
                "code_format": ".+",
                "is_locked": False,
                "is_locking": False,
                "is_open": False,
                "is_opening": False,
                "is_unlocking": False,
                "is_jammed": True,
            },
            LockState.JAMMED,
            {
                "code_format": ".+",
            },
        ),
        (
            LockState.LOCKED,
            {
                "code_format": ".+",
                "is_locked": True,
                "is_locking": False,
                "is_open": False,
                "is_opening": False,
                "is_unlocking": False,
                "is_jammed": False,
            },
            LockState.LOCKED,
            {
                "code_format": ".+",
            },
        ),
        (
            LockState.LOCKING,
            {
                "code_format": ".+",
                "is_locked": False,
                "is_locking": True,
                "is_open": False,
                "is_opening": False,
                "is_unlocking": False,
                "is_jammed": False,
            },
            LockState.LOCKING,
            {
                "code_format": ".+",
            },
        ),
        (
            LockState.JAMMED,
            {
                "code_format": ".+",
                "is_locked": False,
                "is_locking": False,
                "is_opening": False,
                "is_unlocking": False,
                "is_jammed": True,
            },
            STATE_UNKNOWN,
            {
                "code_format": None,
            },
        ),
        (
            STATE_UNAVAILABLE,
            {
                "code_format": ".+",
                "is_locked": False,
                "is_locking": False,
                "is_open": False,
                "is_opening": False,
                "is_unlocking": False,
                "is_jammed": True,
            },
            STATE_UNKNOWN,
            {
                "code_format": None,
            },
        ),
        (
            STATE_UNKNOWN,
            {
                "code_format": ".+",
                "is_locked": False,
                "is_locking": False,
                "is_open": False,
                "is_opening": False,
                "is_unlocking": False,
                "is_jammed": True,
            },
            STATE_UNKNOWN,
            {
                "code_format": None,
            },
        ),
    ],
)
async def test_restore_state(
    hass: HomeAssistant,
    style: ConfigurationStyle,
    saved_state: LockState | str,
    saved_extra_data: dict | None,
    initial_state: LockState | str,
    initial_attributes: ConfigType,
) -> None:
    """Test restoring state."""

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

    await setup_restore_template_entity(
        hass,
        TEST_LOCK,
        style,
        {
            "code_format": "{{ state_attr('sensor.test_state', 'code_format') }}",
            "state": "{{ state_attr('sensor.test_state', 'lock_state') }}",
            "lock": [],
            "open": [],
            "unlock": [],
        },
        "is_state_attr('sensor.test_state', 'lock_state', 'unlocked')",
    )

    assert_state_and_attributes(hass, TEST_LOCK, initial_state, initial_attributes)

    await async_trigger(
        hass,
        "sensor.test_state",
        "anything",
        {"lock_state": LockState.UNLOCKED, "code_format": "\\\\d+"},
    )

    # The first trigger should replace the restored code_format attribute
    assert_state_and_attributes(
        hass, TEST_LOCK, LockState.UNLOCKED, {"code_format": "\\\\d+"}
    )
