"""Test the cloud component."""

from unittest.mock import patch

import pytest

from homeassistant.components import cloud
from homeassistant.components.cloud.const import DOMAIN
from homeassistant.components.cloud.prefs import STORAGE_KEY
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Context
from homeassistant.exceptions import Unauthorized
from homeassistant.setup import async_setup_component


async def test_constructor_loads_info_from_config(hass):
    """Test non-dev mode loads info from SERVERS constant."""
    with patch("hass_nabucasa.Cloud.initialize"):
        result = await async_setup_component(
            hass,
            "cloud",
            {
                "http": {},
                "cloud": {
                    cloud.CONF_MODE: cloud.MODE_DEV,
                    "cognito_client_id": "test-cognito_client_id",
                    "user_pool_id": "test-user_pool_id",
                    "region": "test-region",
                    "relayer_server": "test-relayer-server",
                    "accounts_server": "test-acounts-server",
                    "cloudhook_server": "test-cloudhook-server",
                    "remote_sni_server": "test-remote-sni-server",
                    "alexa_server": "test-alexa-server",
                    "acme_server": "test-acme-server",
                    "remotestate_server": "test-remotestate-server",
                },
            },
        )
        assert result

    cl = hass.data["cloud"]
    assert cl.mode == cloud.MODE_DEV
    assert cl.cognito_client_id == "test-cognito_client_id"
    assert cl.user_pool_id == "test-user_pool_id"
    assert cl.region == "test-region"
    assert cl.relayer_server == "test-relayer-server"
    assert cl.iot.ws_server_url == "wss://test-relayer-server/websocket"
    assert cl.accounts_server == "test-acounts-server"
    assert cl.cloudhook_server == "test-cloudhook-server"
    assert cl.alexa_server == "test-alexa-server"
    assert cl.acme_server == "test-acme-server"
    assert cl.remotestate_server == "test-remotestate-server"


async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user):
    """Setup cloud component and test services."""
    cloud = hass.data[DOMAIN]

    assert hass.services.has_service(DOMAIN, "remote_connect")
    assert hass.services.has_service(DOMAIN, "remote_disconnect")

    with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect:
        await hass.services.async_call(DOMAIN, "remote_connect", blocking=True)

    assert mock_connect.called
    assert cloud.client.remote_autostart

    with patch("hass_nabucasa.remote.RemoteUI.disconnect") as mock_disconnect:
        await hass.services.async_call(DOMAIN, "remote_disconnect", blocking=True)

    assert mock_disconnect.called
    assert not cloud.client.remote_autostart

    # Test admin access required
    non_admin_context = Context(user_id=hass_read_only_user.id)

    with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect, pytest.raises(
        Unauthorized
    ):
        await hass.services.async_call(
            DOMAIN, "remote_connect", blocking=True, context=non_admin_context
        )

    assert mock_connect.called is False

    with patch(
        "hass_nabucasa.remote.RemoteUI.disconnect"
    ) as mock_disconnect, pytest.raises(Unauthorized):
        await hass.services.async_call(
            DOMAIN, "remote_disconnect", blocking=True, context=non_admin_context
        )

    assert mock_disconnect.called is False


async def test_startup_shutdown_events(hass, mock_cloud_fixture):
    """Test if the cloud will start on startup event."""
    with patch("hass_nabucasa.Cloud.stop") as mock_stop:
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
        await hass.async_block_till_done()

    assert mock_stop.called


async def test_setup_existing_cloud_user(hass, hass_storage):
    """Test setup with API push default data."""
    user = await hass.auth.async_create_system_user("Cloud test")
    hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": user.id}}
    with patch("hass_nabucasa.Cloud.initialize"):
        result = await async_setup_component(
            hass,
            "cloud",
            {
                "http": {},
                "cloud": {
                    cloud.CONF_MODE: cloud.MODE_DEV,
                    "cognito_client_id": "test-cognito_client_id",
                    "user_pool_id": "test-user_pool_id",
                    "region": "test-region",
                    "relayer_server": "test-relayer-serer",
                },
            },
        )
        assert result

    assert hass_storage[STORAGE_KEY]["data"]["cloud_user"] == user.id


async def test_on_connect(hass, mock_cloud_fixture):
    """Test cloud on connect triggers."""
    cl = hass.data["cloud"]

    assert len(cl.iot._on_connect) == 3

    assert len(hass.states.async_entity_ids("binary_sensor")) == 0

    cloud_states = []

    def handle_state(cloud_state):
        nonlocal cloud_states
        cloud_states.append(cloud_state)

    cloud.async_listen_connection_change(hass, handle_state)

    assert "async_setup" in str(cl.iot._on_connect[-1])
    await cl.iot._on_connect[-1]()
    await hass.async_block_till_done()

    assert len(hass.states.async_entity_ids("binary_sensor")) == 1

    with patch("homeassistant.helpers.discovery.async_load_platform") as mock_load:
        await cl.iot._on_connect[-1]()
        await hass.async_block_till_done()

    assert len(mock_load.mock_calls) == 0

    assert len(cloud_states) == 1
    assert cloud_states[-1] == cloud.CloudConnectionState.CLOUD_CONNECTED

    assert len(cl.iot._on_disconnect) == 2
    assert "async_setup" in str(cl.iot._on_disconnect[-1])
    await cl.iot._on_disconnect[-1]()
    await hass.async_block_till_done()

    assert len(cloud_states) == 2
    assert cloud_states[-1] == cloud.CloudConnectionState.CLOUD_DISCONNECTED


async def test_remote_ui_url(hass, mock_cloud_fixture):
    """Test getting remote ui url."""
    cl = hass.data["cloud"]

    # Not logged in
    with pytest.raises(cloud.CloudNotAvailable):
        cloud.async_remote_ui_url(hass)

    with patch.object(cloud, "async_is_logged_in", return_value=True):
        # Remote not enabled
        with pytest.raises(cloud.CloudNotAvailable):
            cloud.async_remote_ui_url(hass)

        with patch.object(cl.remote, "connect"):
            await cl.client.prefs.async_update(remote_enabled=True)
            await hass.async_block_till_done()

        # No instance domain
        with pytest.raises(cloud.CloudNotAvailable):
            cloud.async_remote_ui_url(hass)

        # Remote finished initializing
        cl.client.prefs._prefs["remote_domain"] = "example.com"

        assert cloud.async_remote_ui_url(hass) == "https://example.com"