import json
from unittest import TestCase
from unittest.mock import MagicMock, patch

from samtranslator.model.apigateway import ApiGatewayDeployment
from samtranslator.translator.transform import transform

from tests.plugins.application.test_serverless_app_plugin import mock_get_region
from tests.translator.helpers import get_template_parameter_values

mock_policy_loader = MagicMock()
mock_policy_loader.load.return_value = {
    "AmazonDynamoDBFullAccess": "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess",
    "AmazonDynamoDBReadOnlyAccess": "arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess",
    "AWSLambdaRole": "arn:aws:iam::aws:policy/service-role/AWSLambdaRole",
}


@patch("botocore.client.ClientEndpointBridge._check_default_region", mock_get_region)
def test_redeploy_explicit_api():
    """
    Test to verify that we will redeploy an API when Swagger document changes
    :return:
    """
    manifest = {
        "Transform": "AWS::Serverless-2016-10-31",
        "Resources": {
            "ExplicitApi": {
                "Type": "AWS::Serverless::Api",
                "Properties": {"StageName": "prod", "DefinitionUri": "s3://mybucket/swagger.json?versionId=123"},
            }
        },
    }
    original_deployment_ids = translate_and_find_deployment_ids(manifest)

    # Now update the API specification. This should redeploy the API by creating a new deployment resource
    manifest["Resources"]["ExplicitApi"]["Properties"]["DefinitionUri"] = "s3://mybucket/swagger.json?versionId=456"
    updated_deployment_ids = translate_and_find_deployment_ids(manifest)

    assert original_deployment_ids != updated_deployment_ids

    # Now, update an unrelated property. This should NOT generate a new deploymentId
    manifest["Resources"]["ExplicitApi"]["Properties"]["StageName"] = "newStageName"
    assert updated_deployment_ids == translate_and_find_deployment_ids(manifest)


@patch("botocore.client.ClientEndpointBridge._check_default_region", mock_get_region)
def test_redeploy_implicit_api():
    manifest = {
        "Transform": "AWS::Serverless-2016-10-31",
        "Resources": {
            "FirstLambdaFunction": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "InitialFunction",
                    "CodeUri": "s3://bucket/code.zip",
                    "Handler": "index.handler",
                    "Runtime": "nodejs12.x",
                    "Events": {"MyApi": {"Type": "Api", "Properties": {"Path": "/first", "Method": "get"}}},
                },
            },
            "SecondLambdaFunction": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "CodeUri": "s3://bucket/code.zip",
                    "Handler": "index.handler",
                    "Runtime": "nodejs12.x",
                    "Events": {"MyApi": {"Type": "Api", "Properties": {"Path": "/second", "Method": "get"}}},
                },
            },
        },
    }

    original_deployment_ids = translate_and_find_deployment_ids(manifest)

    # Update API of one Lambda function should redeploy API
    manifest["Resources"]["FirstLambdaFunction"]["Properties"]["Events"]["MyApi"]["Properties"]["Method"] = "post"
    first_updated_deployment_ids = translate_and_find_deployment_ids(manifest)
    assert original_deployment_ids != first_updated_deployment_ids

    # Update API of second Lambda function should redeploy API
    manifest["Resources"]["SecondLambdaFunction"]["Properties"]["Events"]["MyApi"]["Properties"]["Method"] = "post"
    second_updated_deployment_ids = translate_and_find_deployment_ids(manifest)
    assert first_updated_deployment_ids != second_updated_deployment_ids

    # Now, update an unrelated property. This should NOT generate a new deploymentId
    manifest["Resources"]["SecondLambdaFunction"]["Properties"]["Runtime"] = "java"
    assert second_updated_deployment_ids == translate_and_find_deployment_ids(manifest)

    manifest["Resources"]["FirstLambdaFunction"]["Properties"]["FunctionName"] = "ChangedFunctionName"
    fourth_updated_deployment_ids = translate_and_find_deployment_ids(manifest)
    assert fourth_updated_deployment_ids != second_updated_deployment_ids


@patch("boto3.session.Session.region_name", "ap-southeast-1")
def translate_and_find_deployment_ids(manifest):
    parameter_values = get_template_parameter_values()
    output_fragment = transform(manifest, parameter_values, mock_policy_loader)
    print(json.dumps(output_fragment, indent=2))

    deployment_ids = set()
    for key, value in output_fragment["Resources"].items():
        if value["Type"] == "AWS::ApiGateway::Deployment":
            deployment_ids.add(key)

    return deployment_ids


class TestApiGatewayDeploymentResource(TestCase):
    @patch("samtranslator.translator.logical_id_generator.LogicalIdGenerator")
    def test_make_auto_deployable_with_swagger_dict(self, LogicalIdGeneratorMock):
        prefix = "prefix"
        generator_mock = LogicalIdGeneratorMock.return_value
        stage = MagicMock()
        id_val = "SomeLogicalId"
        full_hash = "127e3fb91142ab1ddc5f5446adb094442581a90d"
        generator_mock.gen.return_value = id_val
        generator_mock.get_hash.return_value = full_hash

        swagger = {"a": "b"}
        deployment = ApiGatewayDeployment(logical_id=prefix)
        deployment.make_auto_deployable(stage, swagger=swagger)

        self.assertEqual(deployment.logical_id, id_val)
        self.assertEqual(deployment.Description, f"RestApi deployment id: {full_hash}")

        LogicalIdGeneratorMock.assert_called_once_with(prefix, str(swagger))
        generator_mock.gen.assert_called_once_with()
        generator_mock.get_hash.assert_called_once_with(length=40)  # getting full SHA
        stage.update_deployment_ref.assert_called_once_with(id_val)

    @patch("samtranslator.translator.logical_id_generator.LogicalIdGenerator")
    def test_make_auto_deployable_no_swagger(self, LogicalIdGeneratorMock):
        prefix = "prefix"
        stage = MagicMock()
        deployment = ApiGatewayDeployment(logical_id=prefix)
        deployment.make_auto_deployable(stage, swagger=None)

        self.assertEqual(deployment.logical_id, prefix)
        self.assertEqual(deployment.Description, None)

        LogicalIdGeneratorMock.assert_not_called()
        stage.update_deployment_ref.assert_not_called()
