"""Test the Config flow for the Bayesian integration."""

from types import MappingProxyType
from unittest.mock import patch

import pytest
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.bayesian.config_flow import (
    USER,
    ObservationTypes,
    OptionsFlowSteps,
)
from homeassistant.components.bayesian.const import (
    CONF_P_GIVEN_F,
    CONF_P_GIVEN_T,
    CONF_PRIOR,
    CONF_PROBABILITY_THRESHOLD,
    CONF_TO_STATE,
    DOMAIN,
)
from homeassistant.config_entries import (
    ConfigEntry,
    ConfigSubentry,
    ConfigSubentryDataWithId,
    FlowType,
)
from homeassistant.const import (
    CONF_ABOVE,
    CONF_BELOW,
    CONF_DEVICE_CLASS,
    CONF_ENTITY_ID,
    CONF_NAME,
    CONF_PLATFORM,
    CONF_STATE,
    CONF_VALUE_TEMPLATE,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

from tests.common import MockConfigEntry


async def test_config_flow_step_user(hass: HomeAssistant) -> None:
    """Test the config flow with an example."""
    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ):
        # Open config flow
        result0 = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        assert result0["step_id"] == USER
        assert result0["type"] is FlowResultType.FORM
        assert (
            result0["description_placeholders"]["url"]
            == "https://www.home-assistant.io/integrations/bayesian/"
        )

        # Enter basic settings
        result1 = await hass.config_entries.flow.async_configure(
            result0["flow_id"],
            {
                CONF_NAME: "Office occupied",
                CONF_PROBABILITY_THRESHOLD: 50,
                CONF_PRIOR: 15,
                CONF_DEVICE_CLASS: "occupancy",
            },
        )
        await hass.async_block_till_done()

        assert result1["type"] == FlowResultType.CREATE_ENTRY
        assert result1["result"].title == "Office occupied"
        assert result1["next_flow"][0] == FlowType.CONFIG_SUBENTRIES_FLOW


async def test_subentry_flow(hass: HomeAssistant) -> None:
    """Test the subentry flow with a full example."""
    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        # Set up the initial config entry as a mock to isolate testing of subentry flows
        config_entry = MockConfigEntry(
            domain=DOMAIN,
            data={
                CONF_NAME: "Office occupied",
                CONF_PROBABILITY_THRESHOLD: 50,
                CONF_PRIOR: 15,
                CONF_DEVICE_CLASS: "occupancy",
            },
        )
        config_entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

        # Open subentry flow
        result = await hass.config_entries.subentries.async_init(
            (config_entry.entry_id, "observation"),
            context={"source": config_entries.SOURCE_USER},
        )
        # Confirm the next page is the observation type selector
        assert result["step_id"] == "user"
        assert result["type"] is FlowResultType.MENU
        assert result["flow_id"] is not None

        # Set up a numeric state observation first
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.NUMERIC_STATE)
        assert result["type"] is FlowResultType.FORM

        # Set up a numeric range with only 'Above'
        # Also indirectly tests the conversion of proabilities to fractions
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                CONF_ABOVE: 40,
                CONF_P_GIVEN_T: 85,
                CONF_P_GIVEN_F: 45,
                CONF_NAME: "Office is bright",
            },
        )
        await hass.async_block_till_done()

        # Open another subentry flow
        result = await hass.config_entries.subentries.async_init(
            (config_entry.entry_id, "observation"),
            context={"source": config_entries.SOURCE_USER},
        )
        assert result["step_id"] == "user"
        assert result["type"] is FlowResultType.MENU
        assert result["flow_id"] is not None

        # Add a state observation
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.STATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.STATE)
        assert result["type"] is FlowResultType.FORM
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.work_laptop",
                CONF_TO_STATE: "on",
                CONF_P_GIVEN_T: 60,
                CONF_P_GIVEN_F: 20,
                CONF_NAME: "Work laptop on network",
            },
        )
        await hass.async_block_till_done()

        # Open another subentry flow
        result = await hass.config_entries.subentries.async_init(
            (config_entry.entry_id, "observation"),
            context={"source": config_entries.SOURCE_USER},
        )
        assert result["step_id"] == "user"
        assert result["type"] is FlowResultType.MENU
        assert result["flow_id"] is not None

        # Lastly, add a template observation
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.TEMPLATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.TEMPLATE)
        assert result["type"] is FlowResultType.FORM
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_VALUE_TEMPLATE: """
{% set current_time = now().time() %}
{% set start_time = strptime("07:00", "%H:%M").time() %}
{% set end_time = strptime("18:30", "%H:%M").time() %}
{% if start_time <= current_time <= end_time %}
True
{% else %}
False
{% endif %}
                """,
                CONF_P_GIVEN_T: 45,
                CONF_P_GIVEN_F: 5,
                CONF_NAME: "Daylight hours",
            },
        )

        observations = [
            dict(subentry.data) for subentry in config_entry.subentries.values()
        ]
        # assert config_entry["version"] == 1
        assert observations == [
            {
                CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
                CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                CONF_ABOVE: 40,
                CONF_P_GIVEN_T: 0.85,
                CONF_P_GIVEN_F: 0.45,
                CONF_NAME: "Office is bright",
            },
            {
                CONF_PLATFORM: str(ObservationTypes.STATE),
                CONF_ENTITY_ID: "sensor.work_laptop",
                CONF_TO_STATE: "on",
                CONF_P_GIVEN_T: 0.6,
                CONF_P_GIVEN_F: 0.2,
                CONF_NAME: "Work laptop on network",
            },
            {
                CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
                CONF_VALUE_TEMPLATE: (
                    "{% set current_time = now().time() %}\n"
                    '{% set start_time = strptime("07:00",'
                    ' "%H:%M").time() %}\n'
                    '{% set end_time = strptime("18:30",'
                    ' "%H:%M").time() %}\n'
                    "{% if start_time <= current_time"
                    " <= end_time %}\nTrue\n"
                    "{% else %}\nFalse\n{% endif %}"
                ),
                CONF_P_GIVEN_T: 0.45,
                CONF_P_GIVEN_F: 0.05,
                CONF_NAME: "Daylight hours",
            },
        ]

    assert len(mock_setup_entry.mock_calls) == 1


async def test_single_state_observation(hass: HomeAssistant) -> None:
    """Test a Bayesian sensor with just one state observation added.

    This test combines the config flow for a single state observation.
    """

    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        assert result["step_id"] == USER
        assert result["type"] is FlowResultType.FORM

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {
                CONF_NAME: "Anyone home",
                CONF_PROBABILITY_THRESHOLD: 50,
                CONF_PRIOR: 66,
                CONF_DEVICE_CLASS: "occupancy",
            },
        )
        await hass.async_block_till_done()

        assert result["type"] == FlowResultType.CREATE_ENTRY
        entry_id = result["result"].entry_id
        sub_flow_id = result["next_flow"][1]

        # Confirm the next step is the menu
        result = hass.config_entries.subentries.async_get(sub_flow_id)
        assert result["flow_id"] is not None

        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.STATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.STATE)
        assert result["type"] is FlowResultType.FORM

        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.kitchen_occupancy",
                CONF_TO_STATE: "on",
                CONF_P_GIVEN_T: 40,
                CONF_P_GIVEN_F: 0.5,
                CONF_NAME: "Kitchen Motion",
            },
        )

        assert result["type"] == FlowResultType.CREATE_ENTRY
        await hass.async_block_till_done()

        config_entry = hass.config_entries.async_get_entry(entry_id)
        assert config_entry is not None
        assert type(config_entry) is ConfigEntry
        assert config_entry.version == 1
        assert config_entry.options == {
            CONF_NAME: "Anyone home",
            CONF_PROBABILITY_THRESHOLD: 0.5,
            CONF_PRIOR: 0.66,
            CONF_DEVICE_CLASS: "occupancy",
        }
        assert len(config_entry.subentries) == 1
        assert list(config_entry.subentries.values())[0].data == {
            CONF_PLATFORM: CONF_STATE,
            CONF_ENTITY_ID: "sensor.kitchen_occupancy",
            CONF_TO_STATE: "on",
            CONF_P_GIVEN_T: 0.4,
            CONF_P_GIVEN_F: 0.005,
            CONF_NAME: "Kitchen Motion",
        }

    assert len(mock_setup_entry.mock_calls) == 1


async def test_single_numeric_state_observation(hass: HomeAssistant) -> None:
    """Test a Bayesian sensor with just one numeric_state observation added.

    Combines the config flow and the options flow for a
    single numeric_state observation.
    """

    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        assert result["step_id"] == USER
        assert result["type"] is FlowResultType.FORM

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {
                CONF_NAME: "Nice day",
                CONF_PROBABILITY_THRESHOLD: 51,
                CONF_PRIOR: 20,
            },
        )
        assert result["type"] == FlowResultType.CREATE_ENTRY
        config_entry = result["result"]
        sub_flow_id = result["next_flow"][1]
        await hass.async_block_till_done()

        # select numeric state observation
        result = await hass.config_entries.subentries.async_configure(
            sub_flow_id, {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.NUMERIC_STATE)
        assert result["type"] is FlowResultType.FORM
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 20,
                CONF_BELOW: 35,
                CONF_P_GIVEN_T: 95,
                CONF_P_GIVEN_F: 8,
                CONF_NAME: "20 - 35 outside",
            },
        )
        await hass.async_block_till_done()

        assert config_entry.options == {
            CONF_NAME: "Nice day",
            CONF_PROBABILITY_THRESHOLD: 0.51,
            CONF_PRIOR: 0.2,
        }
        assert len(config_entry.subentries) == 1
        assert list(config_entry.subentries.values())[0].data == {
            CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
            CONF_ENTITY_ID: "sensor.outside_temperature",
            CONF_ABOVE: 20,
            CONF_BELOW: 35,
            CONF_P_GIVEN_T: 0.95,
            CONF_P_GIVEN_F: 0.08,
            CONF_NAME: "20 - 35 outside",
        }

    assert len(mock_setup_entry.mock_calls) == 1


async def test_multi_numeric_state_observation(hass: HomeAssistant) -> None:
    """Test a Bayesian sensor with just more than one numeric_state observation added.

    Technically a subset of the tests in test_config_flow() but may help to
    narrow down errors more quickly.
    """

    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        assert result["step_id"] == USER
        assert result["type"] is FlowResultType.FORM

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {
                CONF_NAME: "Nice day",
                CONF_PROBABILITY_THRESHOLD: 51,
                CONF_PRIOR: 20,
            },
        )
        await hass.async_block_till_done()

        assert result["type"] == FlowResultType.CREATE_ENTRY
        config_entry = result["result"]
        sub_flow_id = result["next_flow"][1]

        # select numeric state observation
        result = await hass.config_entries.subentries.async_configure(
            sub_flow_id, {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.NUMERIC_STATE)
        assert result["type"] is FlowResultType.FORM
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 20,
                CONF_BELOW: 35,
                CONF_P_GIVEN_T: 95,
                CONF_P_GIVEN_F: 8,
                CONF_NAME: "20 - 35 outside",
            },
        )
        await hass.async_block_till_done()

        # This should fail as overlapping ranges for the same entity are not allowed
        result = await hass.config_entries.subentries.async_init(
            (config_entry.entry_id, "observation"),
            context={"source": config_entries.SOURCE_USER},
        )
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
        )
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 30,
                CONF_BELOW: 40,
                CONF_P_GIVEN_T: 95,
                CONF_P_GIVEN_F: 8,
                CONF_NAME: "30 - 40 outside",
            },
        )
        await hass.async_block_till_done()
        assert result["errors"] == {"base": "overlapping_ranges"}

        # This should fail as above should always be less than below
        current_step = result["step_id"]
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 40,
                CONF_BELOW: 35,
                CONF_P_GIVEN_T: 95,
                CONF_P_GIVEN_F: 8,
                CONF_NAME: "35 - 40 outside",
            },
        )
        await hass.async_block_till_done()
        assert result["step_id"] == current_step
        assert result["errors"] == {"base": "above_below"}

        # This should work
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 35,
                CONF_BELOW: 40,
                CONF_P_GIVEN_T: 70,
                CONF_P_GIVEN_F: 20,
                CONF_NAME: "35 - 40 outside",
            },
        )
        await hass.async_block_till_done()

        assert config_entry.version == 1
        assert config_entry.options == {
            CONF_NAME: "Nice day",
            CONF_PROBABILITY_THRESHOLD: 0.51,
            CONF_PRIOR: 0.2,
        }
        observations = [
            dict(subentry.data) for subentry in config_entry.subentries.values()
        ]
        assert observations == [
            {
                CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 20.0,
                CONF_BELOW: 35.0,
                CONF_P_GIVEN_T: 0.95,
                CONF_P_GIVEN_F: 0.08,
                CONF_NAME: "20 - 35 outside",
            },
            {
                CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
                CONF_ENTITY_ID: "sensor.outside_temperature",
                CONF_ABOVE: 35.0,
                CONF_BELOW: 40.0,
                CONF_P_GIVEN_T: 0.7,
                CONF_P_GIVEN_F: 0.2,
                CONF_NAME: "35 - 40 outside",
            },
        ]

    assert len(mock_setup_entry.mock_calls) == 1


async def test_single_template_observation(hass: HomeAssistant) -> None:
    """Test a Bayesian sensor with just one template observation added.

    Technically a subset of the tests in test_config_flow() but may help to
    narrow down errors more quickly.
    """

    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        assert result["step_id"] == USER
        assert result["type"] is FlowResultType.FORM

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {
                CONF_NAME: "Paulus Home",
                CONF_PROBABILITY_THRESHOLD: 90,
                CONF_PRIOR: 50,
                CONF_DEVICE_CLASS: "occupancy",
            },
        )
        await hass.async_block_till_done()

        assert result["type"] == FlowResultType.CREATE_ENTRY
        config_entry = result["result"]
        sub_flow_id = result["next_flow"][1]

        # Select template observation
        result = await hass.config_entries.subentries.async_configure(
            sub_flow_id, {"next_step_id": str(ObservationTypes.TEMPLATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.TEMPLATE)
        assert result["type"] is FlowResultType.FORM
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_VALUE_TEMPLATE: (
                    "{{is_state('device_tracker.paulus',"
                    "'not_home') and"
                    " ((as_timestamp(now()) -"
                    " as_timestamp(states"
                    ".device_tracker.paulus"
                    ".last_changed)) > 300)}}"
                ),
                CONF_P_GIVEN_T: 5,
                CONF_P_GIVEN_F: 99,
                CONF_NAME: "Not seen in last 5 minutes",
            },
        )
        await hass.async_block_till_done()
        assert config_entry.version == 1
        assert config_entry.options == {
            CONF_NAME: "Paulus Home",
            CONF_PROBABILITY_THRESHOLD: 0.9,
            CONF_PRIOR: 0.5,
            CONF_DEVICE_CLASS: "occupancy",
        }
        assert len(config_entry.subentries) == 1
        assert list(config_entry.subentries.values())[0].data == {
            CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
            CONF_VALUE_TEMPLATE: (
                "{{is_state('device_tracker.paulus',"
                "'not_home') and"
                " ((as_timestamp(now()) -"
                " as_timestamp(states"
                ".device_tracker.paulus"
                ".last_changed)) > 300)}}"
            ),
            CONF_P_GIVEN_T: 0.05,
            CONF_P_GIVEN_F: 0.99,
            CONF_NAME: "Not seen in last 5 minutes",
        }

    assert len(mock_setup_entry.mock_calls) == 1


async def test_basic_options(hass: HomeAssistant) -> None:
    """Test reconfiguring the basic options using an options flow."""
    config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            CONF_NAME: "Office occupied",
            CONF_PROBABILITY_THRESHOLD: 0.5,
            CONF_PRIOR: 0.15,
            CONF_DEVICE_CLASS: "occupancy",
        },
        subentries_data=[
            ConfigSubentryDataWithId(
                data=MappingProxyType(
                    {
                        CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
                        CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                        CONF_ABOVE: 40,
                        CONF_P_GIVEN_T: 0.85,
                        CONF_P_GIVEN_F: 0.45,
                        CONF_NAME: "Office is bright",
                    }
                ),
                subentry_id="01JXCPHRM64Y84GQC58P5EKVHY",
                subentry_type="observation",
                title="Office is bright",
                unique_id=None,
            )
        ],
        title="Office occupied",
    )
    # Setup the mock config entry
    config_entry.add_to_hass(hass)
    assert await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    # Give the sensor a real value
    hass.states.async_set("sensor.office_illuminance_lux", 50)

    # Start the options flow
    result = await hass.config_entries.options.async_init(config_entry.entry_id)

    # Confirm the first page is the form for editing the basic options
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == str(OptionsFlowSteps.INIT)

    # Change all possible settings (name can be changed elsewhere in the UI)
    await hass.config_entries.options.async_configure(
        result["flow_id"],
        {
            CONF_PROBABILITY_THRESHOLD: 49,
            CONF_PRIOR: 14,
            CONF_DEVICE_CLASS: "presence",
        },
    )
    await hass.async_block_till_done()

    # Confirm the changes stuck
    assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
        CONF_NAME: "Office occupied",
        CONF_PROBABILITY_THRESHOLD: 0.49,
        CONF_PRIOR: 0.14,
        CONF_DEVICE_CLASS: "presence",
    }
    assert config_entry.subentries == {
        "01JXCPHRM64Y84GQC58P5EKVHY": ConfigSubentry(
            data=MappingProxyType(
                {
                    CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
                    CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                    CONF_ABOVE: 40,
                    CONF_P_GIVEN_T: 0.85,
                    CONF_P_GIVEN_F: 0.45,
                    CONF_NAME: "Office is bright",
                }
            ),
            subentry_id="01JXCPHRM64Y84GQC58P5EKVHY",
            subentry_type="observation",
            title="Office is bright",
            unique_id=None,
        )
    }


async def test_reconfiguring_observations(hass: HomeAssistant) -> None:
    """Test editing observations through options flow, once of each of the 3 types."""
    # Setup the config entry
    config_entry = MockConfigEntry(
        data={},
        domain=DOMAIN,
        options={
            CONF_NAME: "Office occupied",
            CONF_PROBABILITY_THRESHOLD: 0.5,
            CONF_PRIOR: 0.15,
            CONF_DEVICE_CLASS: "occupancy",
        },
        subentries_data=[
            ConfigSubentryDataWithId(
                data=MappingProxyType(
                    {
                        CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
                        CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                        CONF_ABOVE: 40,
                        CONF_P_GIVEN_T: 0.85,
                        CONF_P_GIVEN_F: 0.45,
                        CONF_NAME: "Office is bright",
                    }
                ),
                subentry_id="01JXCPHRM64Y84GQC58P5EKVHY",
                subentry_type="observation",
                title="Office is bright",
                unique_id=None,
            ),
            ConfigSubentryDataWithId(
                data=MappingProxyType(
                    {
                        CONF_PLATFORM: str(ObservationTypes.STATE),
                        CONF_ENTITY_ID: "sensor.work_laptop",
                        CONF_TO_STATE: "on",
                        CONF_P_GIVEN_T: 0.6,
                        CONF_P_GIVEN_F: 0.2,
                        CONF_NAME: "Work laptop on network",
                    },
                ),
                subentry_id="13TCPHRM64Y84GQC58P5EKTHF",
                subentry_type="observation",
                title="Work laptop on network",
                unique_id=None,
            ),
            ConfigSubentryDataWithId(
                data=MappingProxyType(
                    {
                        CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
                        CONF_VALUE_TEMPLATE: (
                            "{% set current_time = now().time() %}\n"
                            '{% set start_time = strptime("07:00",'
                            ' "%H:%M").time() %}\n'
                            '{% set end_time = strptime("18:30",'
                            ' "%H:%M").time() %}\n'
                            "{% if start_time <= current_time"
                            " <= end_time %}\nTrue\n"
                            "{% else %}\nFalse\n{% endif %}"
                        ),
                        CONF_P_GIVEN_T: 0.45,
                        CONF_P_GIVEN_F: 0.05,
                        CONF_NAME: "Daylight hours",
                    }
                ),
                subentry_id="27TCPHRM64Y84GQC58P5EIES",
                subentry_type="observation",
                title="Daylight hours",
                unique_id=None,
            ),
        ],
        title="Office occupied",
    )

    # Set up the mock entry
    config_entry.add_to_hass(hass)
    assert await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    hass.states.async_set("sensor.office_illuminance_lux", 50)

    # select a subentry for reconfiguration
    result = await config_entry.start_subentry_reconfigure_flow(
        hass, subentry_id="13TCPHRM64Y84GQC58P5EKTHF"
    )
    await hass.async_block_till_done()

    # confirm the first page is the form for editing the observation
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reconfigure"
    assert result["description_placeholders"]["parent_sensor_name"] == "Office occupied"
    assert result["description_placeholders"]["device_class_on"] == "Detected"
    assert result["description_placeholders"]["device_class_off"] == "Clear"

    # Edit all settings
    await hass.config_entries.subentries.async_configure(
        result["flow_id"],
        {
            CONF_ENTITY_ID: "sensor.desktop",
            CONF_TO_STATE: "on",
            CONF_P_GIVEN_T: 70,
            CONF_P_GIVEN_F: 12,
            CONF_NAME: "Desktop on network",
        },
    )
    await hass.async_block_till_done()

    # Confirm the changes to the state config
    assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
        CONF_NAME: "Office occupied",
        CONF_PROBABILITY_THRESHOLD: 0.5,
        CONF_PRIOR: 0.15,
        CONF_DEVICE_CLASS: "occupancy",
    }
    observations = [
        dict(subentry.data)
        for subentry in hass.config_entries.async_get_entry(
            config_entry.entry_id
        ).subentries.values()
    ]
    assert observations == [
        {
            CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
            CONF_ENTITY_ID: "sensor.office_illuminance_lux",
            CONF_ABOVE: 40,
            CONF_P_GIVEN_T: 0.85,
            CONF_P_GIVEN_F: 0.45,
            CONF_NAME: "Office is bright",
        },
        {
            CONF_PLATFORM: str(ObservationTypes.STATE),
            CONF_ENTITY_ID: "sensor.desktop",
            CONF_TO_STATE: "on",
            CONF_P_GIVEN_T: 0.7,
            CONF_P_GIVEN_F: 0.12,
            CONF_NAME: "Desktop on network",
        },
        {
            CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
            CONF_VALUE_TEMPLATE: (
                "{% set current_time = now().time() %}\n"
                '{% set start_time = strptime("07:00",'
                ' "%H:%M").time() %}\n'
                '{% set end_time = strptime("18:30",'
                ' "%H:%M").time() %}\n'
                "{% if start_time <= current_time"
                " <= end_time %}\nTrue\n"
                "{% else %}\nFalse\n{% endif %}"
            ),
            CONF_P_GIVEN_T: 0.45,
            CONF_P_GIVEN_F: 0.05,
            CONF_NAME: "Daylight hours",
        },
    ]

    # Next test editing a numeric_state observation
    # select the subentry for reconfiguration
    result = await config_entry.start_subentry_reconfigure_flow(
        hass, subentry_id="01JXCPHRM64Y84GQC58P5EKVHY"
    )
    await hass.async_block_till_done()

    # confirm the first page is the form for editing the observation
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reconfigure"

    await hass.async_block_till_done()

    # Test an invalid re-configuration
    # This should fail as the probabilities are equal
    current_step = result["step_id"]
    result = await hass.config_entries.subentries.async_configure(
        result["flow_id"],
        {
            CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
            CONF_ABOVE: 2000,
            CONF_P_GIVEN_T: 80,
            CONF_P_GIVEN_F: 80,
            CONF_NAME: "Office is bright",
        },
    )
    await hass.async_block_till_done()
    assert result["step_id"] == current_step
    assert result["errors"] == {"base": "equal_probabilities"}

    # This should work
    result = await hass.config_entries.subentries.async_configure(
        result["flow_id"],
        {
            CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
            CONF_ABOVE: 2000,
            CONF_P_GIVEN_T: 80,
            CONF_P_GIVEN_F: 40,
            CONF_NAME: "Office is bright",
        },
    )
    await hass.async_block_till_done()
    assert "errors" not in result

    # Confirm the changes to the state config
    assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
        CONF_NAME: "Office occupied",
        CONF_PROBABILITY_THRESHOLD: 0.5,
        CONF_PRIOR: 0.15,
        CONF_DEVICE_CLASS: "occupancy",
    }
    observations = [
        dict(subentry.data)
        for subentry in hass.config_entries.async_get_entry(
            config_entry.entry_id
        ).subentries.values()
    ]
    assert observations == [
        {
            CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
            CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
            CONF_ABOVE: 2000,
            CONF_P_GIVEN_T: 0.8,
            CONF_P_GIVEN_F: 0.4,
            CONF_NAME: "Office is bright",
        },
        {
            CONF_PLATFORM: str(ObservationTypes.STATE),
            CONF_ENTITY_ID: "sensor.desktop",
            CONF_TO_STATE: "on",
            CONF_P_GIVEN_T: 0.7,
            CONF_P_GIVEN_F: 0.12,
            CONF_NAME: "Desktop on network",
        },
        {
            CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
            CONF_VALUE_TEMPLATE: (
                "{% set current_time = now().time() %}\n"
                '{% set start_time = strptime("07:00",'
                ' "%H:%M").time() %}\n'
                '{% set end_time = strptime("18:30",'
                ' "%H:%M").time() %}\n'
                "{% if start_time <= current_time"
                " <= end_time %}\nTrue\n"
                "{% else %}\nFalse\n{% endif %}"
            ),
            CONF_P_GIVEN_T: 0.45,
            CONF_P_GIVEN_F: 0.05,
            CONF_NAME: "Daylight hours",
        },
    ]

    # Next test editing a template observation
    # select the subentry for reconfiguration
    result = await config_entry.start_subentry_reconfigure_flow(
        hass, subentry_id="27TCPHRM64Y84GQC58P5EIES"
    )
    await hass.async_block_till_done()

    # confirm the first page is the form for editing the observation
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reconfigure"
    await hass.async_block_till_done()

    await hass.config_entries.subentries.async_configure(
        result["flow_id"],
        {
            CONF_VALUE_TEMPLATE: """
{% set current_time = now().time() %}
{% set start_time = strptime("07:00", "%H:%M").time() %}
{% set end_time = strptime("17:30", "%H:%M").time() %}
{% if start_time <= current_time <= end_time %}
True
{% else %}
False
{% endif %}
""",  # changed the end_time
            CONF_P_GIVEN_T: 55,
            CONF_P_GIVEN_F: 13,
            CONF_NAME: "Office hours",
        },
    )
    await hass.async_block_till_done()
    # Confirm the changes to the state config
    assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
        CONF_NAME: "Office occupied",
        CONF_PROBABILITY_THRESHOLD: 0.5,
        CONF_PRIOR: 0.15,
        CONF_DEVICE_CLASS: "occupancy",
    }
    observations = [
        dict(subentry.data)
        for subentry in hass.config_entries.async_get_entry(
            config_entry.entry_id
        ).subentries.values()
    ]
    assert observations == [
        {
            CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
            CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
            CONF_ABOVE: 2000,
            CONF_P_GIVEN_T: 0.8,
            CONF_P_GIVEN_F: 0.4,
            CONF_NAME: "Office is bright",
        },
        {
            CONF_PLATFORM: str(ObservationTypes.STATE),
            CONF_ENTITY_ID: "sensor.desktop",
            CONF_TO_STATE: "on",
            CONF_P_GIVEN_T: 0.7,
            CONF_P_GIVEN_F: 0.12,
            CONF_NAME: "Desktop on network",
        },
        {
            CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
            CONF_VALUE_TEMPLATE: (
                "{% set current_time = now().time() %}\n"
                '{% set start_time = strptime("07:00",'
                ' "%H:%M").time() %}\n'
                '{% set end_time = strptime("17:30",'
                ' "%H:%M").time() %}\n'
                "{% if start_time <= current_time"
                " <= end_time %}\nTrue\n"
                "{% else %}\nFalse\n{% endif %}"
            ),
            CONF_P_GIVEN_T: 0.55,
            CONF_P_GIVEN_F: 0.13,
            CONF_NAME: "Office hours",
        },
    ]


async def test_invalid_configs(hass: HomeAssistant) -> None:
    """Test that invalid configs are refused."""
    with patch(
        "homeassistant.components.bayesian.async_setup_entry", return_value=True
    ):
        result0 = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        assert result0["step_id"] == USER
        assert result0["type"] is FlowResultType.FORM

        # priors should never be Zero, because then the sensor can never return 'on'
        with pytest.raises(vol.Invalid) as excinfo:
            result = await hass.config_entries.flow.async_configure(
                result0["flow_id"],
                {
                    CONF_NAME: "Office occupied",
                    CONF_PROBABILITY_THRESHOLD: 50,
                    CONF_PRIOR: 0,
                },
            )
        assert CONF_PRIOR in excinfo.value.path
        assert excinfo.value.error_message == "extreme_prior_error"

        # priors should never be 100% because then the sensor can never be 'off'
        with pytest.raises(vol.Invalid) as excinfo:
            result = await hass.config_entries.flow.async_configure(
                result0["flow_id"],
                {
                    CONF_NAME: "Office occupied",
                    CONF_PROBABILITY_THRESHOLD: 50,
                    CONF_PRIOR: 100,
                },
            )
        assert CONF_PRIOR in excinfo.value.path
        assert excinfo.value.error_message == "extreme_prior_error"

        # Threshold should never be 100% because then the sensor can never be 'on'
        with pytest.raises(vol.Invalid) as excinfo:
            result = await hass.config_entries.flow.async_configure(
                result0["flow_id"],
                {
                    CONF_NAME: "Office occupied",
                    CONF_PROBABILITY_THRESHOLD: 100,
                    CONF_PRIOR: 50,
                },
            )
        assert CONF_PROBABILITY_THRESHOLD in excinfo.value.path
        assert excinfo.value.error_message == "extreme_threshold_error"

        # Threshold should never be 0 because then the sensor can never be 'off'
        with pytest.raises(vol.Invalid) as excinfo:
            result = await hass.config_entries.flow.async_configure(
                result0["flow_id"],
                {
                    CONF_NAME: "Office occupied",
                    CONF_PROBABILITY_THRESHOLD: 0,
                    CONF_PRIOR: 50,
                },
            )
        assert CONF_PROBABILITY_THRESHOLD in excinfo.value.path
        assert excinfo.value.error_message == "extreme_threshold_error"

        # Now lets submit a valid config so we can test the observation flows
        result = await hass.config_entries.flow.async_configure(
            result0["flow_id"],
            {
                CONF_NAME: "Office occupied",
                CONF_PROBABILITY_THRESHOLD: 50,
                CONF_PRIOR: 30,
            },
        )
        await hass.async_block_till_done()
        assert result.get("errors") is None

        assert result["type"] == FlowResultType.CREATE_ENTRY
        config_entry = result["result"]
        sub_flow_id = result["next_flow"][1]

        result = await hass.config_entries.subentries.async_configure(
            sub_flow_id, {"next_step_id": str(ObservationTypes.STATE)}
        )
        await hass.async_block_till_done()

        assert result["step_id"] == str(ObservationTypes.STATE)
        assert result["type"] is FlowResultType.FORM

        # Observations with a probability of 0 will create certainties
        with pytest.raises(vol.Invalid) as excinfo:
            result = await hass.config_entries.subentries.async_configure(
                result["flow_id"],
                {
                    CONF_ENTITY_ID: "sensor.work_laptop",
                    CONF_TO_STATE: "on",
                    CONF_P_GIVEN_T: 0,
                    CONF_P_GIVEN_F: 60,
                    CONF_NAME: "Work laptop on network",
                },
            )
        assert CONF_P_GIVEN_T in excinfo.value.path
        assert excinfo.value.error_message == "extreme_prob_given_error"

        # Observations with a probability of 1 will create certainties
        with pytest.raises(vol.Invalid) as excinfo:
            result = await hass.config_entries.subentries.async_configure(
                result["flow_id"],
                {
                    CONF_ENTITY_ID: "sensor.work_laptop",
                    CONF_TO_STATE: "on",
                    CONF_P_GIVEN_T: 60,
                    CONF_P_GIVEN_F: 100,
                    CONF_NAME: "Work laptop on network",
                },
            )
        assert CONF_P_GIVEN_F in excinfo.value.path
        assert excinfo.value.error_message == "extreme_prob_given_error"

        # Observations with equal probabilities have no effect
        # Try with a ObservationTypes.STATE observation
        current_step = result["step_id"]
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.work_laptop",
                CONF_TO_STATE: "on",
                CONF_P_GIVEN_T: 60,
                CONF_P_GIVEN_F: 60,
                CONF_NAME: "Work laptop on network",
            },
        )
        await hass.async_block_till_done()
        assert result["step_id"] == current_step
        assert result["errors"] == {"base": "equal_probabilities"}

        # now submit a valid result
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.work_laptop",
                CONF_TO_STATE: "on",
                CONF_P_GIVEN_T: 60,
                CONF_P_GIVEN_F: 70,
                CONF_NAME: "Work laptop on network",
            },
        )
        await hass.async_block_till_done()

        result = await hass.config_entries.subentries.async_init(
            (config_entry.entry_id, "observation"),
            context={"source": config_entries.SOURCE_USER},
        )
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
        )
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                CONF_ABOVE: 40,
                CONF_P_GIVEN_T: 85,
                CONF_P_GIVEN_F: 85,
                CONF_NAME: "Office is bright",
            },
        )
        await hass.async_block_till_done()
        assert result["errors"] == {"base": "equal_probabilities"}

        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_ENTITY_ID: "sensor.office_illuminance_lux",
                CONF_ABOVE: 40,
                CONF_P_GIVEN_T: 85,
                CONF_P_GIVEN_F: 10,
                CONF_NAME: "Office is bright",
            },
        )
        await hass.async_block_till_done()
        # Try with a ObservationTypes.TEMPLATE observation
        result = await hass.config_entries.subentries.async_init(
            (config_entry.entry_id, "observation"),
            context={"source": config_entries.SOURCE_USER},
        )
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"], {"next_step_id": str(ObservationTypes.TEMPLATE)}
        )
        current_step = result["step_id"]
        result = await hass.config_entries.subentries.async_configure(
            result["flow_id"],
            {
                CONF_VALUE_TEMPLATE: (
                    "{{ is_state('device_tracker.paulus', 'not_home') }}"
                ),
                CONF_P_GIVEN_T: 50,
                CONF_P_GIVEN_F: 50,
                CONF_NAME: "Paulus not home",
            },
        )
        await hass.async_block_till_done()
        assert result["step_id"] == current_step
        assert result["errors"] == {"base": "equal_probabilities"}
