# -*- coding: utf-8 -*-
# Copyright (c) 2017 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 boto3
import cachetools.func
import pyfaaster.aws.tools as tools
from boto3.exceptions import ResourceNotExistsError
from botocore.config import Config

import czc.constants as constants

logger = tools.setup_logging(constants.PROJECT_NAME)

THROTTLE_PRONE_SERVICES = [
    'lambda',
    'cloudformation',
    'ecs',
    'iam',
    'logs',
    'rds',
    'elbv2'
]


sts_client = boto3.client('sts')


def boto3_service_name(arn_service_name):
    """
    When creating a Boto session, sometimes you can't simply take the service name from the ARN and use that service
    name to create a boto session to that service using the service name as the service_name parameter for the
    boto3 Session.resource() method.
    TODO: figure out if we still need this
    >>> boto3_service_name('elasticloadbalancing')
    'elb'
    >>> boto3_service_name('a4b')
    'alexaforbusiness'
    """
    boto3_service_names = {
        'a4b': 'alexaforbusiness',
        'elasticfilesystem': 'efs',
        'elasticloadbalancing': 'elb',
        'states': 'stepfunctions'
    }
    translated_service_name = None
    try:
        translated_service_name = boto3_service_names[arn_service_name]
    except KeyError:
        pass
    return translated_service_name or arn_service_name


class ProviderConnectionError(Exception):
    """
    Thrown when we can't make an AWS connection
    """
    pass


class Provider(object):
    def __init__(self, role_arn, external_id, region, service=None):
        if not all([role_arn, external_id, region]):
            raise ProviderConnectionError(f'role_arn {role_arn} | '
                                          f'external_id {external_id} | '
                                          f'service {service} | '
                                          f'region {region} must be  specified')

        self.__role_arn = role_arn
        self.__external_id = external_id
        self.__service = boto3_service_name(service)
        self.__region = region

    @property
    def role_arn(self):
        return self.__role_arn

    @property
    def external_id(self):
        return self.__external_id

    @property
    def service(self):
        return self.__service

    @service.setter
    def service(self, service):
        self.__service = boto3_service_name(service)

    @property
    def region(self):
        return self.__region

    @property
    def client(self):
        session = assume_role_session(self.role_arn, self.external_id)
        logger.debug(f'Configuring client for {self.__service} in {self.__region}')

        client = session.client(
            service_name=self.__service,
            region_name=self.__region,
            config=boto_config(self.__service)
        )

        return client

    @property
    def resource(self):
        session = assume_role_session(self.role_arn, self.external_id)
        try:
            resource = session.resource(
                service_name=self.__service,
                region_name=self.__region,
                config=boto_config(self.__service)
            )

            return resource
        except ResourceNotExistsError:
            logger.debug(f'Attempted to create resource for service {self.__service}')

        return None


def boto_config(service):
    if service in THROTTLE_PRONE_SERVICES:
        return Config(retries=dict(max_attempts=1))

    # do the default boto
    return Config(retries=dict(max_attempts=4))


@cachetools.func.ttl_cache(maxsize=1024, ttl=2700)
def assume_role_session(role_arn, external_id, duration=3600, region='us-east-1'):
    credentials = sts_client.assume_role(
        RoleArn=role_arn,
        RoleSessionName='cloudzero',
        ExternalId=external_id,
        DurationSeconds=duration
    )['Credentials']

    logger.debug(f'Got assume role credentials {credentials}')

    session = boto3.Session(
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken']
    )
    return session
