import asyncio
import json

from api.apilogger.internal_logger import LogApplicationInfo, LogApplicationWarn, LogApplicationDebug, LogApiError
from api.vars import current_user, headers, tid
from api.settings import API_VERSION, RBAC_HOST
from api.client_http import request


async def create_role(project_scope=None, template="1",
                      assigned_by="/v2/people/1", assigned_to=current_user.get()) -> str:
    LogApplicationInfo("Creating a Role via Pitboss.", tid=tid.get(), user=current_user.get())

    url = RBAC_HOST + '/{}/rbac/roles'.format(API_VERSION)
    body = {
        "fromTemplate": '/{0}/profile/roles/templates/{1}'.format(API_VERSION, str(template)),
        "projectScope": project_scope,
        "assignedBy": assigned_by,
        "assignedTo": assigned_to
    }

    try:
        resp_json, resp_status, resp_headers = request("POST", url, data=body, headers=headers.get())
    except Exception as e:
        LogApiError("Failed to create role. Error: {}".format(e), tid=tid.get(), user=current_user.get())
        raise

    try:
        role = resp_json['refs']['self']
        LogApplicationDebug("Successfully created Role URI: {}".format(role), tid=tid.get(), user=current_user.get())
    except KeyError:
        LogApplicationWarn("New role was not returned in the response body.", tid=tid.get(), user=current_user.get())
        raise

    await grant_access(project_scope, project_scope, headers.get())
    return role


async def get_all_roles() -> dict:
    url = RBAC_HOST + '/{}/rbac/roles'.format(API_VERSION)
    LogApplicationDebug("Getting All Roles. Calling {}".format(url), tid=tid.get(), user=current_user.get())

    try:
        data, status, heads = await request("GET", url, headers=headers.get())
    except Exception:
        raise

    return data


async def get_access_maps(roles=None, section='all') -> list:
    access_maps = []
    fixed = []
    wildcard = []

    for role in roles:
        url = RBAC_HOST + role + '/access'

        try:
            data, status, heads = await request("GET", url, headers=headers.get())
        except Exception:
            raise

        if status > 399 or heads['Content-Type'] != 'application/json':
            LogApplicationWarn("There was an issue returning the access maps from RBAC. "
                               "Response Content-Type: {0}; Response Status: {1};"
                               .format(heads['Content-Type'], status), tid=tid.get(), user=current_user.get())
            continue

        access_maps.append(data)

    if section == 'all':
        return access_maps
    elif section == 'fixed':
        for am in access_maps:
            fixed.append(am['Fixed'])
        return fixed
    elif section == 'wildcard':
        for am in access_maps:
            wildcard.append(am['Wildcard'])
        return wildcard


async def get_all_role_scopes(findstring=None) -> list:
    LogApplicationInfo("Getting All Role Scopes.")
    scopes = []
    data = await get_all_roles()

    if not data:
        return scopes

    for role in data:
        roleurl = RBAC_HOST + role
        LogApplicationDebug("Calling Pitboss for role {0} at URL {1}".format(role, roleurl),
                            tid=tid.get(), user=current_user.get())

        try:
            data, status, heads = await request("GET", roleurl, headers=headers.get())
        except Exception:
            raise

        if status > 399 or heads['Content-Type'] != 'application/json':
            LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}"
                               .format(heads['Content-Type'], status), tid=tid.get(), user=current_user.get())
            continue

        rolescope = data["projectScope"]

        if findstring is None:
            LogApplicationDebug("Findstring is none. Role Scope: {0}".format(rolescope),
                                tid=tid.get(), user=current_user.get())
            scopes.append(rolescope)
        elif rolescope.find(findstring) != -1:
            LogApplicationDebug("Matching role scope to project: {0}".format(rolescope),
                                tid=tid.get(), user=current_user.get())
            scopes.append(rolescope)
        else:
            LogApplicationDebug("Non-matching role scope to project: {0}".format(rolescope),
                                tid=tid.get(), user=current_user.get())
            continue

    return scopes


async def grant_access(path=None, scope=None, methods='all'):
    """

    :param path: Path to grant access to
    :param scope: Scope that the role is associated with
    :param methods: Either 'all' or a list[] of HTTP methods to grant access to
    :return: None
    """
    LogApplicationInfo("Granting Access via Pitboss.", tid=tid.get(), user=current_user.get())
    url = RBAC_HOST + "/v2/rbac/update" + path + "/*"
    LogApplicationDebug("Pitboss URL to be called: {0}".format(url), tid=tid.get(), user=current_user.get())
    body = {
        "scope": scope
    }

    access_methods = []
    available_methods = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'OPTIONS']
    if methods == 'all':
        access_methods = available_methods
    elif type(methods) is list:
        for m in methods:
            if m in available_methods:
                access_methods.append(m)
    else:
        raise Exception("Invalid methods format")

    for me in access_methods:
        try:
            data, status, heads = await request(me, url, data=body, headers=headers.get())
        except Exception as e:
            LogApiError("Unable to grant {0} access. Error: {1}".format(me, e), tid=tid.get(), user=current_user.get())
            await asyncio.sleep(.2)
            continue

        await asyncio.sleep(.2)

    return


async def get_related_role_scopes(findstring=None) -> list:
    LogApplicationInfo("Getting All Role Scopes.", tid=tid.get(), user=current_user.get())
    scopes = []
    data = await get_all_roles()

    for role in data:
        roleurl = RBAC_HOST + role
        LogApplicationInfo("Calling Pitboss for role {0} at URL {1}".format(role, roleurl))

        try:
            data, status, heads = await request("GET", roleurl, headers=headers.get(),
                                                tid=tid.get(), user=current_user.get())
        except Exception:
            LogApiError("Unable to get role scope for role {}".format(role), tid=tid.get(), user=current_user.get())
            continue

        if status > 399 or heads['Content-Type'] != 'application/json':
            LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}"
                               .format(heads['Content-Type'], status), tid=tid.get(), user=current_user.get())
            continue

        rolescope = data["projectScope"]

        if findstring is None:
            scopes.append(rolescope)
        elif rolescope.find(findstring) != -1:
            scopes.append(rolescope)
        else:
            continue

    return scopes


async def autocollab(data=None, scope=None) -> list:
    url = RBAC_HOST + '/{0}/rbac/collaborate'.format(API_VERSION) + scope

    try:
        role_list, status, heads = await request(method="POST", url=url, data=data, headers=headers.get())
    except Exception as e:
        LogApiError("Something went wrong calling the autoupdater: {0}".format(e))
        raise
    else:
        if status > 399:
            LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}; Response text: {2}"
                               .format(heads["Content-Type"], status, json.dumps(role_list, indent=2)),
                               tid=tid.get(), user=current_user.get())
        else:
            LogApplicationInfo("Printing Responses for Autoupdate. Status: {0}  Text: {1}"
                               .format(status, json.dumps(role_list, indent=2)),
                               tid=tid.get(), user=current_user.get())
            LogApplicationDebug("Role List Length: {0} and Type: {1}".format(len(role_list), type(role_list)),
                                tid=tid.get(), user=current_user.get())

            for role in role_list:
                LogApplicationDebug("Role in role list returned by autocollab: {0}".format(role),
                                    tid=tid.get(), user=current_user.get())

    await asyncio.sleep(.2)
    return role_list


async def get_sys_token(people_id):
    """

    :param people_id:
    :return:
    """
    systoken_url = RBAC_HOST + '/v2/rbac/systoken'
    systoken_body = {
        "people_id": people_id
    }

    try:
        systok, status, heads = await request(method="POST", url=systoken_url, data=systoken_body,
                                              headers=headers.get())
    except Exception as e:
        LogApiError("Something went wrong retrieving the systoken: {0}".format(e))
        raise

    if status > 399:
        LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}; Response text: {2}"
                           .format(heads["Content-Type"], status, json.dumps(systok, indent=2)),
                           tid=tid.get(), user=current_user.get())
    else:
        LogApplicationInfo("Printing Responses for Systoken. Status: {0}  Text: {1}"
                           .format(status, json.dumps(systok, indent=2)),
                           tid=tid.get(), user=current_user.get())

    return systok

import asyncio
import json

from api.apilogger.internal_logger import LogApplicationInfo, LogApplicationWarn, LogApplicationDebug, LogApiError
from api.vars import current_user, headers, tid
from api.settings import API_VERSION, RBAC_HOST
from api.client_http import request


async def create_role(project_scope=None, template="1",
                      assigned_by="/v2/people/1", assigned_to=current_user.get()) -> str:
    LogApplicationInfo("Creating a Role via Pitboss.", tid=tid.get(), user=current_user.get())

    url = RBAC_HOST + '/{}/rbac/roles'.format(API_VERSION)
    body = {
        "fromTemplate": '/{0}/profile/roles/templates/{1}'.format(API_VERSION, str(template)),
        "projectScope": project_scope,
        "assignedBy": assigned_by,
        "assignedTo": assigned_to
    }

    try:
        resp_json, resp_status, resp_headers = request("POST", url, data=body, headers=headers.get())
    except Exception as e:
        LogApiError("Failed to create role. Error: {}".format(e), tid=tid.get(), user=current_user.get())
        raise

    try:
        role = resp_json['refs']['self']
        LogApplicationDebug("Successfully created Role URI: {}".format(role), tid=tid.get(), user=current_user.get())
    except KeyError:
        LogApplicationWarn("New role was not returned in the response body.", tid=tid.get(), user=current_user.get())
        raise

    await grant_access(project_scope, project_scope, headers.get())
    return role


async def get_all_roles() -> dict:
    url = RBAC_HOST + '/{}/rbac/roles'.format(API_VERSION)
    LogApplicationDebug("Getting All Roles. Calling {}".format(url), tid=tid.get(), user=current_user.get())

    try:
        data, status, heads = await request("GET", url, headers=headers.get())
    except Exception:
        raise

    return data


async def get_access_maps(roles=None, section='all') -> list:
    access_maps = []
    fixed = []
    wildcard = []

    for role in roles:
        url = RBAC_HOST + role + '/access'

        try:
            data, status, heads = await request("GET", url, headers=headers.get())
        except Exception:
            raise

        if status > 399 or heads['Content-Type'] != 'application/json':
            LogApplicationWarn("There was an issue returning the access maps from RBAC. "
                               "Response Content-Type: {0}; Response Status: {1};"
                               .format(heads['Content-Type'], status), tid=tid.get(), user=current_user.get())
            continue

        access_maps.append(data)

    if section == 'all':
        return access_maps
    elif section == 'fixed':
        for am in access_maps:
            fixed.append(am['Fixed'])
        return fixed
    elif section == 'wildcard':
        for am in access_maps:
            wildcard.append(am['Wildcard'])
        return wildcard


async def get_all_role_scopes(findstring=None) -> list:
    LogApplicationInfo("Getting All Role Scopes.")
    scopes = []
    data = await get_all_roles()

    if not data:
        return scopes

    for role in data:
        roleurl = RBAC_HOST + role
        LogApplicationDebug("Calling Pitboss for role {0} at URL {1}".format(role, roleurl),
                            tid=tid.get(), user=current_user.get())

        try:
            data, status, heads = await request("GET", roleurl, headers=headers.get())
        except Exception:
            raise

        if status > 399 or heads['Content-Type'] != 'application/json':
            LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}"
                               .format(heads['Content-Type'], status), tid=tid.get(), user=current_user.get())
            continue

        rolescope = data["projectScope"]

        if findstring is None:
            LogApplicationDebug("Findstring is none. Role Scope: {0}".format(rolescope),
                                tid=tid.get(), user=current_user.get())
            scopes.append(rolescope)
        elif rolescope.find(findstring) != -1:
            LogApplicationDebug("Matching role scope to project: {0}".format(rolescope),
                                tid=tid.get(), user=current_user.get())
            scopes.append(rolescope)
        else:
            LogApplicationDebug("Non-matching role scope to project: {0}".format(rolescope),
                                tid=tid.get(), user=current_user.get())
            continue

    return scopes


async def grant_access(path=None, scope=None, methods='all'):
    """

    :param path: Path to grant access to
    :param scope: Scope that the role is associated with
    :param methods: Either 'all' or a list[] of HTTP methods to grant access to
    :return: None
    """
    LogApplicationInfo("Granting Access via Pitboss.", tid=tid.get(), user=current_user.get())
    url = RBAC_HOST + "/v2/rbac/update" + path + "/*"
    LogApplicationDebug("Pitboss URL to be called: {0}".format(url), tid=tid.get(), user=current_user.get())
    body = {
        "scope": scope
    }

    access_methods = []
    available_methods = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'OPTIONS']
    if methods == 'all':
        access_methods = available_methods
    elif type(methods) is list:
        for m in methods:
            if m in available_methods:
                access_methods.append(m)
    else:
        raise Exception("Invalid methods format")

    for me in access_methods:
        try:
            data, status, heads = await request(me, url, data=body, headers=headers.get())
        except Exception as e:
            LogApiError("Unable to grant {0} access. Error: {1}".format(me, e), tid=tid.get(), user=current_user.get())
            await asyncio.sleep(.2)
            continue

        await asyncio.sleep(.2)

    return


async def get_related_role_scopes(findstring=None) -> list:
    LogApplicationInfo("Getting All Role Scopes.", tid=tid.get(), user=current_user.get())
    scopes = []
    data = await get_all_roles()

    for role in data:
        roleurl = RBAC_HOST + role
        LogApplicationInfo("Calling Pitboss for role {0} at URL {1}".format(role, roleurl))

        try:
            data, status, heads = await request("GET", roleurl, headers=headers.get(),
                                                tid=tid.get(), user=current_user.get())
        except Exception:
            LogApiError("Unable to get role scope for role {}".format(role), tid=tid.get(), user=current_user.get())
            continue

        if status > 399 or heads['Content-Type'] != 'application/json':
            LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}"
                               .format(heads['Content-Type'], status), tid=tid.get(), user=current_user.get())
            continue

        rolescope = data["projectScope"]

        if findstring is None:
            scopes.append(rolescope)
        elif rolescope.find(findstring) != -1:
            scopes.append(rolescope)
        else:
            continue

    return scopes


async def autocollab(data=None, scope=None) -> list:
    url = RBAC_HOST + '/{0}/rbac/collaborate'.format(API_VERSION) + scope

    try:
        role_list, status, heads = await request(method="POST", url=url, data=data, headers=headers.get())
    except Exception as e:
        LogApiError("Something went wrong calling the autoupdater: {0}".format(e))
        raise
    else:
        if status > 399:
            LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}; Response text: {2}"
                               .format(heads["Content-Type"], status, json.dumps(role_list, indent=2)),
                               tid=tid.get(), user=current_user.get())
        else:
            LogApplicationInfo("Printing Responses for Autoupdate. Status: {0}  Text: {1}"
                               .format(status, json.dumps(role_list, indent=2)),
                               tid=tid.get(), user=current_user.get())
            LogApplicationDebug("Role List Length: {0} and Type: {1}".format(len(role_list), type(role_list)),
                                tid=tid.get(), user=current_user.get())

            for role in role_list:
                LogApplicationDebug("Role in role list returned by autocollab: {0}".format(role),
                                    tid=tid.get(), user=current_user.get())

    await asyncio.sleep(.2)
    return role_list


async def get_sys_token(people_id):
    """

    :param people_id:
    :return:
    """
    systoken_url = RBAC_HOST + '/v2/rbac/systoken'
    systoken_body = {
        "people_id": people_id
    }

    try:
        systok, status, heads = await request(method="POST", url=systoken_url, data=systoken_body,
                                              headers=headers.get())
    except Exception as e:
        LogApiError("Something went wrong retrieving the systoken: {0}".format(e))
        raise

    if status > 399:
        LogApplicationWarn("Response Content-Type: {0}; Response Status: {1}; Response text: {2}"
                           .format(heads["Content-Type"], status, json.dumps(systok, indent=2)),
                           tid=tid.get(), user=current_user.get())
    else:
        LogApplicationInfo("Printing Responses for Systoken. Status: {0}  Text: {1}"
                           .format(status, json.dumps(systok, indent=2)),
                           tid=tid.get(), user=current_user.get())

    return systok

:param path: Path to grant access to     :param scope: Scope that the role is associated with     :param methods: Either 'all' or a list[] of HTTP methods to grant access to     :return: None :param people_id:     :return: