"""Test the Risco config flow."""
from unittest.mock import PropertyMock, patch

import pytest
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.risco.config_flow import (
    CannotConnectError,
    UnauthorizedError,
)
from homeassistant.components.risco.const import CONF_COMMUNICATION_DELAY, DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

from tests.common import MockConfigEntry

TEST_SITE_NAME = "test-site-name"
TEST_CLOUD_DATA = {
    "username": "test-username",
    "password": "test-password",
    "pin": "1234",
}

TEST_LOCAL_DATA = {
    "host": "test-host",
    "port": 5004,
    "pin": "1234",
}

TEST_RISCO_TO_HA = {
    "arm": "armed_away",
    "partial_arm": "armed_home",
    "A": "armed_home",
    "B": "armed_home",
    "C": "armed_night",
    "D": "armed_night",
}

TEST_HA_TO_RISCO = {
    "armed_away": "arm",
    "armed_home": "partial_arm",
    "armed_night": "C",
}

TEST_OPTIONS = {
    "scan_interval": 10,
    "code_arm_required": True,
    "code_disarm_required": True,
}


async def test_cloud_form(hass: HomeAssistant) -> None:
    """Test we get the cloud form."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] == FlowResultType.MENU

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

    assert result2["type"] == FlowResultType.FORM
    assert result2["errors"] == {}

    with patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.login",
        return_value=True,
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.site_name",
        new_callable=PropertyMock(return_value=TEST_SITE_NAME),
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.close"
    ) as mock_close, patch(
        "homeassistant.components.risco.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry:
        result3 = await hass.config_entries.flow.async_configure(
            result2["flow_id"], TEST_CLOUD_DATA
        )
        await hass.async_block_till_done()

    assert result3["type"] == FlowResultType.CREATE_ENTRY
    assert result3["title"] == TEST_SITE_NAME
    assert result3["data"] == TEST_CLOUD_DATA
    assert len(mock_setup_entry.mock_calls) == 1
    mock_close.assert_awaited_once()


@pytest.mark.parametrize(
    ("exception", "error"),
    [
        (UnauthorizedError, "invalid_auth"),
        (CannotConnectError, "cannot_connect"),
        (Exception, "unknown"),
    ],
)
async def test_cloud_error(hass: HomeAssistant, login_with_error, error) -> None:
    """Test we handle config flow errors."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result2 = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "cloud"}
    )

    with patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.close"
    ) as mock_close:
        result3 = await hass.config_entries.flow.async_configure(
            result2["flow_id"], TEST_CLOUD_DATA
        )

    mock_close.assert_awaited_once()
    assert result3["type"] == FlowResultType.FORM
    assert result3["errors"] == {"base": error}


async def test_form_cloud_already_exists(hass: HomeAssistant) -> None:
    """Test that a flow with an existing username aborts."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_CLOUD_DATA["username"],
        data=TEST_CLOUD_DATA,
    )

    entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

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

    result3 = await hass.config_entries.flow.async_configure(
        result2["flow_id"], TEST_CLOUD_DATA
    )

    assert result3["type"] == FlowResultType.ABORT
    assert result3["reason"] == "already_configured"


async def test_form_reauth(hass: HomeAssistant, cloud_config_entry) -> None:
    """Test reauthenticate."""

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_REAUTH},
        data=cloud_config_entry.data,
    )
    assert result["type"] == "form"
    assert result["errors"] == {}

    with patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.login",
        return_value=True,
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.site_name",
        new_callable=PropertyMock(return_value=TEST_SITE_NAME),
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.close",
    ), patch(
        "homeassistant.components.risco.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry:
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"], {**TEST_CLOUD_DATA, CONF_PASSWORD: "new_password"}
        )
        await hass.async_block_till_done()

    assert result2["type"] == "abort"
    assert result2["reason"] == "reauth_successful"
    assert cloud_config_entry.data[CONF_PASSWORD] == "new_password"
    assert len(mock_setup_entry.mock_calls) == 1


async def test_form_reauth_with_new_username(
    hass: HomeAssistant, cloud_config_entry
) -> None:
    """Test reauthenticate with new username."""

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_REAUTH},
        data=cloud_config_entry.data,
    )
    assert result["type"] == "form"
    assert result["errors"] == {}

    with patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.login",
        return_value=True,
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.site_name",
        new_callable=PropertyMock(return_value=TEST_SITE_NAME),
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoCloud.close",
    ), patch(
        "homeassistant.components.risco.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry:
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"], {**TEST_CLOUD_DATA, CONF_USERNAME: "new_user"}
        )
        await hass.async_block_till_done()

    assert result2["type"] == "abort"
    assert result2["reason"] == "reauth_successful"
    assert cloud_config_entry.data[CONF_USERNAME] == "new_user"
    assert cloud_config_entry.unique_id == "new_user"
    assert len(mock_setup_entry.mock_calls) == 1


async def test_local_form(hass: HomeAssistant) -> None:
    """Test we get the local form."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] == FlowResultType.MENU

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

    assert result2["type"] == FlowResultType.FORM
    assert result2["errors"] == {}

    with patch(
        "homeassistant.components.risco.config_flow.RiscoLocal.connect",
        return_value=True,
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoLocal.id",
        new_callable=PropertyMock(return_value=TEST_SITE_NAME),
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoLocal.disconnect"
    ) as mock_close, patch(
        "homeassistant.components.risco.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry:
        result3 = await hass.config_entries.flow.async_configure(
            result2["flow_id"], TEST_LOCAL_DATA
        )
        await hass.async_block_till_done()

    expected_data = {
        **TEST_LOCAL_DATA,
        **{"type": "local", CONF_COMMUNICATION_DELAY: 0},
    }
    assert result3["type"] == FlowResultType.CREATE_ENTRY
    assert result3["title"] == TEST_SITE_NAME
    assert result3["data"] == expected_data
    assert len(mock_setup_entry.mock_calls) == 1
    mock_close.assert_awaited_once()


@pytest.mark.parametrize(
    ("exception", "error"),
    [
        (UnauthorizedError, "invalid_auth"),
        (CannotConnectError, "cannot_connect"),
        (Exception, "unknown"),
    ],
)
async def test_local_error(hass: HomeAssistant, connect_with_error, error) -> None:
    """Test we handle config flow errors."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result2 = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "local"}
    )

    result3 = await hass.config_entries.flow.async_configure(
        result2["flow_id"], TEST_LOCAL_DATA
    )

    assert result3["type"] == FlowResultType.FORM
    assert result3["errors"] == {"base": error}


async def test_form_local_already_exists(hass: HomeAssistant) -> None:
    """Test that a flow with an existing host aborts."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_SITE_NAME,
        data=TEST_LOCAL_DATA,
    )

    entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

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

    with patch(
        "homeassistant.components.risco.config_flow.RiscoLocal.connect",
        return_value=True,
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoLocal.id",
        new_callable=PropertyMock(return_value=TEST_SITE_NAME),
    ), patch(
        "homeassistant.components.risco.config_flow.RiscoLocal.disconnect",
    ):
        result3 = await hass.config_entries.flow.async_configure(
            result2["flow_id"], TEST_LOCAL_DATA
        )

    assert result3["type"] == FlowResultType.ABORT
    assert result3["reason"] == "already_configured"


async def test_options_flow(hass: HomeAssistant) -> None:
    """Test options flow."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_CLOUD_DATA["username"],
        data=TEST_CLOUD_DATA,
    )

    entry.add_to_hass(hass)

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "init"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input=TEST_OPTIONS,
    )
    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "risco_to_ha"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input=TEST_RISCO_TO_HA,
    )

    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "ha_to_risco"

    with patch("homeassistant.components.risco.async_setup_entry", return_value=True):
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input=TEST_HA_TO_RISCO,
        )

    assert result["type"] == FlowResultType.CREATE_ENTRY
    assert entry.options == {
        **TEST_OPTIONS,
        "risco_states_to_ha": TEST_RISCO_TO_HA,
        "ha_states_to_risco": TEST_HA_TO_RISCO,
    }


async def test_ha_to_risco_schema(hass: HomeAssistant) -> None:
    """Test that the schema for the ha-to-risco mapping step is generated properly."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        unique_id=TEST_CLOUD_DATA["username"],
        data=TEST_CLOUD_DATA,
    )

    entry.add_to_hass(hass)

    result = await hass.config_entries.options.async_init(entry.entry_id)

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input=TEST_OPTIONS,
    )
    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input=TEST_RISCO_TO_HA,
    )

    # Test an HA state that isn't used
    with pytest.raises(vol.error.MultipleInvalid):
        await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={**TEST_HA_TO_RISCO, "armed_custom_bypass": "D"},
        )

    # Test a combo that can't be selected
    with pytest.raises(vol.error.MultipleInvalid):
        await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={**TEST_HA_TO_RISCO, "armed_night": "A"},
        )