"""Implement the auth feature from Hass.io for Add-ons."""
from ipaddress import ip_address
import logging
import os

from aiohttp import web
from aiohttp.web_exceptions import (
    HTTPInternalServerError,
    HTTPNotFound,
    HTTPUnauthorized,
)
import voluptuous as vol

from homeassistant.auth.models import User
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.const import KEY_HASS_USER, KEY_REAL_IP
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType

from .const import ATTR_ADDON, ATTR_PASSWORD, ATTR_USERNAME

_LOGGER = logging.getLogger(__name__)


SCHEMA_API_AUTH = vol.Schema(
    {
        vol.Required(ATTR_USERNAME): cv.string,
        vol.Required(ATTR_PASSWORD): cv.string,
        vol.Required(ATTR_ADDON): cv.string,
    },
    extra=vol.ALLOW_EXTRA,
)

SCHEMA_API_PASSWORD_RESET = vol.Schema(
    {vol.Required(ATTR_USERNAME): cv.string, vol.Required(ATTR_PASSWORD): cv.string},
    extra=vol.ALLOW_EXTRA,
)


@callback
def async_setup_auth_view(hass: HomeAssistantType, user: User):
    """Auth setup."""
    hassio_auth = HassIOAuth(hass, user)
    hassio_password_reset = HassIOPasswordReset(hass, user)

    hass.http.register_view(hassio_auth)
    hass.http.register_view(hassio_password_reset)


class HassIOBaseAuth(HomeAssistantView):
    """Hass.io view to handle auth requests."""

    def __init__(self, hass: HomeAssistantType, user: User):
        """Initialize WebView."""
        self.hass = hass
        self.user = user

    def _check_access(self, request: web.Request):
        """Check if this call is from Supervisor."""
        # Check caller IP
        hassio_ip = os.environ["HASSIO"].split(":")[0]
        if request[KEY_REAL_IP] != ip_address(hassio_ip):
            _LOGGER.error("Invalid auth request from %s", request[KEY_REAL_IP])
            raise HTTPUnauthorized()

        # Check caller token
        if request[KEY_HASS_USER].id != self.user.id:
            _LOGGER.error("Invalid auth request from %s", request[KEY_HASS_USER].name)
            raise HTTPUnauthorized()

    def _get_provider(self):
        """Return Homeassistant auth provider."""
        prv = self.hass.auth.get_auth_provider("homeassistant", None)
        if prv is not None:
            return prv

        _LOGGER.error("Can't find Home Assistant auth.")
        raise HTTPNotFound()


class HassIOAuth(HassIOBaseAuth):
    """Hass.io view to handle auth requests."""

    name = "api:hassio:auth"
    url = "/api/hassio_auth"

    @RequestDataValidator(SCHEMA_API_AUTH)
    async def post(self, request, data):
        """Handle auth requests."""
        self._check_access(request)

        await self._check_login(data[ATTR_USERNAME], data[ATTR_PASSWORD])
        return web.Response(status=200)

    async def _check_login(self, username, password):
        """Check User credentials."""
        provider = self._get_provider()

        try:
            await provider.async_validate_login(username, password)
        except HomeAssistantError:
            raise HTTPUnauthorized() from None


class HassIOPasswordReset(HassIOBaseAuth):
    """Hass.io view to handle password reset requests."""

    name = "api:hassio:auth:password:reset"
    url = "/api/hassio_auth/password_reset"

    @RequestDataValidator(SCHEMA_API_PASSWORD_RESET)
    async def post(self, request, data):
        """Handle password reset requests."""
        self._check_access(request)

        await self._change_password(data[ATTR_USERNAME], data[ATTR_PASSWORD])
        return web.Response(status=200)

    async def _change_password(self, username, password):
        """Check User credentials."""
        provider = self._get_provider()

        try:
            await self.hass.async_add_executor_job(
                provider.data.change_password, username, password
            )
            await provider.data.async_save()
        except HomeAssistantError:
            raise HTTPInternalServerError()
