"""Tests for the SmartThings config flow module."""
from uuid import uuid4

from aiohttp import ClientResponseError
from asynctest import Mock, patch
from pysmartthings import APIResponseError

from homeassistant import data_entry_flow
from homeassistant.components.smartthings import smartapp
from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler
from homeassistant.components.smartthings.const import (
    CONF_INSTALLED_APP_ID,
    CONF_INSTALLED_APPS,
    CONF_LOCATION_ID,
    CONF_REFRESH_TOKEN,
    DOMAIN,
)
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry, mock_coro


async def test_step_user(hass):
    """Test the access token form is shown for a user initiated flow."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    result = await flow.async_step_user()

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"


async def test_step_init(hass):
    """Test the access token form is shown for an init flow."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    result = await flow.async_step_import()

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"


async def test_base_url_not_https(hass):
    """Test the base_url parameter starts with https://."""
    hass.config.api.base_url = "http://0.0.0.0"
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"base": "base_url_not_https"}


async def test_invalid_token_format(hass):
    """Test an error is shown for invalid token formats."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    result = await flow.async_step_user({"access_token": "123456789"})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"access_token": "token_invalid_format"}


async def test_token_already_setup(hass):
    """Test an error is shown when the token is already setup."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    token = str(uuid4())
    entry = MockConfigEntry(domain=DOMAIN, data={"access_token": token})
    entry.add_to_hass(hass)

    result = await flow.async_step_user({"access_token": token})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"access_token": "token_already_setup"}


async def test_token_unauthorized(hass, smartthings_mock):
    """Test an error is shown when the token is not authorized."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    request_info = Mock(real_url="http://example.com")
    smartthings_mock.apps.side_effect = ClientResponseError(
        request_info=request_info, history=None, status=401
    )

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"access_token": "token_unauthorized"}


async def test_token_forbidden(hass, smartthings_mock):
    """Test an error is shown when the token is forbidden."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    request_info = Mock(real_url="http://example.com")
    smartthings_mock.apps.side_effect = ClientResponseError(
        request_info=request_info, history=None, status=403
    )

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"access_token": "token_forbidden"}


async def test_webhook_error(hass, smartthings_mock):
    """Test an error is when there's an error with the webhook endpoint."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    data = {"error": {}}
    request_info = Mock(real_url="http://example.com")
    error = APIResponseError(
        request_info=request_info, history=None, data=data, status=422
    )
    error.is_target_error = Mock(return_value=True)

    smartthings_mock.apps.side_effect = error

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"base": "webhook_error"}


async def test_api_error(hass, smartthings_mock):
    """Test an error is shown when other API errors occur."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    data = {"error": {}}
    request_info = Mock(real_url="http://example.com")
    error = APIResponseError(
        request_info=request_info, history=None, data=data, status=400
    )

    smartthings_mock.apps.side_effect = error

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"base": "app_setup_error"}


async def test_unknown_api_error(hass, smartthings_mock):
    """Test an error is shown when there is an unknown API error."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    request_info = Mock(real_url="http://example.com")
    smartthings_mock.apps.side_effect = ClientResponseError(
        request_info=request_info, history=None, status=404
    )

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"base": "app_setup_error"}


async def test_unknown_error(hass, smartthings_mock):
    """Test an error is shown when there is an unknown API error."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    smartthings_mock.apps.side_effect = Exception("Unknown error")

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "user"
    assert result["errors"] == {"base": "app_setup_error"}


async def test_app_created_then_show_wait_form(
    hass, app, app_oauth_client, smartthings_mock
):
    """Test SmartApp is created when one does not exist and shows wait form."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    smartthings_mock.apps.return_value = []
    smartthings_mock.create_app.return_value = (app, app_oauth_client)

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "wait_install"


async def test_cloudhook_app_created_then_show_wait_form(
    hass, app, app_oauth_client, smartthings_mock
):
    """Test SmartApp is created with a cloudhoko and shows wait form."""
    hass.config.components.add("cloud")

    # Unload the endpoint so we can reload it under the cloud.
    await smartapp.unload_smartapp_endpoint(hass)

    with patch.object(
        hass.components.cloud, "async_active_subscription", return_value=True
    ), patch.object(
        hass.components.cloud,
        "async_create_cloudhook",
        return_value=mock_coro("http://cloud.test"),
    ) as mock_create_cloudhook:

        await smartapp.setup_smartapp_endpoint(hass)

        flow = SmartThingsFlowHandler()
        flow.hass = hass
        smartthings_mock.apps.return_value = []
        smartthings_mock.create_app.return_value = (app, app_oauth_client)

        result = await flow.async_step_user({"access_token": str(uuid4())})

        assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
        assert result["step_id"] == "wait_install"
        assert mock_create_cloudhook.call_count == 1


async def test_app_updated_then_show_wait_form(
    hass, app, app_oauth_client, smartthings_mock
):
    """Test SmartApp is updated when an existing is already created."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    smartthings_mock.apps.return_value = [app]
    smartthings_mock.generate_app_oauth.return_value = app_oauth_client

    result = await flow.async_step_user({"access_token": str(uuid4())})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "wait_install"


async def test_wait_form_displayed(hass):
    """Test the wait for installation form is displayed."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass

    result = await flow.async_step_wait_install(None)

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "wait_install"


async def test_wait_form_displayed_after_checking(hass, smartthings_mock):
    """Test error is shown when the user has not installed the app."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    flow.access_token = str(uuid4())

    result = await flow.async_step_wait_install({})

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "wait_install"
    assert result["errors"] == {"base": "app_not_installed"}


async def test_config_entry_created_when_installed(
    hass, location, installed_app, smartthings_mock
):
    """Test a config entry is created once the app is installed."""
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    flow.access_token = str(uuid4())
    flow.app_id = installed_app.app_id
    flow.api = smartthings_mock
    flow.oauth_client_id = str(uuid4())
    flow.oauth_client_secret = str(uuid4())
    data = {
        CONF_REFRESH_TOKEN: str(uuid4()),
        CONF_LOCATION_ID: installed_app.location_id,
        CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
    }
    hass.data[DOMAIN][CONF_INSTALLED_APPS].append(data)

    result = await flow.async_step_wait_install({})

    assert not hass.data[DOMAIN][CONF_INSTALLED_APPS]
    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
    assert result["data"]["app_id"] == installed_app.app_id
    assert result["data"]["installed_app_id"] == installed_app.installed_app_id
    assert result["data"]["location_id"] == installed_app.location_id
    assert result["data"]["access_token"] == flow.access_token
    assert result["data"]["refresh_token"] == data[CONF_REFRESH_TOKEN]
    assert result["data"]["client_secret"] == flow.oauth_client_secret
    assert result["data"]["client_id"] == flow.oauth_client_id
    assert result["title"] == location.name


async def test_multiple_config_entry_created_when_installed(
    hass, app, locations, installed_apps, smartthings_mock
):
    """Test a config entries are created for multiple installs."""
    assert await async_setup_component(hass, "persistent_notification", {})
    flow = SmartThingsFlowHandler()
    flow.hass = hass
    flow.access_token = str(uuid4())
    flow.app_id = app.app_id
    flow.api = smartthings_mock
    flow.oauth_client_id = str(uuid4())
    flow.oauth_client_secret = str(uuid4())
    for installed_app in installed_apps:
        data = {
            CONF_REFRESH_TOKEN: str(uuid4()),
            CONF_LOCATION_ID: installed_app.location_id,
            CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
        }
        hass.data[DOMAIN][CONF_INSTALLED_APPS].append(data)
    install_data = hass.data[DOMAIN][CONF_INSTALLED_APPS].copy()

    result = await flow.async_step_wait_install({})

    assert not hass.data[DOMAIN][CONF_INSTALLED_APPS]

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
    assert result["data"]["app_id"] == installed_apps[0].app_id
    assert result["data"]["installed_app_id"] == installed_apps[0].installed_app_id
    assert result["data"]["location_id"] == installed_apps[0].location_id
    assert result["data"]["access_token"] == flow.access_token
    assert result["data"]["refresh_token"] == install_data[0][CONF_REFRESH_TOKEN]
    assert result["data"]["client_secret"] == flow.oauth_client_secret
    assert result["data"]["client_id"] == flow.oauth_client_id
    assert result["title"] == locations[0].name

    await hass.async_block_till_done()
    entries = hass.config_entries.async_entries("smartthings")
    assert len(entries) == 1
    assert entries[0].data["app_id"] == installed_apps[1].app_id
    assert entries[0].data["installed_app_id"] == installed_apps[1].installed_app_id
    assert entries[0].data["location_id"] == installed_apps[1].location_id
    assert entries[0].data["access_token"] == flow.access_token
    assert entries[0].data["client_secret"] == flow.oauth_client_secret
    assert entries[0].data["client_id"] == flow.oauth_client_id
    assert entries[0].title == locations[1].name