"""Offer API to configure the Home Assistant auth provider."""
import voluptuous as vol

from homeassistant.auth.providers import homeassistant as auth_ha
from homeassistant.components import websocket_api


WS_TYPE_CREATE = "config/auth_provider/homeassistant/create"
SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
    {
        vol.Required("type"): WS_TYPE_CREATE,
        vol.Required("user_id"): str,
        vol.Required("username"): str,
        vol.Required("password"): str,
    }
)

WS_TYPE_DELETE = "config/auth_provider/homeassistant/delete"
SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
    {vol.Required("type"): WS_TYPE_DELETE, vol.Required("username"): str}
)

WS_TYPE_CHANGE_PASSWORD = "config/auth_provider/homeassistant/change_password"
SCHEMA_WS_CHANGE_PASSWORD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
    {
        vol.Required("type"): WS_TYPE_CHANGE_PASSWORD,
        vol.Required("current_password"): str,
        vol.Required("new_password"): str,
    }
)


async def async_setup(hass):
    """Enable the Home Assistant views."""
    hass.components.websocket_api.async_register_command(
        WS_TYPE_CREATE, websocket_create, SCHEMA_WS_CREATE
    )
    hass.components.websocket_api.async_register_command(
        WS_TYPE_DELETE, websocket_delete, SCHEMA_WS_DELETE
    )
    hass.components.websocket_api.async_register_command(
        WS_TYPE_CHANGE_PASSWORD, websocket_change_password, SCHEMA_WS_CHANGE_PASSWORD
    )
    return True


def _get_provider(hass):
    """Get homeassistant auth provider."""
    for prv in hass.auth.auth_providers:
        if prv.type == "homeassistant":
            return prv

    raise RuntimeError("Provider not found")


@websocket_api.require_admin
@websocket_api.async_response
async def websocket_create(hass, connection, msg):
    """Create credentials and attach to a user."""
    provider = _get_provider(hass)
    await provider.async_initialize()

    user = await hass.auth.async_get_user(msg["user_id"])

    if user is None:
        connection.send_message(
            websocket_api.error_message(msg["id"], "not_found", "User not found")
        )
        return

    if user.system_generated:
        connection.send_message(
            websocket_api.error_message(
                msg["id"],
                "system_generated",
                "Cannot add credentials to a system generated user.",
            )
        )
        return

    try:
        await hass.async_add_executor_job(
            provider.data.add_auth, msg["username"], msg["password"]
        )
    except auth_ha.InvalidUser:
        connection.send_message(
            websocket_api.error_message(
                msg["id"], "username_exists", "Username already exists"
            )
        )
        return

    credentials = await provider.async_get_or_create_credentials(
        {"username": msg["username"]}
    )
    await hass.auth.async_link_user(user, credentials)

    await provider.data.async_save()
    connection.send_message(websocket_api.result_message(msg["id"]))


@websocket_api.require_admin
@websocket_api.async_response
async def websocket_delete(hass, connection, msg):
    """Delete username and related credential."""
    provider = _get_provider(hass)
    await provider.async_initialize()

    credentials = await provider.async_get_or_create_credentials(
        {"username": msg["username"]}
    )

    # if not new, an existing credential exists.
    # Removing the credential will also remove the auth.
    if not credentials.is_new:
        await hass.auth.async_remove_credentials(credentials)

        connection.send_message(websocket_api.result_message(msg["id"]))
        return

    try:
        provider.data.async_remove_auth(msg["username"])
        await provider.data.async_save()
    except auth_ha.InvalidUser:
        connection.send_message(
            websocket_api.error_message(
                msg["id"], "auth_not_found", "Given username was not found."
            )
        )
        return

    connection.send_message(websocket_api.result_message(msg["id"]))


@websocket_api.async_response
async def websocket_change_password(hass, connection, msg):
    """Change user password."""
    user = connection.user
    if user is None:
        connection.send_message(
            websocket_api.error_message(msg["id"], "user_not_found", "User not found")
        )
        return

    provider = _get_provider(hass)
    await provider.async_initialize()

    username = None
    for credential in user.credentials:
        if credential.auth_provider_type == provider.type:
            username = credential.data["username"]
            break

    if username is None:
        connection.send_message(
            websocket_api.error_message(
                msg["id"], "credentials_not_found", "Credentials not found"
            )
        )
        return

    try:
        await provider.async_validate_login(username, msg["current_password"])
    except auth_ha.InvalidAuth:
        connection.send_message(
            websocket_api.error_message(
                msg["id"], "invalid_password", "Invalid password"
            )
        )
        return

    await hass.async_add_executor_job(
        provider.data.change_password, username, msg["new_password"]
    )
    await provider.data.async_save()

    connection.send_message(websocket_api.result_message(msg["id"]))
