from flask import request
from auth.login_token import token_login
from db import cursor
from util import all_def, all_notdef
import valid

ranks = ["none", "user", "moderator", "admin", "bot"]


def util_two_id(type="user"):
    '''
    type?: "user" | "game"
    ! only used internally !
    func(token_id?: str, explicit_id?: str)

    This decorator doesn't check for hierarchy constraints, but does
    make sure that token_id or explicit_id are valid user_id's
    '''
    def decorator(func):
        def wrapper():
            token_id = None
            explicit_id = None

            token = request.cookies.get("token") or ""
            if token: token_id = token_login(token)

            data = request.get_json()
            if data: explicit_id = data.get("id")

            # if there's an explicit_id, validate it using `type`
            if explicit_id and \
               not valid.validate(explicit_id, type):
                explicit_id = None

            return func(token_id, explicit_id)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator


def two_person(func):
    '''
    endpoint should have two parameters:
    endpoint(user_1_id: str, user_2_id: str)

    no authentication, just runs endpoint() if both token_id and
    explicit_id are present from @util_two_id.
    '''
    @util_two_id("user")
    def wrapper(token_id, explicit_id):
        if not all_def([token_id, explicit_id]):
            return "", 400

        return func(token_id, explicit_id)

    wrapper.__name__ = func.__name__
    return wrapper


def one_person(func):
    '''
    endpoint should have two parameters:
    endpoint(user_id: str, viewer?: str)

    uses json data id with token_login id as fallback
    doesn't check for authentication
    expects that func takes these arguments: (user_id, viewer?)
    '''
    @util_two_id("user")
    def wrapper(token_id, explicit_id):
        if all_notdef([token_id, explicit_id]):
            return "", 400

        return func(explicit_id or token_id, token_id)

    wrapper.__name__ = func.__name__
    return wrapper


def game_id_with_viewer(func):
    '''
    endpoint should have two parameters:
    endpoint(game_id: str, viewer?: str)
    '''
    @util_two_id("game")
    def wrapper(token_id, game_id):
        if all_notdef([token_id, game_id]):
            return "", 400

        return func(game_id, token_id)

    wrapper.__name__ = func.__name__
    return wrapper


def auth_required(level):
    '''
    level = "none" | "user" | "moderator" | "admin" | "bot"
    endpoint should have one parameter for the user_id of the request author:
    endpoint(user_id: str) # `user_id` can only be `None` when `level == "none"`

    @auth_required function decorator (use after @flask.Blueprint.route() decorator)
    This decorator only runs endpoint() if token_id from
    @util_two_id is not None and passes hierarchy constraints
    '''
    def decorator(func):
        @util_two_id("user")
        def wrapper(token_id, explicit_id):
            if not token_id:
                if level == ranks[0]:
                    return func(None)
                else:
                    return "", 400

            user_rank_text = cursor.execute(
                "select type from users where user_id = ?", [token_id]
            ).fetchone()[0]

            required_rank = ranks.index(level)
            user_rank = ranks.index(user_rank_text)
            if required_rank > user_rank: return "", 403

            return func(token_id)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator


def io_auth_required(level):
    '''
    level = "none" | "user" | "moderator" | "admin" | "bot"
    endpoint should have two parameters:
    endpoint(data: socket.io.data, user_id: str) # `user_id` can only be `None` when `level == "none"`

    uses the @auth_required decorator, but is only for use with
    @io.on decorators
    '''
    def decorator(func):
        # data is the original @io.on data
        def wrapper(data={}):

            token = request.cookies.get("token") or ""
            user_id = token_login(token)

            if not user_id:
                if level == ranks[0]:
                    return func(data, None)
                else:
                    return

            return func(data, user_id)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator