# 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

import logging
from collections import defaultdict
from typing import Callable, Iterable, Mapping

from toolz.dicttoolz import get_in

from czc.constants import FEATURE_NAME
from czc.exceptions import HTTPResponseException
from czc.string import camel_to_snake, vacuum_unpack

logger = logging.getLogger(FEATURE_NAME)


def get_allowed_cloud_account_ids(event: Mapping) -> Iterable[str]:
    allowed_cloud_account_ids = unpack_acls(event)['cloud_account_ids']
    return allowed_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)
