######################################################################################################################
#  Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.                                           #
#                                                                                                                    #
#  Licensed under the Amazon Software License (the "License"). You may not use this file except in compliance        #
#  with the License. A copy of the License is located at                                                             #
#                                                                                                                    #
#      http://aws.amazon.com/asl/                                                                                    #
#                                                                                                                    #
#  or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES #
#  OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions    #
#  and limitations under the License.                                                                                #
######################################################################################################################

import os

import boto3

from boto_retry import get_client_with_retries
from services.aws_service import AwsService

WARN_NO_PRICING_API_ACCESS = "Valid instance types not available from pricing API {}, using values from Boto3 EC2 service model {}"

ENV_EC2_VALID_INSTANCE_TYPES = "EC2_VALID_INSTANCE_TYPES"

ADDRESSES = "Addresses"
ATTRIBUTES = "AccountAttributes"
AVAILABILITY_ZONES = "AvailabilityZones"
BUNDLE_TASKS = "BundleTasks"
CLASSIC_LINK = "VpcClassicLink"
CLASSIC_LINK_DNS_SUPPORT = "VpcClassicLinkDnsSupport"
CLASSIC_LINK_INSTANCES = "ClassicLinkInstances"
CONVERSION_TASKS = "ConversionTasks"
CUSTOMER_GATEWAYS = "CustomerGateways"
DHCP_OPTIONS = "DhcpOptions"
EXPORT_TASKS = "ExportTasks"
FLEET_REQUEST_HISTORY = "SpotFleetRequestHistory"
FLOW_LOGS = "FlowLogs"
GROUP_REFERENCES = "SecurityGroupReferences"
HOST_RESERVATION_OFFERINGS = "HostReservationOfferings"
HOST_RESERVATIONS = "HostReservations"
HOSTS = "Hosts"
ID_FORMAT = "IdFormat"
IDENTITY_ID_FORMAT = "IdentityIdFormat"
IMAGE_ATTRIBUTE = "ImageAttribute"
IMAGES = "Images"
IMPORT_IMAGE_TASKS = "ImportImageTasks"
IMPORT_SNAPSHOT_TASKS = "ImportSnapshotTasks"
INSTANCE_ATTRIBUTE = "InstanceAttribute"
INSTANCE_AVAILABILITY = "ScheduledInstanceAvailability"
INSTANCE_CREDIT_SPECIFICATIONS = "InstanceCreditSpecifications"
INSTANCE_STATUS = "InstanceStatus"
INSTANCES = "Instances"
INTERFACE_ATTRIBUTE = "NetworkInterfaceAttribute"
INTERNET_GATEWAYS = "InternetGateways"
KEY_PAIRS = "KeyPairs"
MOVING_ADDRESSES = "MovingAddresses"
NAT_GATEWAYS = "NatGateways"
NETWORK_ACLS = "NetworkAcls"
NETWORK_INTERFACES = "NetworkInterfaces"
PLACEMENT_GROUPS = "PlacementGroups"
PREFIX_LISTS = "PrefixLists"
REGIONS = "Regions"
RESERVED_INSTANCE_LISTINGS = "ReservedInstancesListings"
RESERVED_INSTANCE_MODIFICATIONS = "ReservedInstancesModifications"
RESERVED_INSTANCES = "ReservedInstances"
RESERVED_INSTANCES_OFFERINGS = "ReservedInstancesOfferings"
ROUTE_TABLES = "RouteTables"
SCHEDULED_INSTANCES = "ScheduledInstances"
SECURITY_GROUPS = "SecurityGroups"
STALE_SECURITY_GROUPS = "StaleSecurityGroups"
SNAPSHOT_ATTRIBUTE = "SnapshotAttribute"
SNAPSHOTS = "Snapshots"
SPOT_DATAFEED_SUBSCRIPTION = "SpotDatafeedSubscription"
SPOT_FLEET_INSTANCES = "SpotFleetInstances"
SPOT_FLEET_REQUESTS = "SpotFleetRequests"
SPOT_INSTANCE_REQUESTS = "SpotInstanceRequests"
SPOT_PRICE_HISTORY = "SpotPriceHistory"
SUBNETS = "Subnets"
TAGS = "Tags"
VOLUME_ATTRIBUTE = "VolumeAttribute"
VOLUME_STATUS = "VolumeStatus"
VOLUMES = "Volumes"
VPC_ATTRIBUTE = "VpcAttribute"
VPC_ENDPOINT_SERVICES = "VpcEndpointServices"
VPC_ENDPOINTS = "VpcEndpoints"
VPC_PEERING_CONNECTIONS = "VpcPeeringConnections"
VPCS = "Vpcs"
VPN_CONNECTIONS = "VpnConnections"
VPN_GATEWAYS = "VpnGateways"

CUSTOM_RESULT_PATHS = custom_result_paths = {
    CLASSIC_LINK_INSTANCES: INSTANCES,
    HOST_RESERVATION_OFFERINGS: "OfferingSet",
    HOST_RESERVATIONS: "HostReservationSet",
    ID_FORMAT: "Statuses",
    IDENTITY_ID_FORMAT: "Statuses",

    IMAGE_ATTRIBUTE: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "BlockDeviceMappings",
        "Description",
        "ImageId",
        "KernelId",
        "LaunchPermissions",
        "ProductCodes",
        "RamdiskId",
        "SriovNetSupport"
    ]]) + "}",

    INSTANCE_ATTRIBUTE: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "BlockDeviceMappings",
        "Description",
        "DisableApiTermination",
        "EbsOptimized"
        "EnaSupport",
        "Groups",
        "InstanceId",
        "InstanceInitiatedShutdownBehavior",
        "InstanceType",
        "KernelId",
        "ProductCodes",
        "RamdiskId",
        "RootDeviceName",
        "SourceDestCheck",
        "SriovNetSupport",
        "UserData"
    ]]) + "}",

    INSTANCES: "Reservations[*].Instances[]",
    INSTANCE_STATUS: "InstanceStatuses",
    MOVING_ADDRESSES: "MovingAddressStatuses",
    INTERFACE_ATTRIBUTE: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "Attachment",
        "Description",
        "Groups",
        "NetworkInterfaceId",
        "SourceDestCheck"
    ]]) + "}",

    GROUP_REFERENCES: "SecurityGroupReferenceSet",
    INSTANCE_AVAILABILITY: "ScheduledInstanceAvailabilitySet",
    SCHEDULED_INSTANCES: "ScheduledInstanceSet",
    SNAPSHOT_ATTRIBUTE: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "CreateVolumePermissions",
        "ProductCodes",
        "SnapshotId"
    ]]) + "}",

    SPOT_FLEET_INSTANCES: "ActiveInstances",
    FLEET_REQUEST_HISTORY: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "HistoryRecords",
        "LastEvaluatedTime"
        "SpotFleetRequestId",
        "StartTime"
    ]]) + "}",

    SPOT_FLEET_REQUESTS: "SpotFleetRequestConfigs",
    VOLUME_ATTRIBUTE: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "VolumeId",
        "AutoEnableIO",
        "ProductCodes"
    ]]) + "}",

    VOLUME_STATUS: "VolumeStatuses",

    VPC_ATTRIBUTE: "{" + ",".join(['"{}":{}'.format(i, i) for i in [
        "VpcId",
        "EnableDnsSupport",
        "EnableDnsHostnames"
    ]]) + "}",

    CLASSIC_LINK: VPCS,
    CLASSIC_LINK_DNS_SUPPORT: VPCS,
    VPC_ENDPOINT_SERVICES: "ServiceNames"
}

RESOURCE_NAMES = [
    ATTRIBUTES,
    ADDRESSES,
    AVAILABILITY_ZONES,
    BUNDLE_TASKS,
    CLASSIC_LINK_INSTANCES,
    CONVERSION_TASKS,
    CUSTOMER_GATEWAYS,
    DHCP_OPTIONS,
    EXPORT_TASKS,
    FLOW_LOGS,
    HOST_RESERVATION_OFFERINGS,
    HOST_RESERVATIONS,
    HOSTS,
    ID_FORMAT,
    IDENTITY_ID_FORMAT,
    IMAGE_ATTRIBUTE,
    IMAGES,
    IMPORT_IMAGE_TASKS,
    IMPORT_SNAPSHOT_TASKS,
    INSTANCE_ATTRIBUTE,
    INSTANCE_CREDIT_SPECIFICATIONS,
    INSTANCE_STATUS,
    INSTANCES,
    INTERNET_GATEWAYS,
    KEY_PAIRS,
    MOVING_ADDRESSES,
    NAT_GATEWAYS,
    NETWORK_ACLS,
    INTERFACE_ATTRIBUTE,
    NETWORK_INTERFACES,
    PLACEMENT_GROUPS,
    PREFIX_LISTS,
    REGIONS,
    RESERVED_INSTANCES,
    RESERVED_INSTANCE_LISTINGS,
    RESERVED_INSTANCE_MODIFICATIONS,
    RESERVED_INSTANCES_OFFERINGS,
    ROUTE_TABLES,
    INSTANCE_AVAILABILITY,
    SCHEDULED_INSTANCES,
    GROUP_REFERENCES,
    SECURITY_GROUPS,
    SNAPSHOT_ATTRIBUTE,
    SNAPSHOTS,
    SPOT_DATAFEED_SUBSCRIPTION,
    SPOT_FLEET_INSTANCES,
    FLEET_REQUEST_HISTORY,
    SPOT_FLEET_REQUESTS,
    SPOT_INSTANCE_REQUESTS,
    SPOT_PRICE_HISTORY,
    SECURITY_GROUPS,
    SUBNETS,
    TAGS,
    VOLUME_ATTRIBUTE,
    VOLUME_STATUS,
    VOLUMES,
    VPC_ATTRIBUTE,
    CLASSIC_LINK,
    CLASSIC_LINK_DNS_SUPPORT,
    VPC_ENDPOINT_SERVICES,
    VPC_ENDPOINTS,
    VPC_PEERING_CONNECTIONS,
    VPCS,
    VPN_CONNECTIONS,
    VPN_GATEWAYS
]

RESOURCES_WITH_TAGS = [
    ADDRESSES,
    CUSTOMER_GATEWAYS,
    DHCP_OPTIONS,
    IMAGES,
    INSTANCES,
    INTERNET_GATEWAYS,
    NETWORK_ACLS,
    NETWORK_INTERFACES,
    RESERVED_INSTANCES,
    ROUTE_TABLES,
    SECURITY_GROUPS,
    SNAPSHOTS,
    SPOT_INSTANCE_REQUESTS,
    SUBNETS,
    VOLUMES,
    VPC_PEERING_CONNECTIONS,
    VPCS,
    VPN_CONNECTIONS,
    VPN_GATEWAYS
]

_valid_instance_types = None


class Ec2Service(AwsService):
    _valid_instance_types = None

    def __init__(self, role_arn=None, session=None, tags_as_dict=True, as_named_tuple=False, service_retry_strategy=None):
        """
        :param role_arn: Optional (cross account) role to use to retrieve services
        :param session: Optional session to use to retrieve services
        :param tags_as_dict: Set to True true to convert resource tags to dictionaries
        :param as_named_tuple: Set to True to return resources as named tuples instead of a dictionary
        :param service_retry_strategy: service retry strategy for making boto api calls
        """

        AwsService.__init__(self,
                            service_name='ec2',
                            resource_names=RESOURCE_NAMES,
                            resources_with_tags=RESOURCES_WITH_TAGS,
                            role_arn=role_arn,
                            session=session,
                            as_named_tuple=as_named_tuple,
                            tags_as_dict=tags_as_dict,
                            custom_result_paths=CUSTOM_RESULT_PATHS,
                            service_retry_strategy=service_retry_strategy)

    def _transform_returned_resource(self, client, resource, use_cached_tags=False):

        if self._resource_name in [INSTANCE_ATTRIBUTE,
                                   IMAGE_ATTRIBUTE,
                                   INTERFACE_ATTRIBUTE,
                                   SNAPSHOT_ATTRIBUTE,
                                   FLEET_REQUEST_HISTORY,
                                   VOLUME_ATTRIBUTE,
                                   VPC_ATTRIBUTE]:
            temp = {r: resource[r] for r in resource if resource[r] is not None}
            for r in temp:
                if isinstance(temp[r], dict) and "Value" in temp[r]:
                    temp[r] = temp[r]["Value"]
        else:
            temp = resource

        return AwsService._transform_returned_resource(self, client, temp)

    def _get_tags_for_resource(self, client, resource):
        """
        Returns the tags for specific resources that require additional boto calls to retrieve their tags.
        :param client: Client that can be used to make the boto call to retrieve the tags
        :param resource: The resource for which to retrieve the tags
        :return: Tags
        """
        return resource.get(TAGS, [])

    @staticmethod
    def valid_instance_types(logger=None):

        # first check if the environment variable is set
        ec2_types = os.getenv(ENV_EC2_VALID_INSTANCE_TYPES, "").strip()
        if ec2_types != "":
            return [e.strip() for e in ec2_types.split(",")]

        # if the types are not in the environment variable fetch the types from the pricing service api
        global _valid_instance_types
        if _valid_instance_types is None:
            _valid_instance_types = []
            # noinspection PyPep8
            try:
                pricing = get_client_with_retries("pricing", ["get_attribute_values"], region="us-east-1")

                args = {
                    "ServiceCode": "AmazonEC2",
                    "AttributeName": "instanceType",
                    "_expected_boto3_exceptions_": ["AccessDeniedException"]
                }
                while True:
                    sc = pricing.get_attribute_values_with_retries(**args)
                    for a in sc.get("AttributeValues"):
                        if len(a["Value"].split(".")) > 1:
                            _valid_instance_types.append(a["Value"])
                    if "NextToken" in sc:
                        args["NextToken"] = sc["NextToken"]
                    else:
                        break
            except Exception as ex:
                ec2 = boto3.Session().client("ec2")
                # noinspection PyProtectedMember
                _valid_instance_types = ec2._service_model._service_description["shapes"]["InstanceType"]["enum"]
                version = ec2._service_model.api_version
                if logger is not None:
                    logger.warning(WARN_NO_PRICING_API_ACCESS, ex, version)
        return _valid_instance_types
