# Copyright (c) 2020 CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com

from collections import defaultdict
from collections.abc import Callable, Iterable, Mapping

from toolz.dicttoolz import get_in

from czc.exceptions import HTTPResponseException
from czc.logging import get_logger
from czc.string import camel_to_snake, vacuum_unpack

logger = get_logger()


def get_allowed_cloud_account_ids(event: Mapping) -> Iterable[str]:
    return unpack_acls(event)['cloud_account_ids']


def has_wildcard_access(allowed_cloud_account_ids: Iterable[str]) -> bool:
    return '*' in allowed_cloud_account_ids


def path_authorizer(params: Iterable) -> Callable:
    def path_authorizer_handler(handler):
        def handler_wrapper(event, context, **kwargs):
            try:
                valid_params = authorize_path(event, params)
                return handler(event, context, **{**kwargs, **valid_params})

            except PathAuthorizationException as exc:
                return {
                    'body': {
                        'error': exc.body,
                    },
                    'statusCode': 403,
                }

        return handler_wrapper

    return path_authorizer_handler


def authorize_path(event: Mapping, params: Iterable, snake_auth_param: bool = True, snake_out_param: bool = True) -> Mapping:
    def auth(param):
        path_param_value = get_in(['pathParameters', param], event)
        auth_param_value = get_in(['requestContext', 'authorizer', camel_to_snake(param) if snake_auth_param else param], event)
        if path_param_value != auth_param_value:
            message = f'{param} {auth_param_value} attempting to access data for {param} {path_param_value}'
            logger.warning(message, exc_info=True)
            raise PathAuthorizationException('User not authorized', param)
        return path_param_value

    return {(camel_to_snake(param) if snake_out_param else param): auth(param) for param in params}


organization_path_authorizer = path_authorizer(['organizationId'])


def authorize_organizer(handler: Callable) -> Callable:
    def handler_wrapper(event, context, **kwargs):
        allowed_cloud_account_ids = unpack_acls(event)['cloud_account_ids']
        if not has_wildcard_access(allowed_cloud_account_ids):
            logger.warning('User attempting to access an organizer-only endpoint')
            return {
                'body': {
                    'error': 'User not authorized.',
                },
                'statusCode': 403,
            }
        return handler(event, context, **kwargs)

    return handler_wrapper


def unpack_acls(event: Mapping) -> Mapping[str, Iterable]:
    acls_dict: Mapping[str, Iterable]
    acls_dict = defaultdict(list)
    encoded_acls = get_in(['requestContext', 'authorizer', 'acls'], event)
    acls = vacuum_unpack(encoded_acls)
    acls_dict['cloud_account_ids'] = acls.get('cloud_account_ids', [])
    acls_dict['tags'] = acls.get('tags', [])
    return acls_dict


class AuthorizationException(HTTPResponseException):
    def __init__(self, data):
        super().__init__(data, statusCode=403)


class PathAuthorizationException(AuthorizationException):
    def __init__(self, message, param):
        exception_data = {'type': 'PathAuthorizationException', 'param': param, 'message': message}
        super().__init__(exception_data)
