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

import json

from awscli.customizations.ecs.deploy import (
    MAX_WAIT_MIN,
    TIMEOUT_BUFFER_MIN,
    CodeDeployer,
)
from awscli.customizations.ecs.filehelpers import (
    get_app_name,
    get_deploy_group_name,
)
from awscli.testutils import BaseAWSCommandParamsTest, FileCreator


class TestDeployCommand(BaseAWSCommandParamsTest):
    PREFIX = 'ecs deploy '

    TASK_DEFINITION_JSON = {
        "family": "test",
        "containerDefinitions": [],
        "cpu": "256",
        "memory": '512',
    }

    JSON_APPSPEC = """
    {
        "version": 0.0,
        "resources": [{
            "TestService": {
                "type": "AWS::ECS::Service",
                "properties": {
                    "taskDefinition": "arn:aws:ecs::123:task-definition:1",
                    "loadBalancerInfo": {
                        "containerName": "web",
                        "containerPort": 80
                    }
                }
            }
        }]
    }
    """

    BAD_APPSPEC = """
    version: 0.0
    resources:
      - TestService:
          type: AWS::Lambda::Function
          properties:
            name: myFunction
    """

    YAML_APPSPEC = """
    version: 0.0
    resources:
      - TestService:
          type: AWS::ECS::Service
          properties:
            taskDefinition: arn:aws:ecs::123:task-definition:1
            loadBalancerInfo:
              containerName: web
              containerPort: 80
    """

    APPSPEC_DICT = {
        "version": 0.0,
        "resources": [
            {
                "TestService": {
                    "type": "AWS::ECS::Service",
                    "properties": {
                        "taskDefinition": "arn:aws:ecs::123:task-definition:1",
                        "loadBalancerInfo": {
                            "containerName": "web",
                            "containerPort": 80,
                        },
                    },
                }
            }
        ],
    }

    def setUp(self):
        super(TestDeployCommand, self).setUp()
        # setup required values
        files = FileCreator()
        self.task_def_file = files.create_file(
            'taskDef.json', json.dumps(self.TASK_DEFINITION_JSON), mode='w'
        )
        self.appspec_file = files.create_file(
            'appspec.yaml', self.YAML_APPSPEC, mode='w'
        )
        self.appspec_file_json = files.create_file(
            'appspec.json', self.JSON_APPSPEC, mode='w'
        )
        self.service_name = 'serviceTest'
        self.service_arn = 'arn:aws:ecs:::service/serviceTest'
        # setup default optional values
        self.cluster_name = 'default'
        self.cluster_arn = 'arn:aws:ecs:::cluster/default'
        self.application_name = get_app_name(
            self.service_name, self.cluster_name, None
        )
        self.deployment_group_name = get_deploy_group_name(
            self.service_name, self.cluster_name, None
        )
        # setup test response resources
        self.missing_properties_appspec = files.create_file(
            'appspec_bad.yaml', self.BAD_APPSPEC, mode='w'
        )
        self.task_definition_arn = (
            'arn:aws:ecs::1234567890:task-definition\\test:2'
        )
        self.deployment_id = 'd-1234567XX'
        self.mock_deployer = CodeDeployer(None, self.APPSPEC_DICT)
        self.mock_deployer.update_task_def_arn(self.task_definition_arn)
        self.expected_stdout = (
            "Successfully registered new ECS task "
            "definition " + self.task_definition_arn + "\n"
            "Successfully created deployment " + self.deployment_id + "\n"
            "Waiting for "
            + self.deployment_id
            + " to succeed (will wait up to 30 minutes)..."
            "\nSuccessfully deployed "
            + self.task_definition_arn
            + " to service '"
            + self.service_name
            + "'\n"
        )

    def tearDown(self):
        super(TestDeployCommand, self).tearDown()

    def test_deploy_with_defaults(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.appspec_file

        expected_create_deployment_params = (
            self.mock_deployer._get_create_deploy_request(
                self.application_name, self.deployment_group_name
            )
        )

        self.parsed_responses = self._get_parsed_responses(
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
        )

        expected_params = self._get_expected_params(
            self.service_name,
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
            expected_create_deployment_params,
        )

        stdout, _, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 0
        )

        self.assertEqual(stdout, self.expected_stdout)

    def test_deploy_with_default_arns(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_arn
        cmdline += ' --cluster ' + self.cluster_arn
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.appspec_file

        expected_create_deployment_params = (
            self.mock_deployer._get_create_deploy_request(
                self.application_name, self.deployment_group_name
            )
        )

        self.parsed_responses = self._get_parsed_responses(
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
        )

        expected_params = self._get_expected_params(
            self.service_arn,
            self.cluster_arn,
            self.application_name,
            self.deployment_group_name,
            expected_create_deployment_params,
        )

        stdout, _, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 0
        )

        self.assertEqual(stdout, self.expected_stdout)

    def test_deploy_with_json_appspec(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.appspec_file_json

        expected_create_deployment_params = (
            self.mock_deployer._get_create_deploy_request(
                self.application_name, self.deployment_group_name
            )
        )

        self.parsed_responses = self._get_parsed_responses(
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
        )

        expected_params = self._get_expected_params(
            self.service_name,
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
            expected_create_deployment_params,
        )

        stdout, _, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 0
        )

        self.assertEqual(stdout, self.expected_stdout)

    def test_deploy_with_custom_timeout(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.appspec_file

        expected_create_deployment_params = (
            self.mock_deployer._get_create_deploy_request(
                self.application_name, self.deployment_group_name
            )
        )

        custom_deployment_grp_response = {
            'deploymentGroupInfo': {
                'applicationName': self.application_name,
                'deploymentGroupName': self.deployment_group_name,
                'computePlatform': 'ECS',
                'blueGreenDeploymentConfiguration': {
                    'deploymentReadyOption': {'waitTimeInMinutes': 5},
                    'terminateBlueInstancesOnDeploymentSuccess': {
                        'terminationWaitTimeInMinutes': 60
                    },
                },
                'ecsServices': [
                    {
                        'serviceName': self.service_name,
                        'clusterName': self.cluster_name,
                    }
                ],
            }
        }
        custom_timeout = str(60 + 5 + TIMEOUT_BUFFER_MIN)

        self.parsed_responses = self._get_parsed_responses(
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
        )

        self.parsed_responses[2] = custom_deployment_grp_response

        expected_params = self._get_expected_params(
            self.service_name,
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
            expected_create_deployment_params,
        )

        expected_stdout = (
            "Successfully registered new ECS task "
            "definition " + self.task_definition_arn + "\n"
            "Successfully created deployment " + self.deployment_id + "\n"
            "Waiting for "
            + self.deployment_id
            + " to succeed (will wait up to "
            + custom_timeout
            + " minutes)...\nSuccessfully deployed "
            + self.task_definition_arn
            + " to service '"
            + self.service_name
            + "'\n"
        )

        stdout, _, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 0
        )

        self.assertEqual(stdout, expected_stdout)

    def test_deploy_with_max_timeout(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.appspec_file

        expected_create_deployment_params = (
            self.mock_deployer._get_create_deploy_request(
                self.application_name, self.deployment_group_name
            )
        )

        custom_deployment_grp_response = {
            'deploymentGroupInfo': {
                'applicationName': self.application_name,
                'deploymentGroupName': self.deployment_group_name,
                'computePlatform': 'ECS',
                'blueGreenDeploymentConfiguration': {
                    'deploymentReadyOption': {'waitTimeInMinutes': 90},
                    'terminateBlueInstancesOnDeploymentSuccess': {
                        'terminationWaitTimeInMinutes': 300
                    },
                },
                'ecsServices': [
                    {
                        'serviceName': self.service_name,
                        'clusterName': self.cluster_name,
                    }
                ],
            }
        }
        max_timeout = str(MAX_WAIT_MIN)

        self.parsed_responses = self._get_parsed_responses(
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
        )

        self.parsed_responses[2] = custom_deployment_grp_response

        expected_params = self._get_expected_params(
            self.service_name,
            self.cluster_name,
            self.application_name,
            self.deployment_group_name,
            expected_create_deployment_params,
        )

        expected_stdout = (
            "Successfully registered new ECS task "
            "definition " + self.task_definition_arn + "\n"
            "Successfully created deployment " + self.deployment_id + "\n"
            "Waiting for "
            + self.deployment_id
            + " to succeed (will wait up to "
            + max_timeout
            + " minutes)...\nSuccessfully deployed "
            + self.task_definition_arn
            + " to service '"
            + self.service_name
            + "'\n"
        )

        stdout, _, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 0
        )

        self.assertEqual(stdout, expected_stdout)

    def test_deploy_with_optional_values(self):
        custom_app = 'myOtherApp'
        custom_dgp = 'myOtherDgp'
        custom_cluster = 'myOtherCluster'

        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.appspec_file
        cmdline += ' --cluster ' + self.cluster_name
        cmdline += ' --codedeploy-application ' + custom_app
        cmdline += ' --codedeploy-deployment-group ' + custom_dgp
        cmdline += ' --cluster ' + custom_cluster

        expected_create_deployment_params = (
            self.mock_deployer._get_create_deploy_request(
                custom_app, custom_dgp
            )
        )

        self.parsed_responses = self._get_parsed_responses(
            custom_cluster, custom_app, custom_dgp
        )

        expected_params = self._get_expected_params(
            self.service_name,
            custom_cluster,
            custom_app,
            custom_dgp,
            expected_create_deployment_params,
        )

        stdout, _, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 0
        )

        self.assertEqual(stdout, self.expected_stdout)

    def test_deploy_error_missing_appspec_property(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.missing_properties_appspec

        self.parsed_responses = [
            {
                'services': [
                    {
                        'serviceArn': self.service_arn,
                        'serviceName': self.service_name,
                        'clusterArn': 'arn:aws:ecs:::cluster/'
                        + self.cluster_name,
                    }
                ]
            },
            {
                'application': {
                    'applicationId': '876uyh6-45tdfg',
                    'applicationName': self.application_name,
                    'computePlatform': 'ECS',
                }
            },
            {
                'deploymentGroupInfo': {
                    'applicationName': self.application_name,
                    'deploymentGroupName': self.deployment_group_name,
                    'computePlatform': 'ECS',
                    'blueGreenDeploymentConfiguration': {
                        'deploymentReadyOption': {'waitTimeInMinutes': 5},
                        'terminateBlueInstancesOnDeploymentSuccess': {
                            'terminationWaitTimeInMinutes': 10
                        },
                    },
                    'ecsServices': [
                        {
                            'serviceName': self.service_name,
                            'clusterName': self.cluster_name,
                        }
                    ],
                }
            },
            {
                'taskDefinition': {
                    'taskDefinitionArn': self.task_definition_arn,
                    'family': 'test',
                    'containerDefinitions': [],
                }
            },
        ]

        expected_params = [
            {
                'operation': 'DescribeServices',
                'params': {
                    'cluster': self.cluster_name,
                    'services': [self.service_name],
                },
            },
            {
                'operation': 'GetApplication',
                'params': {'applicationName': self.application_name},
            },
            {
                'operation': 'GetDeploymentGroup',
                'params': {
                    'applicationName': self.application_name,
                    'deploymentGroupName': self.deployment_group_name,
                },
            },
            {
                'operation': 'RegisterTaskDefinition',
                'params': self.TASK_DEFINITION_JSON,
            },
        ]

        expected_stdout = (
            "Successfully registered new ECS task "
            "definition " + self.task_definition_arn + "\n"
        )

        expected_stderr = (
            "\naws: [ERROR]: Resource 'properties' must "
            "include property 'taskDefinition'\n"
        )

        stdout, stderr, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 255
        )

        self.assertEqual(stdout, expected_stdout)
        self.assertEqual(stderr, expected_stderr)

    def test_deploy_error_invalid_platform(self):
        cmdline = self.PREFIX
        cmdline += '--service ' + self.service_name
        cmdline += ' --task-definition ' + self.task_def_file
        cmdline += ' --codedeploy-appspec ' + self.missing_properties_appspec

        self.parsed_responses = [
            {
                'services': [
                    {
                        'serviceArn': self.service_arn,
                        'serviceName': self.service_name,
                        'clusterArn': 'arn:aws:ecs:::cluster/'
                        + self.cluster_name,
                    }
                ]
            },
            {
                'application': {
                    'applicationId': '876uyh6-45tdfg',
                    'applicationName': self.application_name,
                    'computePlatform': 'Server',
                }
            },
            {
                'deploymentGroupInfo': {
                    'applicationName': self.application_name,
                    'deploymentGroupName': self.deployment_group_name,
                    'computePlatform': 'ECS',
                    'ecsServices': [
                        {
                            'serviceName': self.service_name,
                            'clusterName': self.cluster_name,
                        }
                    ],
                }
            },
        ]

        expected_params = [
            {
                'operation': 'DescribeServices',
                'params': {
                    'cluster': self.cluster_name,
                    'services': [self.service_name],
                },
            },
            {
                'operation': 'GetApplication',
                'params': {'applicationName': self.application_name},
            },
            {
                'operation': 'GetDeploymentGroup',
                'params': {
                    'applicationName': self.application_name,
                    'deploymentGroupName': self.deployment_group_name,
                },
            },
        ]

        expected_stderr = (
            "\naws: [ERROR]: Application '"
            + self.application_name
            + "' must support 'ECS' compute platform\n"
        )

        stdout, stderr, _ = self.assert_params_list_for_cmd(
            cmdline, expected_params, 255
        )

        self.assertEqual('', stdout)
        self.assertEqual(expected_stderr, stderr)

    def assert_params_list_for_cmd(
        self, cmd, params, expected_rc=0, stderr_contains=None
    ):
        stdout, stderr, rc = self.run_cmd(cmd, expected_rc)
        if stderr_contains is not None:
            self.assertIn(stderr_contains, stderr)

        self.assertEqual(len(self.operations_called), len(params))
        for i, param in enumerate(params):
            self.assertEqual(
                self.operations_called[i][0].name, param['operation']
            )
            self.assertEqual(self.operations_called[i][1], param['params'])

        return stdout, stderr, rc

    def _get_expected_params(
        self,
        service_name,
        cluster_name,
        app_name,
        dgp_name,
        create_deployment_params,
    ):
        return [
            {
                'operation': 'DescribeServices',
                'params': {
                    'cluster': cluster_name,
                    'services': [service_name],
                },
            },
            {
                'operation': 'GetApplication',
                'params': {'applicationName': app_name},
            },
            {
                'operation': 'GetDeploymentGroup',
                'params': {
                    'applicationName': app_name,
                    'deploymentGroupName': dgp_name,
                },
            },
            {
                'operation': 'RegisterTaskDefinition',
                'params': self.TASK_DEFINITION_JSON,
            },
            {
                'operation': 'CreateDeployment',
                'params': create_deployment_params,
            },
            {
                'operation': 'GetDeployment',
                'params': {'deploymentId': self.deployment_id},
            },
        ]

    def _get_parsed_responses(self, cluster_name, app_name, dgp_name):
        return [
            {
                'services': [
                    {
                        'serviceArn': self.service_arn,
                        'serviceName': self.service_name,
                        'clusterArn': 'arn:aws:ecs:::cluster/' + cluster_name,
                    }
                ]
            },
            {
                'application': {
                    'applicationId': '876uyh6-45tdfg',
                    'applicationName': app_name,
                    'computePlatform': 'ECS',
                }
            },
            {
                'deploymentGroupInfo': {
                    'applicationName': app_name,
                    'deploymentGroupName': dgp_name,
                    'computePlatform': 'ECS',
                    'blueGreenDeploymentConfiguration': {
                        'deploymentReadyOption': {'waitTimeInMinutes': 5},
                        'terminateBlueInstancesOnDeploymentSuccess': {
                            'terminationWaitTimeInMinutes': 10
                        },
                    },
                    'ecsServices': [
                        {
                            'serviceName': self.service_name,
                            'clusterName': cluster_name,
                        }
                    ],
                }
            },
            {
                'taskDefinition': {
                    'taskDefinitionArn': self.task_definition_arn,
                    'family': 'test',
                    'containerDefinitions': [],
                }
            },
            {'deploymentId': self.deployment_id},
            {
                'deploymentInfo': {
                    'applicationName': app_name,
                    'status': 'Succeeded',
                }
            },
        ]
