from uuid import uuid4

import boto3
import pytest
from botocore.client import ClientError

from moto import mock_aws
from moto.core.types import Base64EncodedString
from tests import EXAMPLE_AMI_ID
from tests.test_ec2.helpers import assert_dryrun_error

from . import ec2_aws_verified


def get_subnet_id(conn):
    vpc = conn.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
    subnet = conn.create_subnet(
        VpcId=vpc["VpcId"], CidrBlock="10.0.0.0/16", AvailabilityZone="us-east-1a"
    )["Subnet"]
    subnet_id = subnet["SubnetId"]
    return subnet_id


def get_launch_template(conn, instance_type="t2.micro", ami_id=EXAMPLE_AMI_ID):
    # We're going to place some instances in this region
    # Not all availability zones will support the requested instances types
    # So here we make sure that we're placing them in the main zone
    # (Most tests run against `us-east-1`, so availability zone will be `us-east-1a`)
    azone = conn.meta._client_config.region_name + "a"
    launch_template = conn.create_launch_template(
        LaunchTemplateName="test" + str(uuid4()),
        LaunchTemplateData={
            "ImageId": ami_id,
            "InstanceType": instance_type,
            "DisableApiTermination": False,
            "TagSpecifications": [
                {
                    "ResourceType": "instance",
                    "Tags": [
                        {"Key": "test", "Value": "value"},
                        {"Key": "Name", "Value": "test"},
                    ],
                }
            ],
            "Placement": {
                "AvailabilityZone": azone,
            },
        },
    )["LaunchTemplate"]
    launch_template_id = launch_template["LaunchTemplateId"]
    launch_template_name = launch_template["LaunchTemplateName"]
    return launch_template_id, launch_template_name


class launch_template_context:
    def __init__(self, region: str = "us-west-2"):
        self.ec2 = boto3.client("ec2", region_name=region)
        self.ssm = boto3.client("ssm", region_name=region)

    def __enter__(self):
        kernel_61 = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
        ami_id = self.ssm.get_parameter(Name=kernel_61)["Parameter"]["Value"]
        self.lt_id, self.lt_name = get_launch_template(self.ec2, ami_id=ami_id)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.ec2.delete_launch_template(LaunchTemplateId=self.lt_id)


@pytest.mark.aws_verified
@ec2_aws_verified()
def test_launch_template_is_created_properly(ec2_client=None):
    with launch_template_context() as ctxt:
        template = ctxt.ec2.describe_launch_templates()["LaunchTemplates"][0]
        assert template["DefaultVersionNumber"] == 1
        assert template["LatestVersionNumber"] == 1


@pytest.mark.aws_verified
@ec2_aws_verified()
def test_create_fleet_dryrun(ec2_client=None):
    with launch_template_context() as ctxt:
        # Attempting to create a fleet with DryRun=True should raise an error
        with pytest.raises(ClientError) as ex:
            fleets_before = len(ctxt.ec2.describe_fleets()["Fleets"])
            reservations_before = len(ctxt.ec2.describe_instances()["Reservations"])

            ctxt.ec2.create_fleet(
                DryRun=True,
                ExcessCapacityTerminationPolicy="terminate",
                LaunchTemplateConfigs=[
                    {
                        "LaunchTemplateSpecification": {
                            "LaunchTemplateId": ctxt.lt_id,
                            "Version": "1",
                        },
                    },
                ],
                TargetCapacitySpecification={
                    "DefaultTargetCapacityType": "spot",
                    "OnDemandTargetCapacity": 0,
                    "SpotTargetCapacity": 1,
                    "TotalTargetCapacity": 1,
                },
                SpotOptions={
                    "AllocationStrategy": "lowest-price",
                },
                Type="maintain",
                ValidFrom="2020-01-01T00:00:00Z",
                ValidUntil="2020-12-31T00:00:00Z",
            )

        # Verify the error is the expected DryRun error
        assert_dryrun_error(ex)

        # Verify no fleets were created
        fleets_res = ctxt.ec2.describe_fleets()
        assert len(fleets_res["Fleets"]) == fleets_before

        # Verify no instances were created
        instances_res = ctxt.ec2.describe_instances()
        assert len(instances_res["Reservations"]) == reservations_before


@pytest.mark.aws_verified
@ec2_aws_verified()
@pytest.mark.parametrize(
    ["target_capacity_specification", "expected_on_demand", "expected_spot"],
    [
        pytest.param(
            {"TotalTargetCapacity": 2, "DefaultTargetCapacityType": "on-demand"},
            2,
            0,
            id="default-on-demand-only",
        ),
        pytest.param(
            {"TotalTargetCapacity": 2, "DefaultTargetCapacityType": "spot"},
            0,
            2,
            id="default-spot-only",
        ),
        pytest.param(
            {
                "TotalTargetCapacity": 2,
                "OnDemandTargetCapacity": 2,
                "SpotTargetCapacity": 0,
                "DefaultTargetCapacityType": "on-demand",
            },
            2,
            0,
            id="explicit-on-demand-only",
        ),
        pytest.param(
            {
                "TotalTargetCapacity": 2,
                "OnDemandTargetCapacity": 0,
                "SpotTargetCapacity": 2,
                "DefaultTargetCapacityType": "spot",
            },
            0,
            2,
            id="explicit-spot-only",
        ),
        pytest.param(
            {
                "TotalTargetCapacity": 2,
                "OnDemandTargetCapacity": 1,
                "SpotTargetCapacity": 1,
                "DefaultTargetCapacityType": "on-demand",
            },
            1,
            1,
            id="explicit-mixed-default-on-demand",
        ),
        pytest.param(
            {
                "TotalTargetCapacity": 2,
                "OnDemandTargetCapacity": 1,
                "SpotTargetCapacity": 1,
                "DefaultTargetCapacityType": "spot",
            },
            1,
            1,
            id="explicit-mixed-default-spot",
        ),
        pytest.param(
            {
                "TotalTargetCapacity": 3,
                "OnDemandTargetCapacity": 1,
                "DefaultTargetCapacityType": "spot",
            },
            1,
            2,
            id="partial-on-demand-remainder-to-spot",
        ),
        pytest.param(
            {
                "TotalTargetCapacity": 3,
                "SpotTargetCapacity": 1,
                "DefaultTargetCapacityType": "on-demand",
            },
            2,
            1,
            id="partial-spot-remainder-to-on-demand",
        ),
    ],
)
def test_create_instant_fleet_target_capacity_combinations(
    target_capacity_specification,
    expected_on_demand,
    expected_spot,
    ec2_client=None,
):
    with launch_template_context(region=ec2_client.meta.region_name) as ctxt:
        fleet_res = ctxt.ec2.create_fleet(
            LaunchTemplateConfigs=[
                {
                    "LaunchTemplateSpecification": {
                        "LaunchTemplateId": ctxt.lt_id,
                        "Version": "1",
                    },
                },
            ],
            TargetCapacitySpecification=target_capacity_specification,
            Type="instant",
        )
        fleet_id = fleet_res["FleetId"]
        instances = fleet_res.get("Instances", [])

        on_demand_ids = [
            iid
            for entry in instances
            if entry.get("Lifecycle") == "on-demand"
            for iid in entry.get("InstanceIds", [])
        ]
        spot_ids = [
            iid
            for entry in instances
            if entry.get("Lifecycle") == "spot"
            for iid in entry.get("InstanceIds", [])
        ]

        try:
            assert len(on_demand_ids) == expected_on_demand
            assert len(spot_ids) == expected_spot
        finally:
            ctxt.ec2.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True)


@mock_aws
def test_create_spot_fleet_with_lowest_price():
    conn = boto3.client("ec2", region_name="us-west-2")
    launch_template_id, _ = get_launch_template(conn)

    fleet_res = conn.create_fleet(
        ExcessCapacityTerminationPolicy="terminate",
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 0,
            "SpotTargetCapacity": 1,
            "TotalTargetCapacity": 1,
        },
        SpotOptions={
            "AllocationStrategy": "lowest-price",
        },
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )

    fleet_id = fleet_res["FleetId"]
    assert isinstance(fleet_id, str)
    assert len(fleet_id) == 42

    fleets_res = conn.describe_fleets(FleetIds=[fleet_id])
    assert "Fleets" in fleets_res
    fleets = fleets_res["Fleets"]

    fleet = fleets[0]
    assert fleet["FleetState"] == "active"
    fleet_config = fleet["LaunchTemplateConfigs"][0]

    launch_template_spec = fleet_config["LaunchTemplateSpecification"]

    assert launch_template_spec["LaunchTemplateId"] == launch_template_id
    assert launch_template_spec["Version"] == "1"

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = instance_res["ActiveInstances"]
    assert len(instances) == 1


@mock_aws
def test_create_on_demand_fleet():
    conn = boto3.client("ec2", region_name="us-west-2")
    launch_template_id, _ = get_launch_template(conn)

    fleet_res = conn.create_fleet(
        ExcessCapacityTerminationPolicy="terminate",
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "on-demand",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 0,
            "TotalTargetCapacity": 1,
        },
        OnDemandOptions={
            "AllocationStrategy": "lowestPrice",
        },
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )

    fleet_id = fleet_res["FleetId"]
    assert isinstance(fleet_id, str)
    assert len(fleet_id) == 42

    fleets_res = conn.describe_fleets(FleetIds=[fleet_id])
    assert "Fleets" in fleets_res
    fleets = fleets_res["Fleets"]

    fleet = fleets[0]
    assert fleet["FleetState"] == "active"
    fleet_config = fleet["LaunchTemplateConfigs"][0]

    launch_template_spec = fleet_config["LaunchTemplateSpecification"]

    assert launch_template_spec["LaunchTemplateId"] == launch_template_id
    assert launch_template_spec["Version"] == "1"

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = instance_res["ActiveInstances"]
    assert len(instances) == 1


@mock_aws
def test_create_diversified_spot_fleet():
    conn = boto3.client("ec2", region_name="us-west-2")
    launch_template_id_1, _ = get_launch_template(conn, instance_type="t2.small")
    launch_template_id_2, _ = get_launch_template(conn, instance_type="t2.large")

    fleet_res = conn.create_fleet(
        ExcessCapacityTerminationPolicy="terminate",
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id_1,
                    "Version": "1",
                },
            },
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id_2,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 0,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 2,
        },
        SpotOptions={
            "AllocationStrategy": "diversified",
        },
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )
    fleet_id = fleet_res["FleetId"]

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = instance_res["ActiveInstances"]
    assert len(instances) == 2
    instance_types = {instance["InstanceType"] for instance in instances}
    assert instance_types == {"t2.small", "t2.large"}
    assert "i-" in instances[0]["InstanceId"]


@mock_aws
@pytest.mark.parametrize(
    "spot_allocation_strategy",
    [
        "diversified",
        "lowest-price",
        "capacity-optimized",
        "capacity-optimized-prioritized",
    ],
)
@pytest.mark.parametrize(
    "on_demand_allocation_strategy",
    ["lowestPrice", "prioritized"],
)
def test_request_fleet_using_launch_template_config__name(
    spot_allocation_strategy, on_demand_allocation_strategy
):
    conn = boto3.client("ec2", region_name="us-east-2")

    _, launch_template_name = get_launch_template(conn, instance_type="t2.medium")

    fleet_res = conn.create_fleet(
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateName": launch_template_name,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 3,
        },
        SpotOptions={
            "AllocationStrategy": spot_allocation_strategy,
        },
        OnDemandOptions={
            "AllocationStrategy": on_demand_allocation_strategy,
        },
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )

    fleet_id = fleet_res["FleetId"]

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = instance_res["ActiveInstances"]
    assert len(instances) == 3
    instance_types = {instance["InstanceType"] for instance in instances}
    assert instance_types == {"t2.medium"}
    assert "i-" in instances[0]["InstanceId"]


@mock_aws
def test_create_fleet_request_with_tags():
    conn = boto3.client("ec2", region_name="us-west-2")

    launch_template_id, _ = get_launch_template(conn)
    tags = [
        {"Key": "Name", "Value": "test-fleet"},
        {"Key": "Another", "Value": "tag"},
    ]
    tags_instance = [
        {"Key": "test", "Value": "value"},
        {"Key": "Name", "Value": "test"},
    ]
    fleet_res = conn.create_fleet(
        DryRun=False,
        SpotOptions={
            "AllocationStrategy": "lowestPrice",
            "InstanceInterruptionBehavior": "terminate",
        },
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 3,
        },
        Type="request",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
        TagSpecifications=[
            {
                "ResourceType": "fleet",
                "Tags": tags,
            },
        ],
    )
    fleet_id = fleet_res["FleetId"]
    fleets = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"]

    assert fleets[0]["Tags"] == tags

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = conn.describe_instances(
        InstanceIds=[i["InstanceId"] for i in instance_res["ActiveInstances"]]
    )
    for instance in instances["Reservations"][0]["Instances"]:
        for tag in tags_instance:
            assert tag in instance["Tags"]


@mock_aws
def test_create_fleet_request_with_instance_tags_and_fleet_type_instant():
    conn = boto3.client("ec2", region_name="us-west-2")

    launch_template_id, _ = get_launch_template(conn)
    tags = [
        {"Key": "Name", "Value": "test-fleet"},
        {"Key": "Another", "Value": "tag"},
    ]
    fleet_res = conn.create_fleet(
        DryRun=False,
        SpotOptions={
            "AllocationStrategy": "lowestPrice",
            "InstanceInterruptionBehavior": "terminate",
        },
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 3,
        },
        Type="instant",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
        TagSpecifications=[
            {
                "ResourceType": "instance",
                "Tags": tags,
            },
        ],
    )
    fleet_id = fleet_res["FleetId"]

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = conn.describe_instances(
        InstanceIds=[i["InstanceId"] for i in instance_res["ActiveInstances"]]
    )
    for instance in instances["Reservations"][0]["Instances"]:
        for tag in tags:
            assert tag in instance["Tags"]


@mock_aws
def test_create_fleet_request_rejects_instance_tags_when_type_request():
    conn = boto3.client("ec2", region_name="us-west-2")

    launch_template_id, _ = get_launch_template(conn)
    tags = [
        {"Key": "Name", "Value": "test-fleet"},
        {"Key": "Another", "Value": "tag"},
    ]
    tags_instance = [
        {"Key": "test", "Value": "value"},
        {"Key": "Name", "Value": "test"},
    ]
    fleet_res = conn.create_fleet(
        DryRun=False,
        SpotOptions={
            "AllocationStrategy": "lowestPrice",
            "InstanceInterruptionBehavior": "terminate",
        },
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 3,
        },
        Type="request",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
        TagSpecifications=[
            {
                "ResourceType": "instance",
                "Tags": tags,
            },
        ],
    )
    fleet_id = fleet_res["FleetId"]

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = conn.describe_instances(
        InstanceIds=[i["InstanceId"] for i in instance_res["ActiveInstances"]]
    )
    for instance in instances["Reservations"][0]["Instances"]:
        for tag in tags_instance:
            assert tag in instance["Tags"]
        for tag in tags:
            # When fleet_type="request", TagSpecifications with ResourceType="instance" are ignored
            # according to the AWS spec. So we assert that these tags are not applied to instances.
            assert tag not in instance["Tags"]


@mock_aws
@pytest.mark.parametrize(
    "overrides", [{"InstanceType": "t2.nano"}, {"AvailabilityZone": "us-west-1"}]
)
def test_create_fleet_using_launch_template_config__overrides_single(overrides):
    conn = boto3.client("ec2", region_name="us-east-2")

    template_id, _ = get_launch_template(conn, instance_type="t2.medium")

    fleet_id = conn.create_fleet(
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {"LaunchTemplateId": template_id},
                # Verify we parse the incoming parameters correctly,
                # even when only supplying a dictionary with a single key
                "Overrides": [overrides],
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "TotalTargetCapacity": 1,
        },
    )["FleetId"]

    fleet = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"][0]
    lt_config = fleet["LaunchTemplateConfigs"][0]
    assert lt_config["Overrides"] == [overrides]


@mock_aws
def test_create_fleet_using_launch_template_config__overrides():
    conn = boto3.client("ec2", region_name="us-east-2")
    subnet_id = get_subnet_id(conn)

    template_id, _ = get_launch_template(conn, instance_type="t2.medium")

    template_config_overrides = [
        {
            "InstanceType": "t2.nano",
            "SubnetId": subnet_id,
            "AvailabilityZone": "us-west-1",
            "WeightedCapacity": 2,
        }
    ]

    fleet_res = conn.create_fleet(
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": template_id,
                    "Version": "1",
                },
                "Overrides": template_config_overrides,
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 0,
            "TotalTargetCapacity": 1,
        },
        SpotOptions={
            "AllocationStrategy": "lowest-price",
            "InstanceInterruptionBehavior": "terminate",
        },
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )
    fleet_id = fleet_res["FleetId"]

    instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
    instances = instance_res["ActiveInstances"]
    assert len(instances) == 1
    assert instances[0]["InstanceType"] == "t2.nano"

    instance = conn.describe_instances(
        InstanceIds=[i["InstanceId"] for i in instances]
    )["Reservations"][0]["Instances"][0]
    assert instance["SubnetId"] == subnet_id


@pytest.mark.aws_verified
@ec2_aws_verified()
def test_delete_fleet(ec2_client=None):
    with launch_template_context() as ctxt:
        fleet_res = ctxt.ec2.create_fleet(
            LaunchTemplateConfigs=[
                {
                    "LaunchTemplateSpecification": {
                        "LaunchTemplateId": ctxt.lt_id,
                        "Version": "1",
                    },
                },
            ],
            TargetCapacitySpecification={
                "DefaultTargetCapacityType": "spot",
                "OnDemandTargetCapacity": 1,
                "SpotTargetCapacity": 2,
                "TotalTargetCapacity": 3,
            },
            SpotOptions={
                "AllocationStrategy": "lowestPrice",
                "InstanceInterruptionBehavior": "terminate",
            },
            Type="maintain",
        )
        fleet_id = fleet_res["FleetId"]

        successfull_deletions = ctxt.ec2.delete_fleets(
            FleetIds=[fleet_id], TerminateInstances=False
        )["SuccessfulFleetDeletions"]

        assert len(successfull_deletions) == 1
        assert successfull_deletions[0]["FleetId"] == fleet_id
        assert successfull_deletions[0]["CurrentFleetState"] == "deleted_running"

        successfull_deletions = ctxt.ec2.delete_fleets(
            FleetIds=[fleet_id], TerminateInstances=True
        )["SuccessfulFleetDeletions"]

        assert len(successfull_deletions) == 1
        assert successfull_deletions[0]["FleetId"] == fleet_id
        assert successfull_deletions[0]["CurrentFleetState"] == "deleted_terminating"

        fleets = ctxt.ec2.describe_fleets(FleetIds=[fleet_id])["Fleets"]
        assert len(fleets) == 1

        # AWS doesn't reset this, but Moto does
        # we should figure out why Moto sets this value to 0
        # target_capacity_specification = fleets[0]["TargetCapacitySpecification"]
        # assert target_capacity_specification["TotalTargetCapacity"] == 3
        assert fleets[0]["FleetState"] == "deleted_terminating"

        # Instances should be terminated
        instance_res = ctxt.ec2.describe_fleet_instances(FleetId=fleet_id)
        instances = instance_res["ActiveInstances"]
        assert len(instances) == 0


@mock_aws
def test_describe_fleet_instances_api():
    conn = boto3.client("ec2", region_name="us-west-1")

    launch_template_id, _ = get_launch_template(conn)

    fleet_res = conn.create_fleet(
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 3,
        },
        SpotOptions={
            "AllocationStrategy": "lowestPrice",
            "InstanceInterruptionBehavior": "terminate",
        },
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )

    fleet_id = fleet_res["FleetId"]
    fleet_res = conn.describe_fleet_instances(FleetId=fleet_id)

    assert fleet_res["FleetId"] == fleet_id
    assert len(fleet_res["ActiveInstances"]) == 3

    instance_ids = [i["InstanceId"] for i in fleet_res["ActiveInstances"]]
    for instance_id in instance_ids:
        assert instance_id.startswith("i-") is True

    instance_types = [i["InstanceType"] for i in fleet_res["ActiveInstances"]]
    assert instance_types == ["t2.micro", "t2.micro", "t2.micro"]

    instance_healths = [i["InstanceHealth"] for i in fleet_res["ActiveInstances"]]
    assert instance_healths == ["healthy", "healthy", "healthy"]


@mock_aws
def test_create_fleet_api():
    conn = boto3.client("ec2", region_name="us-west-1")

    launch_template_id, _ = get_launch_template(conn)

    fleet_res = conn.create_fleet(
        LaunchTemplateConfigs=[
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateId": launch_template_id,
                    "Version": "1",
                },
            },
        ],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "spot",
            "OnDemandTargetCapacity": 1,
            "SpotTargetCapacity": 2,
            "TotalTargetCapacity": 3,
        },
        SpotOptions={
            "AllocationStrategy": "lowestPrice",
            "InstanceInterruptionBehavior": "terminate",
        },
        Type="instant",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )

    assert "FleetId" in fleet_res
    assert fleet_res["FleetId"].startswith("fleet-") is True

    assert "Instances" in fleet_res
    assert len(fleet_res["Instances"]) == 3

    instance_ids = [i["InstanceIds"] for i in fleet_res["Instances"]]
    for instance_id in instance_ids:
        assert instance_id[0].startswith("i-") is True

    instance_types = [i["InstanceType"] for i in fleet_res["Instances"]]
    assert instance_types == ["t2.micro", "t2.micro", "t2.micro"]

    lifecycle = [i["Lifecycle"] for i in fleet_res["Instances"]]
    assert "spot" in lifecycle
    assert "on-demand" in lifecycle


@mock_aws
def test_create_fleet_api_response():
    conn = boto3.client("ec2", region_name="us-west-2")
    subnet_id = get_subnet_id(conn)

    launch_template_id, _ = get_launch_template(conn)

    lt_config = {
        "LaunchTemplateSpecification": {
            "LaunchTemplateId": launch_template_id,
            "Version": "1",
        },
        "Overrides": [
            {
                "AvailabilityZone": "us-west-1a",
                "InstanceRequirements": {
                    "AcceleratorCount": {
                        "Max": 10,
                        "Min": 1,
                    },
                    "AcceleratorManufacturers": ["nvidia"],
                    "AcceleratorNames": ["t4"],
                    "AcceleratorTotalMemoryMiB": {
                        "Max": 20972,
                        "Min": 1,
                    },
                    "AcceleratorTypes": ["gpu"],
                    "BareMetal": "included",
                    "BaselineEbsBandwidthMbps": {
                        "Max": 10000,
                        "Min": 125,
                    },
                    "BurstablePerformance": "included",
                    "CpuManufacturers": ["amd", "intel"],
                    "ExcludedInstanceTypes": ["m5.8xlarge"],
                    "InstanceGenerations": ["current"],
                    "LocalStorage": "included",
                    "LocalStorageTypes": ["ssd"],
                    "MemoryGiBPerVCpu": {
                        "Min": 1.0,
                        "Max": 160.0,
                    },
                    "MemoryMiB": {
                        "Min": 2048,
                        "Max": 40960,
                    },
                    "NetworkInterfaceCount": {
                        "Max": 1,
                        "Min": 1,
                    },
                    "OnDemandMaxPricePercentageOverLowestPrice": 99999,
                    "RequireHibernateSupport": True,
                    "SpotMaxPricePercentageOverLowestPrice": 99999,
                    "TotalLocalStorageGB": {
                        "Min": 100.0,
                        "Max": 10000.0,
                    },
                    "VCpuCount": {
                        "Min": 2,
                        "Max": 160,
                    },
                },
                "MaxPrice": "0.5",
                "Priority": 2.0,
                "SubnetId": subnet_id,
                "WeightedCapacity": 1.0,
            },
        ],
    }

    fleet_res = conn.create_fleet(
        ExcessCapacityTerminationPolicy="no-termination",
        LaunchTemplateConfigs=[lt_config],
        TargetCapacitySpecification={
            "DefaultTargetCapacityType": "on-demand",
            "OnDemandTargetCapacity": 10,
            "SpotTargetCapacity": 10,
            "TotalTargetCapacity": 30,
        },
        SpotOptions={
            "AllocationStrategy": "lowest-price",
            "InstanceInterruptionBehavior": "terminate",
            "InstancePoolsToUseCount": 1,
            "MaintenanceStrategies": {
                "CapacityRebalance": {
                    "ReplacementStrategy": "launch-before-terminate",
                    "TerminationDelay": 120,
                },
            },
            "MaxTotalPrice": "50",
            "MinTargetCapacity": 1,
            "SingleAvailabilityZone": True,
            "SingleInstanceType": True,
        },
        OnDemandOptions={
            "AllocationStrategy": "lowest-price",
            "MaxTotalPrice": "50",
            "MinTargetCapacity": 1,
            "SingleAvailabilityZone": True,
            "SingleInstanceType": True,
        },
        ReplaceUnhealthyInstances=True,
        TerminateInstancesWithExpiration=True,
        Type="maintain",
        ValidFrom="2020-01-01T00:00:00Z",
        ValidUntil="2020-12-31T00:00:00Z",
    )
    fleet_id = fleet_res["FleetId"]

    fleet_res = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"]
    assert len(fleet_res) == 1
    assert fleet_res[0]["FleetId"] == fleet_id
    assert fleet_res[0]["ExcessCapacityTerminationPolicy"] == "no-termination"
    assert fleet_res[0]["LaunchTemplateConfigs"] == [lt_config]
    assert fleet_res[0]["TargetCapacitySpecification"] == {
        "DefaultTargetCapacityType": "on-demand",
        "OnDemandTargetCapacity": 10,
        "SpotTargetCapacity": 10,
        "TotalTargetCapacity": 30,
    }
    assert fleet_res[0]["SpotOptions"] == {
        "AllocationStrategy": "lowest-price",
        "InstanceInterruptionBehavior": "terminate",
        "InstancePoolsToUseCount": 1,
        "MaintenanceStrategies": {
            "CapacityRebalance": {
                "ReplacementStrategy": "launch-before-terminate",
                "TerminationDelay": 120,
            },
        },
        "MaxTotalPrice": "50",
        "MinTargetCapacity": 1,
        "SingleAvailabilityZone": True,
        "SingleInstanceType": True,
    }
    assert fleet_res[0]["OnDemandOptions"] == {
        "AllocationStrategy": "lowest-price",
        "MaxTotalPrice": "50",
        "MinTargetCapacity": 1,
        "SingleAvailabilityZone": True,
        "SingleInstanceType": True,
    }
    assert fleet_res[0]["ReplaceUnhealthyInstances"] is True
    assert fleet_res[0]["TerminateInstancesWithExpiration"] is True
    assert fleet_res[0]["Type"] == "maintain"
    assert "ValidFrom" in fleet_res[0]
    assert fleet_res[0]["ValidFrom"].isoformat() == "2020-01-01T00:00:00+00:00"
    assert "ValidUntil" in fleet_res[0]
    assert fleet_res[0]["ValidUntil"].isoformat() == "2020-12-31T00:00:00+00:00"


@mock_aws
def test_user_data():
    ec2_client = boto3.client("ec2", region_name="us-west-2")
    user_data = Base64EncodedString.from_raw_string("test user data")
    template_args = {
        "LaunchTemplateName": "test-template",
        "LaunchTemplateData": {
            "ImageId": "ami-0157ed312f9c59a91",
            "InstanceType": "t3.nano",
            "UserData": str(user_data),
        },
    }
    ec2_client.create_launch_template(**template_args)
    fleet_args = {
        "OnDemandOptions": {
            "AllocationStrategy": "lowest-price",
        },
        "TargetCapacitySpecification": {
            "TotalTargetCapacity": 1,
            "DefaultTargetCapacityType": "on-demand",
        },
        "LaunchTemplateConfigs": [
            {
                "LaunchTemplateSpecification": {
                    "LaunchTemplateName": "test-template",
                    "Version": "$Latest",
                },
            },
        ],
        "Type": "instant",
    }
    fleet = ec2_client.create_fleet(**fleet_args)
    instance = fleet["Instances"][0]
    attrs = ec2_client.describe_instance_attribute(
        InstanceId=instance["InstanceIds"][0],
        Attribute="userData",
    )
    assert attrs["UserData"]["Value"] == str(user_data)


@pytest.mark.aws_verified
@ec2_aws_verified()
@pytest.mark.parametrize("version_specified", ["$Latest", "$Default"])
def test_version_resolves_to_actual_version_number(version_specified, ec2_client=None):
    """Test that $Latest or $Default in a fleet LaunchTemplateSpecification resolves to the actual version number"""
    with launch_template_context(region=ec2_client.meta.region_name) as ctxt:
        fleet_response = ctxt.ec2.create_fleet(
            LaunchTemplateConfigs=[
                {
                    "LaunchTemplateSpecification": {
                        "LaunchTemplateId": ctxt.lt_id,
                        "Version": version_specified,
                    },
                    "Overrides": [{"InstanceType": "t3.micro"}],
                }
            ],
            TargetCapacitySpecification={
                "TotalTargetCapacity": 1,
                "OnDemandTargetCapacity": 1,
                "SpotTargetCapacity": 0,
                "DefaultTargetCapacityType": "on-demand",
            },
            OnDemandOptions={"AllocationStrategy": "lowest-price"},
            Type="instant",
        )
        fleet_id = fleet_response["FleetId"]
        instances = fleet_response["Instances"]
        try:
            for instance in instances:
                lt_spec_response = instance["LaunchTemplateAndOverrides"][
                    "LaunchTemplateSpecification"
                ]
                assert lt_spec_response["Version"] == "1"
        finally:
            ctxt.ec2.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True)


# Verified against AWS, but flaky - that's why we do not run it against AWS
# @pytest.mark.aws_verified
@ec2_aws_verified()
def test_fleet_uses_data_from_specified_template_version(ec2_client=None):
    with launch_template_context(region=ec2_client.meta.region_name) as ctxt:
        # Version 1 was created by the context manager with t2.micro;
        # add version 2 with a different instance type
        ctxt.ec2.create_launch_template_version(
            LaunchTemplateId=ctxt.lt_id,
            LaunchTemplateData={"InstanceType": "t3.medium"},
        )

        fleet = ctxt.ec2.create_fleet(
            LaunchTemplateConfigs=[
                {
                    "LaunchTemplateSpecification": {
                        "LaunchTemplateId": ctxt.lt_id,
                        "Version": "1",
                    },
                }
            ],
            TargetCapacitySpecification={
                "TotalTargetCapacity": 1,
                "OnDemandTargetCapacity": 1,
                "DefaultTargetCapacityType": "on-demand",
            },
            OnDemandOptions={"AllocationStrategy": "lowest-price"},
            Type="instant",
        )
        fleet_id = fleet["FleetId"]
        instance_id = fleet["Instances"][0]["InstanceIds"][0]
        try:
            instance = ctxt.ec2.describe_instances(InstanceIds=[instance_id])[
                "Reservations"
            ][0]["Instances"][0]
            assert instance["InstanceType"] == "t2.micro"
        finally:
            ctxt.ec2.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True)


# Verified against AWS, but flaky - that's why we do not run it against AWS
# @pytest.mark.aws_verified
@ec2_aws_verified()
@pytest.mark.parametrize(
    "use_template_name", [False, True], ids=["template-id", "template-name"]
)
@pytest.mark.parametrize(
    ["on_demand_count", "spot_count"],
    [
        pytest.param(1, 0, id="on-demand-only"),
        pytest.param(0, 2, id="spot-only"),
        pytest.param(1, 2, id="mixed-on-demand-spot"),
    ],
)
@pytest.mark.parametrize(
    "overrides",
    [
        pytest.param(
            [{"InstanceType": "t3.micro"}, {"InstanceType": "t3.nano"}],
            id="multi-override",
        ),
        pytest.param(
            [{"InstanceType": "t3.micro"}],
            id="single-override",
        ),
    ],
)
def test_create_instant_fleet_with_launch_template_overrides(
    use_template_name,
    overrides,
    on_demand_count,
    spot_count,
    ec2_client=None,
):
    """Test that LaunchTemplateAndOverrides is included in instant fleet responses"""
    expected_instance_types = {o["InstanceType"] for o in overrides}

    with launch_template_context(region=ec2_client.meta.region_name) as ctxt:
        if use_template_name:
            lt_spec = {"LaunchTemplateName": ctxt.lt_name, "Version": "$Latest"}
        else:
            lt_spec = {"LaunchTemplateId": ctxt.lt_id, "Version": "$Latest"}

        total_capacity = on_demand_count + spot_count
        fleet_request = {
            "LaunchTemplateConfigs": [
                {
                    "LaunchTemplateSpecification": lt_spec,
                    "Overrides": overrides,
                }
            ],
            "TargetCapacitySpecification": {
                "TotalTargetCapacity": total_capacity,
                "OnDemandTargetCapacity": on_demand_count,
                "SpotTargetCapacity": spot_count,
                "DefaultTargetCapacityType": "on-demand"
                if on_demand_count > 0
                else "spot",
            },
            "Type": "instant",
        }

        if on_demand_count > 0:
            fleet_request["OnDemandOptions"] = {"AllocationStrategy": "lowest-price"}
        if spot_count > 0:
            fleet_request["SpotOptions"] = {"AllocationStrategy": "lowest-price"}

        fleet_response = ctxt.ec2.create_fleet(**fleet_request)

        if errors := fleet_response.get("Errors"):
            # Errors only occur occasionally
            # But when they occur, we want to know about them help us debug the problem
            print(errors)  # noqa

        try:
            assert "FleetId" in fleet_response
            fleet_id = fleet_response["FleetId"]
            assert "Instances" in fleet_response
            instances = fleet_response["Instances"]
            total_instance_ids = [i for inst in instances for i in inst["InstanceIds"]]

            assert len(total_instance_ids) == total_capacity

            # Verify lifecycles if mixed
            if on_demand_count > 0 and spot_count > 0:
                lifecycles = {instance["Lifecycle"] for instance in instances}
                assert "on-demand" in lifecycles
                assert "spot" in lifecycles

            for instance in instances:
                assert "LaunchTemplateAndOverrides" in instance

                lt_and_overrides = instance["LaunchTemplateAndOverrides"]
                assert "LaunchTemplateSpecification" in lt_and_overrides

                lt_spec_response = lt_and_overrides["LaunchTemplateSpecification"]
                assert lt_spec_response["LaunchTemplateId"] == ctxt.lt_id

                assert "Overrides" in lt_and_overrides
                overrides_response = lt_and_overrides["Overrides"]
                assert overrides_response["InstanceType"] in expected_instance_types
        finally:
            ctxt.ec2.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True)
