# -*- coding: utf-8 -*-
from functools import wraps
from datetime import datetime
from flask import current_app, request
from flask_restful import abort
from werkzeug.exceptions import NotFound

from .models import (
    APIUser,
    APIRole,
)


def requires_auth(role=None):
    """Validates APIUser credentials.

    Force apiuser and apikey parameters, validates the key and add the user to the request.
    Optionally, you can add a 'role' parameter to enforce that the user has that parameter.
    Role can be a string or a callable returning a string.
    """

    def decorator(f):
        required_role = role() if callable(role) else role

        def _get_api_user_role(referer_user=None):
            # Authentication phase
            api_user_header = current_app.config.get('API_AUTH_API_USER', 'ApiAuth-ApiUser')
            username = request.headers[api_user_header]
            user = APIUser.manager.get(username)

            api_key_header = request.headers[current_app.config.get('API_AUTH_API_KEY', 'ApiAuth-ApiKey')]
            verify_password = user.verify_password(api_key_header)
            if not verify_password or not user.is_active:
                abort(401)
            user.update_data({'last_login': datetime.now()})
            user.save()

            # Authorization phase
            if user.is_superuser and referer_user:
                user = APIUser.manager.get(referer_user)
            role = _validate_role(user)

            return user, role

        def _validate_role(user):
            if not user.roles:
                abort(401)
            if len(user.roles) > 1:
                abort(409, message='Too many roles assigned', status=409)
            role = APIRole.manager.get(user.roles[0])
            if role and not role.is_active or not role.is_valid():
                abort(403)
            if required_role:
                role_object = APIRole.manager.get(required_role)
                if role_object and role_object.key not in user.roles or not role_object.is_active:
                    abort(403)
            return role

        @wraps(f)
        def decorated(*args, **kwargs):
            try:
                api_referer_header = current_app.config.get('API_AUTH_API_REFERER', 'ApiAuth-Referer')
                referer = request.headers.get(api_referer_header)
                user, role = _get_api_user_role(referer_user=referer)
            except (KeyError, NotFound):
                abort(401)
            request.user = user
            request.permissions = role.permissions
            return f(*args, **kwargs)
        return decorated
    return decorator


def get_admin_role():
    return current_app.config.get('API_AUTH_ADMIN_ROLE', 'admin')


def require_admin_role():
    """
    Lazy resolution of the administrative roles.
    Returns the app configuration option for API_AUTH_ADMIN_ROLE
    or "admin".

    """
    return requires_auth(role=get_admin_role)
