from copy import deepcopy
from typing import Any, Dict, List, Optional

from moto.core.utils import camelcase_to_underscores
from moto.ec2.exceptions import (
    InvalidParameterCombination,
    InvalidRequest,
    MissingParameterError,
)
from moto.ec2.utils import filter_iam_instance_profiles

from ._base_response import EC2BaseResponse


class InstanceResponse(EC2BaseResponse):
    def describe_instances(self) -> str:
        self.error_on_dryrun()
        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html
        # You cannot specify this(MaxResults) parameter and the instance IDs parameter in the same request.
        if "InstanceId.1" in self.data and "MaxResults" in self.data:
            raise InvalidParameterCombination(
                "The parameter instancesSet cannot be used with the parameter maxResults"
            )
        filter_dict = self._filters_from_querystring()
        instance_ids = self._get_multi_param("InstanceId")
        token = self._get_param("NextToken")
        if instance_ids:
            reservations = self.ec2_backend.get_reservations_by_instance_ids(
                instance_ids, filters=filter_dict
            )
        else:
            reservations = self.ec2_backend.describe_instances(filters=filter_dict)

        reservation_ids = [reservation.id for reservation in reservations]
        if token:
            start = reservation_ids.index(token) + 1
        else:
            start = 0
        max_results = int(self._get_param("MaxResults", 100))
        reservations_resp = reservations[start : start + max_results]
        next_token = None
        if max_results and len(reservations) > (start + max_results):
            next_token = reservations_resp[-1].id
        template = self.response_template(EC2_DESCRIBE_INSTANCES)
        return (
            template.render(
                account_id=self.current_account,
                reservations=reservations_resp,
                next_token=next_token,
                run_instances=False,
            )
            .replace("True", "true")
            .replace("False", "false")
        )

    def run_instances(self) -> str:
        min_count = int(self._get_param("MinCount", if_none="1"))
        image_id = self._get_param("ImageId")
        owner_id = self._get_param("OwnerId")
        user_data = self._get_param("UserData")
        security_group_names = self._get_multi_param("SecurityGroup")
        kwargs = {
            "instance_type": self._get_param("InstanceType", if_none="m1.small"),
            "is_instance_type_default": not self._get_param("InstanceType"),
            "placement": self._get_param("Placement.AvailabilityZone"),
            "placement_hostid": self._get_param("Placement.HostId"),
            "region_name": self.region,
            "subnet_id": self._get_param("SubnetId"),
            "owner_id": owner_id,
            "key_name": self._get_param("KeyName"),
            "security_group_ids": self._get_multi_param("SecurityGroupId"),
            "nics": self._get_multi_param("NetworkInterface."),
            "private_ip": self._get_param("PrivateIpAddress"),
            "associate_public_ip": self._get_param("AssociatePublicIpAddress"),
            "tags": self._parse_tag_specification(),
            "ebs_optimized": self._get_param("EbsOptimized") or False,
            "disable_api_stop": self._get_param("DisableApiStop") or False,
            "instance_market_options": self._get_param(
                "InstanceMarketOptions.MarketType"
            )
            or {},
            "instance_initiated_shutdown_behavior": self._get_param(
                "InstanceInitiatedShutdownBehavior"
            ),
            "launch_template": self._get_multi_param_dict("LaunchTemplate"),
            "hibernation_options": self._get_multi_param_dict("HibernationOptions"),
            "iam_instance_profile_name": self._get_param("IamInstanceProfile.Name")
            or None,
            "iam_instance_profile_arn": self._get_param("IamInstanceProfile.Arn")
            or None,
            "monitoring_state": "enabled"
            if self._get_param("Monitoring.Enabled") == "true"
            else "disabled",
            "ipv6_address_count": self._get_int_param("Ipv6AddressCount"),
        }
        if len(kwargs["nics"]) and kwargs["subnet_id"]:
            raise InvalidParameterCombination(
                msg="Network interfaces and an instance-level subnet ID may not be specified on the same request"
            )

        mappings = self._parse_block_device_mapping()
        if mappings:
            kwargs["block_device_mappings"] = mappings

        iam_instance_profile_name = kwargs.get("iam_instance_profile_name")
        iam_instance_profile_arn = kwargs.get("iam_instance_profile_arn")
        if iam_instance_profile_arn or iam_instance_profile_name:
            # Validate the profile exists, before we error_on_dryrun and run_instances
            filter_iam_instance_profiles(
                self.current_account,
                partition=self.partition,
                iam_instance_profile_arn=iam_instance_profile_arn,
                iam_instance_profile_name=iam_instance_profile_name,
            )

        self.error_on_dryrun()

        new_reservation = self.ec2_backend.run_instances(
            image_id, min_count, user_data, security_group_names, **kwargs
        )
        if iam_instance_profile_name:
            self.ec2_backend.associate_iam_instance_profile(
                instance_id=new_reservation.instances[0].id,
                iam_instance_profile_name=iam_instance_profile_name,
            )

        if iam_instance_profile_arn:
            self.ec2_backend.associate_iam_instance_profile(
                instance_id=new_reservation.instances[0].id,
                iam_instance_profile_arn=iam_instance_profile_arn,
            )

        template = self.response_template(EC2_RUN_INSTANCES)
        return template.render(
            account_id=self.current_account,
            reservation=new_reservation,
            run_instances=True,
        )

    def terminate_instances(self) -> str:
        instance_ids = self._get_multi_param("InstanceId")

        self.error_on_dryrun()

        instances = self.ec2_backend.terminate_instances(instance_ids)
        from moto.autoscaling import autoscaling_backends
        from moto.elbv2 import elbv2_backends

        autoscaling_backends[self.current_account][
            self.region
        ].notify_terminate_instances(instance_ids)
        elbv2_backends[self.current_account][self.region].notify_terminate_instances(
            instance_ids
        )
        template = self.response_template(EC2_TERMINATE_INSTANCES)
        return template.render(instances=instances)

    def reboot_instances(self) -> str:
        instance_ids = self._get_multi_param("InstanceId")

        self.error_on_dryrun()

        instances = self.ec2_backend.reboot_instances(instance_ids)
        template = self.response_template(EC2_REBOOT_INSTANCES)
        return template.render(instances=instances)

    def stop_instances(self) -> str:
        instance_ids = self._get_multi_param("InstanceId")

        self.error_on_dryrun()

        instances = self.ec2_backend.stop_instances(instance_ids)
        template = self.response_template(EC2_STOP_INSTANCES)
        return template.render(instances=instances)

    def start_instances(self) -> str:
        instance_ids = self._get_multi_param("InstanceId")
        self.error_on_dryrun()

        instances = self.ec2_backend.start_instances(instance_ids)
        template = self.response_template(EC2_START_INSTANCES)
        return template.render(instances=instances)

    def _get_list_of_dict_params(
        self, param_prefix: str, _dct: Dict[str, Any]
    ) -> List[Any]:
        """
        Simplified version of _get_dict_param
        Allows you to pass in a custom dict instead of using self.querystring by default
        """
        params = []
        for key, value in _dct.items():
            if key.startswith(param_prefix):
                params.append(value)
        return params

    def describe_instance_status(self) -> str:
        instance_ids = self._get_multi_param("InstanceId")
        include_all_instances = self._get_param("IncludeAllInstances") == "true"
        filters = self._get_list_prefix("Filter")
        filters = [
            {"name": f["name"], "values": self._get_list_of_dict_params("value.", f)}
            for f in filters
        ]

        instances = self.ec2_backend.describe_instance_status(
            instance_ids, include_all_instances, filters
        )

        template = self.response_template(EC2_INSTANCE_STATUS)
        return template.render(instances=instances)

    def describe_instance_types(self) -> str:
        instance_type_filters = self._get_multi_param("InstanceType")
        filter_dict = self._filters_from_querystring()
        instance_types = self.ec2_backend.describe_instance_types(
            instance_type_filters, filter_dict
        )
        template = self.response_template(EC2_DESCRIBE_INSTANCE_TYPES)
        return template.render(instance_types=instance_types)

    def describe_instance_type_offerings(self) -> str:
        location_type_filters = self._get_param("LocationType")
        filter_dict = self._filters_from_querystring()
        offerings = self.ec2_backend.describe_instance_type_offerings(
            location_type_filters, filter_dict
        )
        template = self.response_template(EC2_DESCRIBE_INSTANCE_TYPE_OFFERINGS)
        return template.render(instance_type_offerings=offerings)

    def describe_instance_attribute(self) -> str:
        # TODO this and modify below should raise IncorrectInstanceState if
        # instance not in stopped state
        attribute = self._get_param("Attribute")
        instance_id = self._get_param("InstanceId")
        instance, value = self.ec2_backend.describe_instance_attribute(
            instance_id, attribute
        )

        if attribute == "groupSet":
            template = self.response_template(EC2_DESCRIBE_INSTANCE_GROUPSET_ATTRIBUTE)
        else:
            template = self.response_template(EC2_DESCRIBE_INSTANCE_ATTRIBUTE)

        return template.render(instance=instance, attribute=attribute, value=value)

    def describe_instance_credit_specifications(self) -> str:
        instance_ids = self._get_multi_param("InstanceId")
        instance = self.ec2_backend.describe_instance_credit_specifications(
            instance_ids
        )
        template = self.response_template(EC2_DESCRIBE_INSTANCE_CREDIT_SPECIFICATIONS)
        return template.render(instances=instance)

    def modify_instance_attribute(self) -> str:
        handlers = [
            self._attribute_value_handler,
            self._dot_value_instance_attribute_handler,
            self._block_device_mapping_handler,
            self._security_grp_instance_attribute_handler,
        ]

        for handler in handlers:
            success = handler()
            if success:
                return success

        msg = (
            "This specific call to ModifyInstanceAttribute has not been"
            " implemented in Moto yet. Feel free to open an issue at"
            " https://github.com/getmoto/moto/issues"
        )
        raise NotImplementedError(msg)

    def _block_device_mapping_handler(self) -> Optional[str]:
        """
        Handles requests which are generated by code similar to:

            instance.modify_attribute(
                BlockDeviceMappings=[{
                    'DeviceName': '/dev/sda1',
                    'Ebs': {'DeleteOnTermination': True}
                }]
            )

        The querystring contains information similar to:

            BlockDeviceMapping.1.Ebs.DeleteOnTermination : ['true']
            BlockDeviceMapping.1.DeviceName : ['/dev/sda1']

        For now we only support the "BlockDeviceMapping.1.Ebs.DeleteOnTermination"
        configuration, but it should be trivial to add anything else.
        """
        mapping_counter = 1
        mapping_device_name_fmt = "BlockDeviceMapping.%s.DeviceName"
        mapping_del_on_term_fmt = "BlockDeviceMapping.%s.Ebs.DeleteOnTermination"
        while True:
            mapping_device_name = mapping_device_name_fmt % mapping_counter
            if mapping_device_name not in self.querystring.keys():
                break

            mapping_del_on_term = mapping_del_on_term_fmt % mapping_counter
            del_on_term_value_str = self.querystring[mapping_del_on_term][0]
            del_on_term_value = True if "true" == del_on_term_value_str else False
            device_name_value = self.querystring[mapping_device_name][0]

            instance_id = self._get_param("InstanceId")
            instance = self.ec2_backend.get_instance(instance_id)

            self.error_on_dryrun()

            block_device_type = instance.block_device_mapping[device_name_value]
            block_device_type.delete_on_termination = del_on_term_value

            # +1 for the next device
            mapping_counter += 1

        if mapping_counter > 1:
            return EC2_MODIFY_INSTANCE_ATTRIBUTE
        return None

    def _dot_value_instance_attribute_handler(self) -> Optional[str]:
        attribute_key = None
        for key, value in self.querystring.items():
            if ".Value" in key:
                attribute_key = key
                break

        if not attribute_key:
            return None

        self.error_on_dryrun()

        value = self.querystring.get(attribute_key)[0]  # type: ignore
        normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0])
        instance_id = self._get_param("InstanceId")
        self.ec2_backend.modify_instance_attribute(
            instance_id, normalized_attribute, value
        )
        return EC2_MODIFY_INSTANCE_ATTRIBUTE

    def _attribute_value_handler(self) -> Optional[str]:
        attribute_key = self._get_param("Attribute")

        if attribute_key is None:
            return None

        self.error_on_dryrun()

        value = self._get_param("Value")
        normalized_attribute = camelcase_to_underscores(attribute_key)
        instance_id = self._get_param("InstanceId")
        self.ec2_backend.modify_instance_attribute(
            instance_id, normalized_attribute, value
        )
        return EC2_MODIFY_INSTANCE_ATTRIBUTE

    def _security_grp_instance_attribute_handler(self) -> str:
        new_security_grp_list = []
        for key in self.querystring:
            if "GroupId." in key:
                new_security_grp_list.append(self.querystring.get(key)[0])  # type: ignore

        instance_id = self._get_param("InstanceId")
        self.error_on_dryrun()

        self.ec2_backend.modify_instance_security_groups(
            instance_id, new_security_grp_list
        )
        return EC2_MODIFY_INSTANCE_ATTRIBUTE

    def _parse_block_device_mapping(self) -> List[Dict[str, Any]]:
        device_mappings = self._get_list_prefix("BlockDeviceMapping")
        mappings = []
        for device_mapping in device_mappings:
            self._validate_block_device_mapping(device_mapping)
            device_template: Dict[str, Any] = deepcopy(BLOCK_DEVICE_MAPPING_TEMPLATE)
            device_template["VirtualName"] = device_mapping.get("virtual_name")
            device_template["DeviceName"] = device_mapping.get("device_name")
            device_template["Ebs"]["SnapshotId"] = device_mapping.get(
                "ebs._snapshot_id"
            )
            device_template["Ebs"]["VolumeSize"] = device_mapping.get(
                "ebs._volume_size"
            )
            device_template["Ebs"]["DeleteOnTermination"] = self._convert_to_bool(
                device_mapping.get("ebs._delete_on_termination", False)
            )
            device_template["Ebs"]["VolumeType"] = device_mapping.get(
                "ebs._volume_type"
            )
            device_template["Ebs"]["Iops"] = device_mapping.get("ebs._iops")
            device_template["Ebs"]["Encrypted"] = self._convert_to_bool(
                device_mapping.get("ebs._encrypted", False)
            )
            device_template["Ebs"]["KmsKeyId"] = device_mapping.get("ebs._kms_key_id")
            device_template["NoDevice"] = device_mapping.get("no_device")
            mappings.append(device_template)

        return mappings

    @staticmethod
    def _validate_block_device_mapping(device_mapping: Dict[str, Any]) -> None:  # type: ignore[misc]
        from botocore import __version__ as botocore_version

        if "no_device" in device_mapping:
            assert isinstance(
                device_mapping["no_device"], str
            ), f"botocore {botocore_version} isn't limiting NoDevice to str type anymore, it is type:{type(device_mapping['no_device'])}"
            if device_mapping["no_device"] == "":
                # the only legit value it can have is empty string
                # and none of the other checks here matter if NoDevice
                # is being used
                return
            else:
                raise InvalidRequest()

        if not any(mapping for mapping in device_mapping if mapping.startswith("ebs.")):
            raise MissingParameterError("ebs")
        if (
            "ebs._volume_size" not in device_mapping
            and "ebs._snapshot_id" not in device_mapping
        ):
            raise MissingParameterError("size or snapshotId")

    @staticmethod
    def _convert_to_bool(bool_str: Any) -> bool:  # type: ignore[misc]
        if isinstance(bool_str, bool):
            return bool_str

        if isinstance(bool_str, str):
            return str(bool_str).lower() == "true"

        return False


BLOCK_DEVICE_MAPPING_TEMPLATE = {
    "VirtualName": None,
    "DeviceName": None,
    "NoDevice": None,
    "Ebs": {
        "SnapshotId": None,
        "VolumeSize": None,
        "DeleteOnTermination": None,
        "VolumeType": None,
        "Iops": None,
        "Encrypted": None,
    },
}

INSTANCE_TEMPLATE = """<item>
          <instanceId>{{ instance.id }}</instanceId>
          <imageId>{{ instance.image_id }}</imageId>
          {% if run_instances %}
          <instanceState>
            <code>0</code>
            <name>pending</name>
         </instanceState>
          {% else %}
          <instanceState>
            <code>{{ instance._state.code }}</code>
            <name>{{ instance._state.name }}</name>
         </instanceState>
         {% endif %}
          <privateDnsName>{{ instance.private_dns }}</privateDnsName>
          <publicDnsName>{{ instance.public_dns }}</publicDnsName>
          <dnsName>{{ instance.public_dns }}</dnsName>
          <reason>{{ instance._reason }}</reason>
          {% if instance.key_name is not none %}
             <keyName>{{ instance.key_name }}</keyName>
          {% endif %}
          <ebsOptimized>{{ instance.ebs_optimized | lower }}</ebsOptimized>
          <amiLaunchIndex>{{ instance.ami_launch_index }}</amiLaunchIndex>
          <instanceType>{{ instance.instance_type }}</instanceType>
          {% if instance.iam_instance_profile %}
          <iamInstanceProfile>
            <arn>{{ instance.iam_instance_profile['Arn'] }}</arn>
            <id>{{ instance.iam_instance_profile['Id'] }}</id>
          </iamInstanceProfile>
          {% endif %}
          <launchTime>{{ instance.launch_time }}</launchTime>
          {% if instance.lifecycle %}
          <instanceLifecycle>{{ instance.lifecycle }}</instanceLifecycle>
          {% endif %}
          <placement>
            {% if instance.placement_hostid %}<hostId>{{ instance.placement_hostid }}</hostId>{% endif %}
            <availabilityZone>{{ instance.placement}}</availabilityZone>
            <groupName/>
            <tenancy>default</tenancy>
          </placement>
          <monitoring>
            <state> {{ instance.monitoring_state }} </state>
          </monitoring>
          {% if instance.subnet_id %}
            <subnetId>{{ instance.subnet_id }}</subnetId>
          {% elif instance.nics[0].subnet.id %}
            <subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
          {% endif %}
          {% if instance.vpc_id %}
            <vpcId>{{ instance.vpc_id }}</vpcId>
          {% elif instance.nics[0].subnet.vpc_id %}
            <vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
          {% endif %}
          <privateIpAddress>{{ instance.private_ip }}</privateIpAddress>
          {% if instance.nics[0].public_ip %}
              <ipAddress>{{ instance.nics[0].public_ip }}</ipAddress>
          {% endif %}
          <sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
          <groupSet>
             {% for group in instance.dynamic_group_list %}
             <item>
               {% if group.id %}
                <groupId>{{ group.id }}</groupId>
                <groupName>{{ group.name }}</groupName>
                {% else %}
                  <groupId>{{ group }}</groupId>
                {% endif %}
             </item>
             {% endfor %}
          </groupSet>
          {% if instance.platform %}
          <platform>{{ instance.platform }}</platform>
          {% endif %}
          <virtualizationType>{{ instance.virtualization_type }}</virtualizationType>
          <stateReason>
              <code>{{ instance._state_reason.code }}</code>
              <message>{{ instance._state_reason.message }}</message>
          </stateReason>
          <architecture>{{ instance.architecture }}</architecture>
          <kernelId>{{ instance.kernel }}</kernelId>
          <rootDeviceType>ebs</rootDeviceType>
          <rootDeviceName>/dev/sda1</rootDeviceName>
          <blockDeviceMapping>
              {% for device_name,deviceobject in instance.get_block_device_mapping %}
              <item>
                 <deviceName>{{ device_name }}</deviceName>
                  <ebs>
                     <volumeId>{{ deviceobject.volume_id }}</volumeId>
                     <status>{{ deviceobject.status }}</status>
                     <attachTime>{{ deviceobject.attach_time }}</attachTime>
                     <deleteOnTermination>{{ 'true' if deviceobject.delete_on_termination else 'false' }}</deleteOnTermination>
                     <size>{{deviceobject.size}}</size>
                </ebs>
              </item>
             {% endfor %}
          </blockDeviceMapping>
          <clientToken>ABCDE{{ account_id }}3</clientToken>
          <hypervisor>xen</hypervisor>
          {% if instance.hibernation_options %}
          <hibernationOptions>
            <configured>{{ instance.hibernation_options.get("Configured") }}</configured>
          </hibernationOptions>
          {% endif %}
          {% if instance.get_tags() %}
          <tagSet>
            {% for tag in instance.get_tags() %}
              <item>
                <resourceId>{{ tag.resource_id }}</resourceId>
                <resourceType>{{ tag.resource_type }}</resourceType>
                <key>{{ tag.key }}</key>
                <value>{{ tag.value }}</value>
              </item>
            {% endfor %}
          </tagSet>
          {% endif %}
          <networkInterfaceSet>
            {% for nic in instance.nics.values() %}
              <item>
                <networkInterfaceId>{{ nic.id }}</networkInterfaceId>
                {% if nic.subnet %}
                  <subnetId>{{ nic.subnet.id }}</subnetId>
                  <vpcId>{{ nic.subnet.vpc_id }}</vpcId>
                {% endif %}
                <description>Primary network interface</description>
                <ownerId>{{ account_id }}</ownerId>
                <status>in-use</status>
                <macAddress>1b:2b:3c:4d:5e:6f</macAddress>
                <privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
                <sourceDestCheck>{{ instance.source_dest_check }}</sourceDestCheck>
                <groupSet>
                  {% for group in nic.group_set %}
                  <item>
                    {% if group.id %}
                      <groupId>{{ group.id }}</groupId>
                      <groupName>{{ group.name }}</groupName>
                    {% else %}
                      <groupId>{{ group }}</groupId>
                    {% endif %}
                  </item>
                  {% endfor %}
                </groupSet>
                <attachment>
                  <attachmentId>{{ nic.attachment_id }}</attachmentId>
                  <deviceIndex>{{ nic.device_index }}</deviceIndex>
                  <status>attached</status>
                  <attachTime>2015-01-01T00:00:00Z</attachTime>
                  <deleteOnTermination>true</deleteOnTermination>
                </attachment>
                {% if nic.public_ip %}
                  <association>
                    <publicIp>{{ nic.public_ip }}</publicIp>
                    <ipOwnerId>{{ account_id }}</ipOwnerId>
                  </association>
                {% endif %}
                {% if nic.ipv6_addresses %}
                <ipv6AddressesSet>
                  {% for ipv6 in nic.ipv6_addresses %}
                  <item>
                    <ipv6Address>{{ ipv6 }}</ipv6Address>
                    <isPrimaryIpv6>false</isPrimaryIpv6>
                  </item>
                  {% endfor %}
                </ipv6AddressesSet>
                {% endif %}
                <privateIpAddressesSet>
                  <item>
                    <privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
                    <primary>true</primary>
                    {% if nic.public_ip %}
                      <association>
                        <publicIp>{{ nic.public_ip }}</publicIp>
                        <ipOwnerId>{{ account_id }}</ipOwnerId>
                      </association>
                    {% endif %}
                  </item>
                </privateIpAddressesSet>
              </item>
            {% endfor %}
          </networkInterfaceSet>
        </item>"""

EC2_RUN_INSTANCES = (
    """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <reservationId>{{ reservation.id }}</reservationId>
  <ownerId>{{ account_id }}</ownerId>
  <groupSet>
    <item>
      <groupId>sg-245f6a01</groupId>
      <groupName>default</groupName>
    </item>
  </groupSet>
  <instancesSet>
    {% for instance in reservation.instances %}
        """
    + INSTANCE_TEMPLATE
    + """
    {% endfor %}
  </instancesSet>
  </RunInstancesResponse>"""
)

EC2_DESCRIBE_INSTANCES = (
    """<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>fdcdcab1-ae5c-489e-9c33-4637c5dda355</requestId>
      <reservationSet>
        {% for reservation in reservations %}
          <item>
            <reservationId>{{ reservation.id }}</reservationId>
            <ownerId>{{ account_id }}</ownerId>
            <groupSet>
              {% for group in reservation.dynamic_group_list %}
              <item>
      {% if group.id %}
                <groupId>{{ group.id }}</groupId>
                <groupName>{{ group.name }}</groupName>
                {% else %}
                <groupId>{{ group }}</groupId>
                {% endif %}
              </item>
              {% endfor %}
            </groupSet>
            <instancesSet>
                {% for instance in reservation.instances %}
                  """
    + INSTANCE_TEMPLATE
    + """
                {% endfor %}
            </instancesSet>
          </item>
        {% endfor %}
      </reservationSet>
      {% if next_token %}
      <nextToken>{{ next_token }}</nextToken>
      {% endif %}
</DescribeInstancesResponse>"""
)

EC2_TERMINATE_INSTANCES = """
<TerminateInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <instancesSet>
    {% for instance, previous_state in instances %}
      <item>
        <instanceId>{{ instance.id }}</instanceId>
        <previousState>
          <code>{{ previous_state.code }}</code>
          <name>{{ previous_state.name }}</name>
        </previousState>
        <currentState>
          <code>32</code>
          <name>shutting-down</name>
        </currentState>
      </item>
    {% endfor %}
  </instancesSet>
</TerminateInstancesResponse>"""

EC2_STOP_INSTANCES = """
<StopInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <instancesSet>
    {% for instance, previous_state in instances %}
      <item>
        <instanceId>{{ instance.id }}</instanceId>
        <previousState>
          <code>{{ previous_state.code }}</code>
          <name>{{ previous_state.name }}</name>
        </previousState>
        <currentState>
          <code>64</code>
          <name>stopping</name>
        </currentState>
      </item>
    {% endfor %}
  </instancesSet>
</StopInstancesResponse>"""

EC2_START_INSTANCES = """
<StartInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <instancesSet>
    {% for instance, previous_state in instances %}
      <item>
        <instanceId>{{ instance.id }}</instanceId>
        <previousState>
          <code>{{ previous_state.code }}</code>
          <name>{{ previous_state.name }}</name>
        </previousState>
        <currentState>
          <code>0</code>
          <name>pending</name>
        </currentState>
      </item>
    {% endfor %}
  </instancesSet>
</StartInstancesResponse>"""

EC2_REBOOT_INSTANCES = """<RebootInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <return>true</return>
</RebootInstancesResponse>"""

EC2_DESCRIBE_INSTANCE_ATTRIBUTE = """<DescribeInstanceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <instanceId>{{ instance.id }}</instanceId>
  <{{ attribute }}>
    {% if value is not none %}
    <value>{{ value }}</value>
    {% endif %}
  </{{ attribute }}>
</DescribeInstanceAttributeResponse>"""

EC2_DESCRIBE_INSTANCE_CREDIT_SPECIFICATIONS = """<DescribeInstanceCreditSpecificationsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
    <requestId>1b234b5c-d6ef-7gh8-90i1-j2345678901</requestId>
    <instanceCreditSpecificationSet>
       {% for instance in instances %}
      <item>
        <instanceId>{{ instance.id }}</instanceId>
        <cpuCredits>standard</cpuCredits>
      </item>
    {% endfor %}
    </instanceCreditSpecificationSet>
</DescribeInstanceCreditSpecificationsResponse>"""

EC2_DESCRIBE_INSTANCE_GROUPSET_ATTRIBUTE = """<DescribeInstanceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <instanceId>{{ instance.id }}</instanceId>
  <{{ attribute }}>
    {% for sg in value %}
      <item>
        <groupId>{{ sg.id }}</groupId>
      </item>
    {% endfor %}
  </{{ attribute }}>
</DescribeInstanceAttributeResponse>"""

EC2_MODIFY_INSTANCE_ATTRIBUTE = """<ModifyInstanceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
  <return>true</return>
</ModifyInstanceAttributeResponse>"""

EC2_INSTANCE_STATUS = """<?xml version="1.0" encoding="UTF-8"?>
<DescribeInstanceStatusResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
    <instanceStatusSet>
      {% for instance in instances %}
        <item>
            <instanceId>{{ instance.id }}</instanceId>
            <availabilityZone>{{ instance.placement }}</availabilityZone>
            <instanceState>
                <code>{{ instance.state_code }}</code>
                <name>{{ instance.state }}</name>
            </instanceState>
            {% if instance.state_code == 16 %}
              <systemStatus>
                  <status>ok</status>
                  <details>
                      <item>
                          <name>reachability</name>
                          <status>passed</status>
                      </item>
                  </details>
              </systemStatus>
              <instanceStatus>
                  <status>ok</status>
                  <details>
                      <item>
                          <name>reachability</name>
                          <status>passed</status>
                      </item>
                  </details>
              </instanceStatus>
            {% else %}
              <systemStatus>
                  <status>not-applicable</status>
              </systemStatus>
              <instanceStatus>
                  <status>not-applicable</status>
              </instanceStatus>
            {% endif %}
        </item>
      {% endfor %}
    </instanceStatusSet>
</DescribeInstanceStatusResponse>"""

EC2_DESCRIBE_INSTANCE_TYPES = """<?xml version="1.0" encoding="UTF-8"?>
<DescribeInstanceTypesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
    <requestId>f8b86168-d034-4e65-b48d-3b84c78e64af</requestId>
    <instanceTypeSet>
    {% for instance_type in instance_types %}
        <item>
            <autoRecoverySupported>{{ instance_type.AutoRecoverySupported }}</autoRecoverySupported>
            <bareMetal>{{ instance_type.BareMetal }}</bareMetal>
            <burstablePerformanceSupported>{{ instance_type.BurstablePerformanceSupported }}</burstablePerformanceSupported>
            <currentGeneration>{{ instance_type.CurrentGeneration }}</currentGeneration>
            <dedicatedHostsSupported>{{ instance_type.DedicatedHostsSupported }}</dedicatedHostsSupported>
            <ebsInfo>
                <ebsOptimizedInfo>
                    <baselineBandwidthInMbps>{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('BaselineBandwidthInMbps', 0) | int }}</baselineBandwidthInMbps>
                    <baselineIops>{{  instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('BaselineIops', 0) | int }}</baselineIops>
                    <baselineThroughputInMBps>{{  instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('BaselineThroughputInMBps', 0.0) | float }}</baselineThroughputInMBps>
                    <maximumBandwidthInMbps>{{  instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('MaximumBandwidthInMbps', 0) | int }}</maximumBandwidthInMbps>
                    <maximumIops>{{  instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('MaximumIops', 0) | int }}</maximumIops>
                    <maximumThroughputInMBps>{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('MaximumThroughputInMBps', 0.0) | float }}</maximumThroughputInMBps>
                </ebsOptimizedInfo>
                <ebsOptimizedSupport>{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedSupport', 'default') }}</ebsOptimizedSupport>
                <encryptionSupport>{{ instance_type.get('EbsInfo', {}).get('EncryptionSupport', 'supported') }}</encryptionSupport>
                <nvmeSupport>{{ instance_type.get('EbsInfo', {}).get('NvmeSupport', 'required') }}</nvmeSupport>
            </ebsInfo>
            <networkInfo>
                <defaultNetworkCardIndex>{{ instance_type.get('NetworkInfo', {}).get('DefaultNetworkCardIndex', 0) | int }}</defaultNetworkCardIndex>
                <efaSupported>{{ instance_type.get('NetworkInfo', {}).get('EfaSupported', False) }}</efaSupported>
                <enaSrdSupported>{{ instance_type.get('NetworkInfo', {}).get('EnaSrdSupported', False) }}</enaSrdSupported>
                <enaSupport>{{ instance_type.get('NetworkInfo', {}).get('EnaSupport', 'unsupported') }}</enaSupport>
                <encryptionInTransitSupported>{{ instance_type.get('NetworkInfo', {}).get('EncryptionInTransitSupported', False) }}</encryptionInTransitSupported>
                <ipv4AddressesPerInterface>{{ instance_type.get('NetworkInfo', {}).get('Ipv4AddressesPerInterface', 0) | int }}</ipv4AddressesPerInterface>
                <ipv6AddressesPerInterface>{{ instance_type.get('NetworkInfo', {}).get('Ipv6AddressesPerInterface', 0) | int }}</ipv6AddressesPerInterface>
                <ipv6Supported>{{ instance_type.get('NetworkInfo', {}).get('Ipv6Supported', False) }}</ipv6Supported>
                <maximumNetworkCards>{{ instance_type.get('NetworkInfo', {}).get('MaximumNetworkCards', 0) | int }}</maximumNetworkCards>
                <maximumNetworkInterfaces>{{ instance_type.get('NetworkInfo', {}).get('MaximumNetworkInterfaces', 0) | int }}</maximumNetworkInterfaces>
                <networkCards>
                  {% for network_card in instance_type.get('NetworkInfo', {}).get('NetworkCards', []) %}
                    <item>
                        <baselineBandwidthInGbps>{{ network_card.get('BaselineBandwidthInGbps', 0.0) | float }}</baselineBandwidthInGbps>
                        <maximumNetworkInterfaces>{{ network_card.get('MaximumNetworkInterfaces', 0) | int }}</maximumNetworkInterfaces>
                        <networkCardIndex>{{ network_card.get('NetworkCardIndex', 0) | int }}</networkCardIndex>
                        <networkPerformance>{{ network_card.get('NetworkPerformance', 'Up to 25 Schmeckles') }}</networkPerformance>
                        <peakBandwidthInGbps>{{ network_card.get('PeakBandwidthInGbps', 0.0) | float }}</peakBandwidthInGbps>
                    </item>
                  {% endfor %}
                </networkCards>
                <networkPerformance>{{ instance_type.get('NetworkInfo', {}).get('NetworkPerformance', 'Up to 25 Schmeckles') }}</networkPerformance>
            </networkInfo>
            <freeTierEligible>{{ instance_type.FreeTierEligible }}</freeTierEligible>
            <hibernationSupported>{{ instance_type.HibernationSupported }}</hibernationSupported>
            <hypervisor>{{ instance_type.get('Hypervisor', 'motovisor') }}</hypervisor>
            <instanceStorageSupported>{{ instance_type.InstanceStorageSupported }}</instanceStorageSupported>
            <placementGroupInfo>
                <supportedStrategies>
                  {% for strategy in instance_type.get('PlacementGroupInfo', {}).get('SupportedStrategies', []) %}
                    <item>{{ strategy }}</item>
                  {% endfor %}
                </supportedStrategies>
            </placementGroupInfo>
            <supportedRootDeviceTypes>
              {% for dev_type in instance_type.get('SupportedRootDeviceTypes', []) %}
                <item>{{ dev_type }}</item>
              {% endfor %}
            </supportedRootDeviceTypes>
            <supportedUsageClasses>
              {% for usage_class in instance_type.get('SupportedUsageClasses', []) %}
                <item>{{ usage_class }}</item>
              {% endfor %}
            </supportedUsageClasses>
            <supportedVirtualizationTypes>
              {% for supported_vtype in instance_type.get('SupportedVirtualizationTypes', []) %}
                <item>{{ supported_vtype }}</item>
              {% endfor %}
            </supportedVirtualizationTypes>
            <instanceType>{{ instance_type.InstanceType }}</instanceType>
            <vCpuInfo>
                <defaultVCpus>{{ instance_type.get('VCpuInfo', {}).get('DefaultVCpus', 0)|int }}</defaultVCpus>
                <defaultCores>{{ instance_type.get('VCpuInfo', {}).get('DefaultCores', 0)|int }}</defaultCores>
                <defaultThreadsPerCore>{{ instance_type.get('VCpuInfo').get('DefaultThreadsPerCore', 0)|int }}</defaultThreadsPerCore>
                <validCores>
                  {% for valid_core in instance_type.get("VCpuInfo", {}).get('ValidCores', []) %}
                    <item>{{ valid_core }}</item>
                  {% endfor %}
                </validCores>
                <validThreadsPerCore>
                  {% for threads_per_core in instance_type.get("VCpuInfo", {}).get('ValidThreadsPerCore', []) %}
                    <item>{{ threads_per_core }}</item>
                  {% endfor %}
                </validThreadsPerCore>
            </vCpuInfo>
            <memoryInfo>
                <sizeInMiB>{{ instance_type.get('MemoryInfo', {}).get('SizeInMiB', 0)|int }}</sizeInMiB>
            </memoryInfo>
            <instanceStorageInfo>
                <totalSizeInGB>{{ instance_type.get('InstanceStorageInfo', {}).get('TotalSizeInGB', 0)|int }}</totalSizeInGB>
            </instanceStorageInfo>
            <processorInfo>
                <supportedArchitectures>
                    {% for arch in instance_type.get('ProcessorInfo', {}).get('SupportedArchitectures', []) %}
                    <item>
                        {{ arch }}
                    </item>
                    {% endfor %}
                </supportedArchitectures>
                <sustainedClockSpeedInGhz>{{ instance_type.get('ProcessorInfo', {}).get('SustainedClockSpeedInGhz', 0.0) | float }}</sustainedClockSpeedInGhz>
            </processorInfo>
            {% if instance_type.get('GpuInfo', {})|length > 0 %}
            <gpuInfo>
                <gpus>
                    {% for gpu in instance_type.get('GpuInfo').get('Gpus') %}
                    <item>
                        <count>{{ gpu['Count']|int }}</count>
                        <manufacturer>{{ gpu['Manufacturer'] }}</manufacturer>
                        <memoryInfo>
                            <sizeInMiB>{{ gpu['MemoryInfo']['SizeInMiB']|int }}</sizeInMiB>
                        </memoryInfo>
                        <name>{{ gpu['Name'] }}</name>
                    </item>
                    {% endfor %}
                </gpus>
                <totalGpuMemoryInMiB>{{ instance_type['GpuInfo']['TotalGpuMemoryInMiB']|int }}</totalGpuMemoryInMiB>
            </gpuInfo>
            {% endif %}
        </item>
    {% endfor %}
    </instanceTypeSet>
</DescribeInstanceTypesResponse>"""


EC2_DESCRIBE_INSTANCE_TYPE_OFFERINGS = """<?xml version="1.0" encoding="UTF-8"?>
<DescribeInstanceTypeOfferingsResponse xmlns="http://api.outscale.com/wsdl/fcuext/2014-04-15/">
    <requestId>f8b86168-d034-4e65-b48d-3b84c78e64af</requestId>
    <instanceTypeOfferingSet>
    {% for offering in instance_type_offerings %}
        <item>
            <instanceType>{{ offering.InstanceType }}</instanceType>
            <location>{{ offering.Location }}</location>
            <locationType>{{ offering.LocationType }}</locationType>
        </item>
    {% endfor %}
    </instanceTypeOfferingSet>
</DescribeInstanceTypeOfferingsResponse>"""
