# -*- coding: utf-8 -*-
# Copyright (c) 2016-present, CloudZero, Inc. All rights reserved.
# Licensed under the BSD-style license. See LICENSE file in the project root for full license information.

import os

import boto3
from botocore.exceptions import ClientError
from lambda_tools import setup_logging
from reactor.utils import boto3_service_name

AWS_RESOURCE_SERVICES = ['cloudformation', 'cloudwatch', 'dynamodb', 'ec2',
                         'glacier', 'iam', 'opsworks', 's3', 'sns', 'sqs']

CONNECTION_TYPE_UNKNOWN = 0
CONNECTION_TYPE_LOCAL = 1
CONNECTION_TYPE_ROLE = 2


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


class Provider(object):
    def __init__(self, service=None, role_arn=None, external_id=None, region=None,
                 endpoint_url=None, aws_access_key_id=None, aws_secret_access_key=None):
        super(Provider, self).__init__()

        self.logger = setup_logging('reactor')

        self.region = region
        if service:
            self.endpoint_url = endpoint_url or os.environ.get(f'{service.upper()}_ENDPOINT')
        else:
            self.endpoint_url = endpoint_url

        self.__service = service
        self.__session = None
        self.__client = None
        self.__aws_access_key_id = aws_access_key_id
        self.__aws_secret_access_key = aws_secret_access_key
        self.__connection_type = CONNECTION_TYPE_UNKNOWN
        self.__resource = None
        self.__role_session_name = 'cloudzero-reactor'

        if role_arn:
            if not external_id:
                raise ProviderConnectionError('If role_arn is supplied external_id MUST be supplied')
            self.role_arn = role_arn
            self.external_id = str(external_id)
            self.__connection_type = CONNECTION_TYPE_ROLE
        else:
            self.__connection_type = CONNECTION_TYPE_LOCAL

        # Lazy connection configuration - only if service was set on instantiation
        if self.__service:
            self.__service = boto3_service_name(self.__service)
            self.configure_connection()

    @property
    def client(self):
        return self.__client

    @property
    def session(self):
        return self.__session

    @property
    def resource(self):
        return self.__resource

    @property
    def aws_connection_details(self):
        if self.role_arn:
            return {'role_arn': self.role_arn,
                    'external_id': self.external_id,
                    'region': self.region}
        else:
            return {'aws_access_key_id': self.__aws_access_key_id,
                    'aws_secret_access_key': self.__aws_secret_access_key,
                    'region': self.region}

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

    @service.setter
    def service(self, value):
        """
        Reconfigure connection if the service is changed

        Args:
            value (str): Service to change to

        Returns:
            None

        """
        self.__service = boto3_service_name(value)
        self.configure_connection()

    def configure_connection(self):

        if self.__connection_type is CONNECTION_TYPE_ROLE:
            sts_client = boto3.client('sts')
            try:
                credentials = sts_client.assume_role(RoleArn=self.role_arn,
                                                     RoleSessionName=self.__role_session_name,
                                                     ExternalId=self.external_id)['Credentials']
            except ClientError as err:
                self.logger.debug(f"Provider | Failed to assume role {err}")
                raise ProviderConnectionError(f"Unable to assume role: {self.role_arn}")

            self.logger.debug("Provider | Assumed role: {}".format(self.role_arn))

            self.__session = boto3.Session(aws_access_key_id=credentials['AccessKeyId'],
                                           aws_secret_access_key=credentials['SecretAccessKey'],
                                           aws_session_token=credentials['SessionToken'],
                                           region_name=self.region)
        else:
            self.__session = boto3.Session(aws_access_key_id=self.__aws_access_key_id,
                                           aws_secret_access_key=self.__aws_secret_access_key,
                                           region_name=self.region)

        self.__client = self.__session.client(service_name=self.__service,
                                              region_name=self.region,
                                              endpoint_url=self.endpoint_url)
        if self.__service in AWS_RESOURCE_SERVICES:
            self.__resource = self.__session.resource(service_name=self.__service,
                                                      region_name=self.region,
                                                      endpoint_url=self.endpoint_url)
        else:
            self.__resource = None

    def __str__(self):
        return "Client: {} Service: {} Resource: {} Endpoint URL {}".format(self.__client,
                                                                            self.__service,
                                                                            self.__resource,
                                                                            self.endpoint_url)

    def all_results(self, api):
        """ Background:
        The AWS API limits the number of items it includes in a response
        to a maximum of 50. For API Calls that produce more results than can
        be returned at once, AWS includes a ContinuationToken in its response
        which can be supplied to subsequent API calls to tell Amazon where you
        left off.

        This method employs boto3's paginators (which are an abstraction of the
        ContinuationToken concept described above) to call AWS repeatedly,
        collecting the outputs until there are no more results. It returns
        all the results it collected to the caller.

        Args:
            api (str): The name of an API for which boto3 defines a paginator,
                       corresponding to the currently instantiated service
        """
        paginator = self.__client.get_paginator(api)
        for r in paginator.paginate():
            yield r
