import json
from copy import deepcopy

import boto3
import pytest

from moto import mock_aws
from moto.core.utils import pascal_to_camelcase, remap_nested_keys


@mock_aws
def test_update_task_definition_family_through_cloudformation_should_trigger_a_replacement():
    template1 = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "Family": "testTaskDefinition1",
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Cpu": "200",
                            "Memory": "500",
                            "Essential": "true",
                        }
                    ],
                    "Volumes": [],
                },
            }
        },
    }
    template1_json = json.dumps(template1)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template1_json)

    template2 = deepcopy(template1)
    template2["Resources"]["testTaskDefinition"]["Properties"]["Family"] = (
        "testTaskDefinition2"
    )
    template2_json = json.dumps(template2)
    cfn_conn.update_stack(StackName="test_stack", TemplateBody=template2_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_task_definitions(familyPrefix="testTaskDefinition2")
    assert len(resp["taskDefinitionArns"]) == 1
    assert resp["taskDefinitionArns"][0].endswith("testTaskDefinition2:1")


@mock_aws
def test_create_service_through_cloudformation():
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testCluster": {
                "Type": "AWS::ECS::Cluster",
                "Properties": {"ClusterName": "testcluster"},
            },
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Cpu": "200",
                            "Memory": "500",
                            "Essential": "true",
                        }
                    ],
                    "Volumes": [],
                },
            },
            "testService": {
                "Type": "AWS::ECS::Service",
                "Properties": {
                    "Cluster": {"Ref": "testCluster"},
                    "DesiredCount": 10,
                    "TaskDefinition": {"Ref": "testTaskDefinition"},
                },
            },
        },
    }
    template_json = json.dumps(template)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_services(cluster="testcluster")
    assert len(resp["serviceArns"]) == 1


@mock_aws
def test_create_service_through_cloudformation_without_desiredcount():
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testCluster": {
                "Type": "AWS::ECS::Cluster",
                "Properties": {"ClusterName": "testcluster"},
            },
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Cpu": "200",
                            "Memory": "500",
                            "Essential": "true",
                        }
                    ],
                    "Volumes": [],
                },
            },
            "testService": {
                "Type": "AWS::ECS::Service",
                "Properties": {
                    "Cluster": {"Ref": "testCluster"},
                    "SchedulingStrategy": "DAEMON",
                    "TaskDefinition": {"Ref": "testTaskDefinition"},
                },
            },
        },
    }
    template_json = json.dumps(template)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_services(cluster="testcluster")
    assert len(resp["serviceArns"]) == 1


@mock_aws
def test_update_service_through_cloudformation_should_trigger_replacement():
    template1 = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testCluster": {
                "Type": "AWS::ECS::Cluster",
                "Properties": {"ClusterName": "testcluster"},
            },
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Cpu": "200",
                            "Memory": "500",
                            "Essential": "true",
                        }
                    ],
                    "Volumes": [],
                },
            },
            "testService": {
                "Type": "AWS::ECS::Service",
                "Properties": {
                    "Cluster": {"Ref": "testCluster"},
                    "TaskDefinition": {"Ref": "testTaskDefinition"},
                    "DesiredCount": 10,
                },
            },
        },
    }
    template_json1 = json.dumps(template1)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json1)
    template2 = deepcopy(template1)
    template2["Resources"]["testCluster"]["Properties"]["ClusterName"] = "updated name"
    template2["Resources"]["testService"]["Properties"]["DesiredCount"] = 5
    template2_json = json.dumps(template2)
    cfn_conn.update_stack(StackName="test_stack", TemplateBody=template2_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_services(cluster="testcluster")
    assert len(resp["serviceArns"]) == 1


@mock_aws
def test_update_service_through_cloudformation_without_desiredcount():
    template1 = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testCluster": {
                "Type": "AWS::ECS::Cluster",
                "Properties": {"ClusterName": "testcluster"},
            },
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Cpu": "200",
                            "Memory": "500",
                            "Essential": "true",
                        }
                    ],
                    "Volumes": [],
                },
            },
            "testService": {
                "Type": "AWS::ECS::Service",
                "Properties": {
                    "Cluster": {"Ref": "testCluster"},
                    "TaskDefinition": {"Ref": "testTaskDefinition"},
                },
            },
        },
    }
    template_json1 = json.dumps(template1)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json1)
    template2 = deepcopy(template1)
    template2["Resources"]["testTaskDefinition"]["Properties"]["ContainerDefinitions"][
        0
    ]["Cpu"] = "300"
    template2_json = json.dumps(template2)
    cfn_conn.update_stack(StackName="test_stack", TemplateBody=template2_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_services(cluster="testcluster")
    assert len(resp["serviceArns"]) == 1


@mock_aws
def test_create_cluster_through_cloudformation():
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testCluster": {
                "Type": "AWS::ECS::Cluster",
                "Properties": {"ClusterName": "testcluster"},
            }
        },
    }
    template_json = json.dumps(template)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_clusters()
    assert len(resp["clusterArns"]) == 0

    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)

    resp = ecs_conn.list_clusters()
    assert len(resp["clusterArns"]) == 1


@mock_aws
def test_create_cluster_through_cloudformation_no_name():
    # cloudformation should create a cluster name for you if you do not provide it
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#cfn-ecs-cluster-clustername
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {"testCluster": {"Type": "AWS::ECS::Cluster"}},
    }
    template_json = json.dumps(template)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_clusters()
    assert len(resp["clusterArns"]) == 1


@mock_aws
def test_update_cluster_name_through_cloudformation_should_trigger_a_replacement():
    template1 = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testCluster": {
                "Type": "AWS::ECS::Cluster",
                "Properties": {"ClusterName": "testcluster1"},
            }
        },
    }
    template2 = deepcopy(template1)
    template2["Resources"]["testCluster"]["Properties"]["ClusterName"] = "testcluster2"
    template1_json = json.dumps(template1)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    stack_resp = cfn_conn.create_stack(
        StackName="test_stack", TemplateBody=template1_json
    )
    ecs_conn = boto3.client("ecs", region_name="us-west-1")

    template2_json = json.dumps(template2)
    cfn_conn.update_stack(StackName=stack_resp["StackId"], TemplateBody=template2_json)
    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_clusters()

    assert len(resp["clusterArns"]) == 2

    cluster1 = ecs_conn.describe_clusters(clusters=["testcluster1"])["clusters"][0]
    assert cluster1["status"] == "INACTIVE"

    cluster1 = ecs_conn.describe_clusters(clusters=["testcluster2"])["clusters"][0]
    assert cluster1["status"] == "ACTIVE"


@mock_aws
def test_create_task_definition_through_cloudformation():
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Cpu": 200,
                            "Memory": 500,
                            "Essential": True,
                            "PortMappings": [
                                {
                                    "ContainerPort": 123,
                                    "HostPort": 123,
                                    "Protocol": "tcp",
                                },
                            ],
                        }
                    ],
                    "Volumes": [{"Name": "ecs-vol"}],
                },
            }
        },
    }
    template_json = json.dumps(template)
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = "test_stack"
    cfn_conn.create_stack(StackName=stack_name, TemplateBody=template_json)

    ecs_conn = boto3.client("ecs", region_name="us-west-1")
    resp = ecs_conn.list_task_definitions()
    assert len(resp["taskDefinitionArns"]) == 1
    task_definition_arn = resp["taskDefinitionArns"][0]

    task_definition_details = cfn_conn.describe_stack_resource(
        StackName=stack_name, LogicalResourceId="testTaskDefinition"
    )["StackResourceDetail"]
    assert task_definition_details["PhysicalResourceId"] == task_definition_arn

    task_definition = ecs_conn.describe_task_definition(
        taskDefinition=task_definition_arn
    ).get("taskDefinition")
    expected_properties = remap_nested_keys(
        template["Resources"]["testTaskDefinition"]["Properties"], pascal_to_camelcase
    )
    assert task_definition["volumes"] == expected_properties["volumes"]
    for key, value in expected_properties["containerDefinitions"][0].items():
        assert task_definition["containerDefinitions"][0][key] == value


@mock_aws
def test_cloudformation_container_definition_validation():
    cfn_conn = boto3.client("cloudformation", region_name="us-west-1")

    # No task memory, no container memory
    invalid_definition = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                        }
                    ],
                },
            }
        },
    }
    invalid_json = json.dumps(invalid_definition)
    # Currently throws a JSON exception, but we expect an XML exception here
    with pytest.raises(Exception):  # noqa: B017 Do not assert blind exception: `Exception`
        cfn_conn.create_stack(StackName="invalid-stack", TemplateBody=invalid_json)

    # No task memory, has container memory
    valid_definition = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                            "Memory": 1024,
                        }
                    ],
                },
            }
        },
    }
    valid_json = json.dumps(valid_definition)
    name = "valid-stack-container-memory"
    cfn_conn.create_stack(StackName=name, TemplateBody=valid_json)
    cfn_conn.update_stack(StackName=name, TemplateBody=valid_json)

    # Has task memory, no container memory
    valid_definition = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Description": "ECS Cluster Test CloudFormation",
        "Resources": {
            "testTaskDefinition": {
                "Type": "AWS::ECS::TaskDefinition",
                "Properties": {
                    "Memory": 1024,
                    "ContainerDefinitions": [
                        {
                            "Name": "ecs-sample",
                            "Image": "amazon/amazon-ecs-sample",
                        }
                    ],
                },
            }
        },
    }

    valid_json = json.dumps(valid_definition)
    name = "valid-stack-task-memory"
    cfn_conn.create_stack(StackName=name, TemplateBody=valid_json)
    cfn_conn.update_stack(StackName=name, TemplateBody=valid_json)
