import json
import re

import boto3

from moto import mock_aws


@mock_aws
def test_s3_bucket_cloudformation_basic():
    s3_client = boto3.client("s3", region_name="us-east-1")
    cf_client = boto3.client("cloudformation", region_name="us-east-1")

    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {"testInstance": {"Type": "AWS::S3::Bucket", "Properties": {}}},
        "Outputs": {"Bucket": {"Value": {"Ref": "testInstance"}}},
    }
    template_json = json.dumps(template)
    cf_client.create_stack(StackName="test_stack", TemplateBody=template_json)
    stack_description = cf_client.describe_stacks(StackName="test_stack")["Stacks"][0]

    s3_client.head_bucket(Bucket=stack_description["Outputs"][0]["OutputValue"])


@mock_aws
def test_s3_bucket_cloudformation_with_properties():
    s3_client = boto3.client("s3", region_name="us-east-1")
    cf_client = boto3.client("cloudformation", region_name="us-east-1")

    bucket_name = "MyBucket"
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "testInstance": {
                "Type": "AWS::S3::Bucket",
                "Properties": {
                    "BucketName": bucket_name,
                    "BucketEncryption": {
                        "ServerSideEncryptionConfiguration": [
                            {
                                "ServerSideEncryptionByDefault": {
                                    "SSEAlgorithm": "AES256"
                                }
                            }
                        ]
                    },
                },
            }
        },
        "Outputs": {"Bucket": {"Value": {"Ref": "testInstance"}}},
    }
    template_json = json.dumps(template)
    cf_client.create_stack(StackName="test_stack", TemplateBody=template_json)
    cf_client.describe_stacks(StackName="test_stack")
    s3_client.head_bucket(Bucket=bucket_name)

    encryption = s3_client.get_bucket_encryption(Bucket=bucket_name)
    assert (
        encryption["ServerSideEncryptionConfiguration"]["Rules"][0][
            "ApplyServerSideEncryptionByDefault"
        ]["SSEAlgorithm"]
        == "AES256"
    )


@mock_aws
def test_s3_bucket_cloudformation_update_no_interruption():
    s3_client = boto3.client("s3", region_name="us-east-1")
    cf_client = boto3.client("cloudformation", region_name="us-east-1")

    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {"testInstance": {"Type": "AWS::S3::Bucket"}},
        "Outputs": {"Bucket": {"Value": {"Ref": "testInstance"}}},
    }
    template_json = json.dumps(template)
    cf_client.create_stack(StackName="test_stack", TemplateBody=template_json)
    stack_description = cf_client.describe_stacks(StackName="test_stack")["Stacks"][0]
    s3_client.head_bucket(Bucket=stack_description["Outputs"][0]["OutputValue"])

    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "testInstance": {
                "Type": "AWS::S3::Bucket",
                "Properties": {
                    "BucketEncryption": {
                        "ServerSideEncryptionConfiguration": [
                            {
                                "ServerSideEncryptionByDefault": {
                                    "SSEAlgorithm": "AES256"
                                }
                            }
                        ]
                    }
                },
            }
        },
        "Outputs": {"Bucket": {"Value": {"Ref": "testInstance"}}},
    }
    template_json = json.dumps(template)
    cf_client.update_stack(StackName="test_stack", TemplateBody=template_json)
    encryption = s3_client.get_bucket_encryption(
        Bucket=stack_description["Outputs"][0]["OutputValue"]
    )
    assert (
        encryption["ServerSideEncryptionConfiguration"]["Rules"][0][
            "ApplyServerSideEncryptionByDefault"
        ]["SSEAlgorithm"]
        == "AES256"
    )


@mock_aws
def test_s3_bucket_cloudformation_update_replacement():
    s3_client = boto3.client("s3", region_name="us-east-1")
    cf_client = boto3.client("cloudformation", region_name="us-east-1")

    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {"testInstance": {"Type": "AWS::S3::Bucket"}},
        "Outputs": {"Bucket": {"Value": {"Ref": "testInstance"}}},
    }
    template_json = json.dumps(template)
    cf_client.create_stack(StackName="test_stack", TemplateBody=template_json)
    stack_description = cf_client.describe_stacks(StackName="test_stack")["Stacks"][0]
    s3_client.head_bucket(Bucket=stack_description["Outputs"][0]["OutputValue"])

    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "testInstance": {
                "Type": "AWS::S3::Bucket",
                "Properties": {"BucketName": "MyNewBucketName"},
            }
        },
        "Outputs": {"Bucket": {"Value": {"Ref": "testInstance"}}},
    }
    template_json = json.dumps(template)
    cf_client.update_stack(StackName="test_stack", TemplateBody=template_json)
    stack_description = cf_client.describe_stacks(StackName="test_stack")["Stacks"][0]
    s3_client.head_bucket(Bucket=stack_description["Outputs"][0]["OutputValue"])


@mock_aws
def test_s3_bucket_cloudformation_outputs():
    region_name = "us-east-1"
    s3_client = boto3.client("s3", region_name=region_name)
    cf_client = boto3.resource("cloudformation", region_name=region_name)
    stack_name = "test-stack"
    bucket_name = "test-bucket"
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "TestBucket": {
                "Type": "AWS::S3::Bucket",
                "Properties": {"BucketName": bucket_name},
            }
        },
        "Outputs": {
            "BucketARN": {
                "Value": {"Fn::GetAtt": ["TestBucket", "Arn"]},
                "Export": {"Name": {"Fn::Sub": "${AWS::StackName}:BucketARN"}},
            },
            "BucketDomainName": {
                "Value": {"Fn::GetAtt": ["TestBucket", "DomainName"]},
                "Export": {"Name": {"Fn::Sub": "${AWS::StackName}:BucketDomainName"}},
            },
            "BucketDualStackDomainName": {
                "Value": {"Fn::GetAtt": ["TestBucket", "DualStackDomainName"]},
                "Export": {
                    "Name": {"Fn::Sub": "${AWS::StackName}:BucketDualStackDomainName"}
                },
            },
            "BucketRegionalDomainName": {
                "Value": {"Fn::GetAtt": ["TestBucket", "RegionalDomainName"]},
                "Export": {
                    "Name": {"Fn::Sub": "${AWS::StackName}:BucketRegionalDomainName"}
                },
            },
            "BucketWebsiteURL": {
                "Value": {"Fn::GetAtt": ["TestBucket", "WebsiteURL"]},
                "Export": {"Name": {"Fn::Sub": "${AWS::StackName}:BucketWebsiteURL"}},
            },
            "BucketName": {
                "Value": {"Ref": "TestBucket"},
                "Export": {"Name": {"Fn::Sub": "${AWS::StackName}:BucketName"}},
            },
        },
    }
    cf_client.create_stack(StackName=stack_name, TemplateBody=json.dumps(template))
    outputs_list = cf_client.Stack(stack_name).outputs
    output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list}
    s3_client.head_bucket(Bucket=output["BucketName"])
    assert re.match(f"arn:aws:s3.+{bucket_name}", output["BucketARN"])
    assert output["BucketDomainName"] == f"{bucket_name}.s3.amazonaws.com"
    assert output["BucketDualStackDomainName"] == (
        f"{bucket_name}.s3.dualstack.{region_name}.amazonaws.com"
    )
    assert output["BucketRegionalDomainName"] == (
        f"{bucket_name}.s3.{region_name}.amazonaws.com"
    )
    assert output["BucketWebsiteURL"] == (
        f"http://{bucket_name}.s3-website.{region_name}.amazonaws.com"
    )
    assert output["BucketName"] == bucket_name