import boto3
import pytest
from botocore.exceptions import ClientError

from moto import mock_aws
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID

from . import cloudfront_test_scaffolding as scaffold


@mock_aws
def test_create_distribution_s3_minimum():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")

    dist = client.create_distribution(DistributionConfig=config)["Distribution"]
    assert dist["ARN"] == f"arn:aws:cloudfront:{ACCOUNT_ID}:distribution/{dist['Id']}"
    assert dist["Status"] == "InProgress"
    assert "LastModifiedTime" in dist
    assert dist["InProgressInvalidationBatches"] == 0
    assert ".cloudfront.net" in dist["DomainName"]

    assert dist["ActiveTrustedSigners"] == {
        "Enabled": False,
        "Items": [],
        "Quantity": 0,
    }

    assert dist["ActiveTrustedKeyGroups"] == {
        "Enabled": False,
        "Items": [],
        "Quantity": 0,
    }

    config = dist["DistributionConfig"]
    assert config["CallerReference"] == "ref"

    assert config["Aliases"]["Quantity"] == 0

    origins = config["Origins"]
    assert origins["Quantity"] == 1
    assert len(origins["Items"]) == 1
    origin = origins["Items"][0]
    assert origin["Id"] == "origin1"
    assert origin["DomainName"] == "asdf.s3.us-east-1.amazonaws.com"
    assert origin["OriginPath"] == "/example"

    assert origin["CustomHeaders"]["Quantity"] == 0

    assert origin["ConnectionAttempts"] == 3
    assert origin["ConnectionTimeout"] == 10
    assert origin["OriginShield"] == {"Enabled": False}

    assert config["OriginGroups"] == {"Quantity": 0}

    default_cache = config["DefaultCacheBehavior"]
    assert default_cache["TargetOriginId"] == "origin1"

    assert default_cache["TrustedSigners"] == {
        "Enabled": False,
        "Items": [],
        "Quantity": 0,
    }

    assert default_cache["TrustedKeyGroups"] == {
        "Enabled": False,
        "Items": [],
        "Quantity": 0,
    }

    assert default_cache["ViewerProtocolPolicy"] == "allow-all"

    methods = default_cache["AllowedMethods"]
    assert methods["Quantity"] == 2
    assert set(methods["Items"]) == {"HEAD", "GET"}

    cached_methods = methods["CachedMethods"]
    assert cached_methods["Quantity"] == 2
    assert set(cached_methods["Items"]) == {"HEAD", "GET"}

    assert default_cache["SmoothStreaming"] is False
    assert default_cache["Compress"] is True
    assert default_cache["LambdaFunctionAssociations"] == {"Quantity": 0}
    assert default_cache["FunctionAssociations"] == {"Quantity": 0}
    assert default_cache["FieldLevelEncryptionId"] == ""
    assert "CachePolicyId" in default_cache

    assert config["CacheBehaviors"] == {"Quantity": 0}
    assert config["CustomErrorResponses"] == {"Quantity": 0}
    assert config["Comment"] == "an optional comment that's not actually optional"

    assert config["Logging"] == {
        "Bucket": "",
        "Enabled": False,
        "IncludeCookies": False,
        "Prefix": "",
    }

    assert config["PriceClass"] == "PriceClass_All"
    assert config["Enabled"] is False
    assert "WebACLId" in config
    assert config["HttpVersion"] == "http2"
    assert config["IsIPV6Enabled"] is True

    assert config["ViewerCertificate"] == {
        "ACMCertificateArn": "",
        "Certificate": "",
        "CertificateSource": "cloudfront",
        "CloudFrontDefaultCertificate": True,
        "IAMCertificateId": "",
        "MinimumProtocolVersion": "TLSv1",
        "SSLSupportMethod": "",
    }

    restriction = config["Restrictions"]["GeoRestriction"]
    assert restriction["RestrictionType"] == "none"
    assert restriction["Quantity"] == 0

    assert config["WebACLId"] == ""


@mock_aws
def test_create_distribution_with_logging():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")
    config["Logging"] = {
        "Enabled": True,
        "IncludeCookies": True,
        "Bucket": "logging-bucket",
        "Prefix": "logging-bucket",
    }

    resp = client.create_distribution(DistributionConfig=config)
    config = resp["Distribution"]["DistributionConfig"]

    assert config["Logging"] == {
        "Bucket": "logging-bucket",
        "Enabled": True,
        "IncludeCookies": True,
        "Prefix": "logging-bucket",
    }


@mock_aws
def test_create_distribution_with_web_acl():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")
    config["WebACLId"] = "test-web-acl"

    resp = client.create_distribution(DistributionConfig=config)
    config = resp["Distribution"]["DistributionConfig"]

    assert config["WebACLId"] == "test-web-acl"


@mock_aws
def test_create_distribution_with_field_level_encryption_and_real_time_log_config_arn():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")
    real_time_log_config_arn = f"arn:aws:cloudfront::{ACCOUNT_ID}:realtime-log-config/ExampleNameForRealtimeLogConfig"
    config["DefaultCacheBehavior"]["RealtimeLogConfigArn"] = real_time_log_config_arn
    config["DefaultCacheBehavior"]["FieldLevelEncryptionId"] = "K3D5EWEUDCCXON"

    resp = client.create_distribution(DistributionConfig=config)

    config = resp["Distribution"]["DistributionConfig"]
    default_cache = config["DefaultCacheBehavior"]

    assert default_cache["FieldLevelEncryptionId"] == "K3D5EWEUDCCXON"
    assert default_cache["RealtimeLogConfigArn"] == real_time_log_config_arn


@mock_aws
def test_create_distribution_with_georestriction():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")
    config["Restrictions"] = {
        "GeoRestriction": {
            "RestrictionType": "whitelist",
            "Quantity": 2,
            "Items": ["GB", "US"],
        }
    }

    resp = client.create_distribution(DistributionConfig=config)
    config = resp["Distribution"]["DistributionConfig"]

    restriction = config["Restrictions"]["GeoRestriction"]
    assert restriction["RestrictionType"] == "whitelist"
    assert restriction["Quantity"] == 2
    assert "US" in restriction["Items"]
    assert "GB" in restriction["Items"]


@mock_aws
def test_create_distribution_with_allowed_methods():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")
    config["DefaultCacheBehavior"]["AllowedMethods"] = {
        "Quantity": 3,
        "Items": ["GET", "HEAD", "PUT"],
        "CachedMethods": {
            "Quantity": 7,
            "Items": ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
        },
    }

    dist = client.create_distribution(DistributionConfig=config)["Distribution"]

    cache = dist["DistributionConfig"]["DefaultCacheBehavior"]

    assert cache["AllowedMethods"] == {
        "CachedMethods": {
            "Items": ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
            "Quantity": 7,
        },
        "Items": ["GET", "HEAD", "PUT"],
        "Quantity": 3,
    }


@mock_aws
def test_create_distribution_with_origins():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_distribution_config("ref")
    config["Origins"]["Items"][0]["ConnectionAttempts"] = 1
    config["Origins"]["Items"][0]["ConnectionTimeout"] = 2
    config["Origins"]["Items"][0]["OriginShield"] = {
        "Enabled": True,
        "OriginShieldRegion": "east",
    }

    dist = client.create_distribution(DistributionConfig=config)["Distribution"]

    origin = dist["DistributionConfig"]["Origins"]["Items"][0]
    assert origin["ConnectionAttempts"] == 1
    assert origin["ConnectionTimeout"] == 2
    assert origin["OriginShield"] == {"Enabled": True, "OriginShieldRegion": "east"}


@mock_aws
@pytest.mark.parametrize("compress", [True, False])
@pytest.mark.parametrize("qs", [True, False])
@pytest.mark.parametrize("smooth", [True, False])
@pytest.mark.parametrize("ipv6", [True, False])
@pytest.mark.parametrize("aliases", [["alias1", "alias2"], ["alias1"]])
def test_create_distribution_with_additional_fields(
    compress, qs, smooth, ipv6, aliases
):
    client = boto3.client("cloudfront", region_name="us-west-1")

    config = scaffold.example_distribution_config("ref")
    config["IsIPV6Enabled"] = ipv6
    config["Aliases"] = {"Quantity": 2, "Items": aliases}
    config["DefaultCacheBehavior"]["ForwardedValues"]["Cookies"] = {
        "Forward": "whitelist",
        "WhitelistedNames": {"Quantity": 1, "Items": ["x-amz-header"]},
    }
    config["DefaultCacheBehavior"]["ForwardedValues"]["QueryString"] = qs
    config["DefaultCacheBehavior"]["Compress"] = compress
    config["DefaultCacheBehavior"]["MinTTL"] = 10
    config["DefaultCacheBehavior"]["SmoothStreaming"] = smooth
    config["PriceClass"] = "PriceClass_100"

    resp = client.create_distribution(DistributionConfig=config)
    config = resp["Distribution"]["DistributionConfig"]

    assert config["Aliases"] == {"Items": aliases, "Quantity": len(aliases)}

    assert config["PriceClass"] == "PriceClass_100"
    assert config["IsIPV6Enabled"] == ipv6

    assert config["DefaultCacheBehavior"]["Compress"] == compress
    assert config["DefaultCacheBehavior"]["MinTTL"] == 10
    assert config["DefaultCacheBehavior"]["SmoothStreaming"] == smooth

    forwarded = config["DefaultCacheBehavior"]["ForwardedValues"]
    assert forwarded["QueryString"] == qs
    assert forwarded["Cookies"]["Forward"] == "whitelist"
    assert forwarded["Cookies"]["WhitelistedNames"]["Items"] == ["x-amz-header"]


@mock_aws
def test_create_distribution_returns_etag():
    client = boto3.client("cloudfront", region_name="us-east-1")

    config = scaffold.example_distribution_config("ref")
    resp = client.create_distribution(DistributionConfig=config)
    dist_id = resp["Distribution"]["Id"]

    headers = resp["ResponseMetadata"]["HTTPHeaders"]
    assert len(headers["etag"]) == 13
    assert (
        headers["location"]
        == f"https://cloudfront.amazonaws.com/2020-05-31/distribution/{dist_id}"
    )


@mock_aws
def test_create_distribution_needs_unique_caller_reference():
    client = boto3.client("cloudfront", region_name="us-east-1")

    # Create standard distribution
    config = scaffold.example_distribution_config(ref="ref")
    dist1 = client.create_distribution(DistributionConfig=config)
    dist1_id = dist1["Distribution"]["Id"]

    # Try to create distribution with the same ref
    with pytest.raises(ClientError) as exc:
        client.create_distribution(DistributionConfig=config)
    err = exc.value.response["Error"]
    assert err["Code"] == "DistributionAlreadyExists"
    assert (
        err["Message"]
        == f"The caller reference that you are using to create a distribution is associated with another distribution. Already exists: {dist1_id}"
    )

    # Creating another distribution with a different reference
    config = scaffold.example_distribution_config(ref="ref2")
    dist2 = client.create_distribution(DistributionConfig=config)
    assert dist1_id != dist2["Distribution"]["Id"]

    resp = client.list_distributions()["DistributionList"]
    assert resp["Quantity"] == 2
    assert len(resp["Items"]) == 2


@mock_aws
def test_get_distribution_config_with_unknown_distribution_id():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        client.get_distribution_config(Id="unknown")

    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 404
    err = exc.value.response["Error"]
    assert err["Code"] == "NoSuchDistribution"
    assert err["Message"] == "The specified distribution does not exist."


@mock_aws
def test_get_distribution_config_with_mismatched_originid():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        client.create_distribution(
            DistributionConfig={
                "CallerReference": "ref",
                "Origins": {
                    "Quantity": 1,
                    "Items": [{"Id": "origin1", "DomainName": "https://getmoto.org"}],
                },
                "DefaultCacheBehavior": {
                    "TargetOriginId": "asdf",
                    "ViewerProtocolPolicy": "allow-all",
                },
                "Comment": "an optional comment that's not actually optional",
                "Enabled": False,
            }
        )
    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 404
    err = exc.value.response["Error"]
    assert err["Code"] == "NoSuchOrigin"
    assert (
        err["Message"] == "One or more of your origins or origin groups do not exist."
    )


@mock_aws
def test_create_origin_without_origin_config():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        client.create_distribution(
            DistributionConfig={
                "CallerReference": "ref",
                "Origins": {
                    "Quantity": 1,
                    "Items": [{"Id": "origin1", "DomainName": "https://getmoto.org"}],
                },
                "DefaultCacheBehavior": {
                    "TargetOriginId": "origin1",
                    "ViewerProtocolPolicy": "allow-all",
                },
                "Comment": "an optional comment that's not actually optional",
                "Enabled": False,
            }
        )

    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 400
    err = exc.value.response["Error"]
    assert err["Code"] == "InvalidOrigin"
    assert (
        err["Message"] == "The specified origin server does not exist or is not valid."
    )


@mock_aws
def test_create_distribution_with_invalid_s3_bucket():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        client.create_distribution(
            DistributionConfig={
                "CallerReference": "ref",
                "Origins": {
                    "Quantity": 1,
                    "Items": [
                        {
                            "Id": "origin1",
                            "DomainName": "https://getmoto.org",
                            "S3OriginConfig": {"OriginAccessIdentity": ""},
                        }
                    ],
                },
                "DefaultCacheBehavior": {
                    "TargetOriginId": "origin1",
                    "ViewerProtocolPolicy": "allow-all",
                },
                "Comment": "an optional comment that's not actually optional",
                "Enabled": False,
            }
        )

    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 400
    err = exc.value.response["Error"]
    assert err["Code"] == "InvalidArgument"
    assert (
        err["Message"]
        == "The parameter Origin DomainName does not refer to a valid S3 bucket."
    )


@mock_aws
@pytest.mark.parametrize("ssl_protocols", (["TLSv1"], ["TLSv1", "SSLv3"]))
def test_create_distribution_custom_config(ssl_protocols):
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.example_dist_custom_config("ref", ssl_protocols)

    dist = client.create_distribution(DistributionConfig=config)["Distribution"][
        "DistributionConfig"
    ]
    assert len(dist["Origins"]["Items"]) == 1
    custom_config = dist["Origins"]["Items"][0]["CustomOriginConfig"]

    assert custom_config["HTTPPort"] == 80
    assert custom_config["HTTPSPort"] == 443
    assert custom_config["OriginReadTimeout"] == 15
    assert custom_config["OriginKeepaliveTimeout"] == 10
    assert custom_config["OriginProtocolPolicy"] == "http-only"
    assert custom_config["OriginSslProtocols"] == {
        "Items": ssl_protocols,
        "Quantity": len(ssl_protocols),
    }


@mock_aws
def test_create_distribution_minimal_custom_config():
    client = boto3.client("cloudfront", region_name="us-west-1")
    config = scaffold.minimal_dist_custom_config("ref")

    dist = client.create_distribution(DistributionConfig=config)["Distribution"]
    dist_config = dist["DistributionConfig"]
    assert len(dist_config["Origins"]["Items"]) == 1
    custom_config = dist_config["Origins"]["Items"][0]["CustomOriginConfig"]

    assert custom_config["HTTPPort"] == 80
    assert custom_config["HTTPSPort"] == 443
    assert custom_config["OriginReadTimeout"] == 30
    assert custom_config["OriginKeepaliveTimeout"] == 5
    assert custom_config["OriginProtocolPolicy"] == "http-only"

    dist = client.get_distribution(Id=dist["Id"])["Distribution"]
    dist_config = dist["DistributionConfig"]
    get_custom_config = dist_config["Origins"]["Items"][0]["CustomOriginConfig"]
    assert custom_config == get_custom_config


@mock_aws
def test_list_distributions_without_any():
    client = boto3.client("cloudfront", region_name="us-east-1")

    dlist = client.list_distributions()["DistributionList"]
    assert dlist["Marker"] == ""
    assert dlist["MaxItems"] == 100
    assert dlist["IsTruncated"] is False
    assert dlist["Quantity"] == 0
    assert "Items" not in dlist


@mock_aws
def test_list_distributions():
    client = boto3.client("cloudfront", region_name="us-east-1")

    config = scaffold.example_distribution_config(ref="ref1")
    dist1 = client.create_distribution(DistributionConfig=config)["Distribution"]
    config = scaffold.example_distribution_config(ref="ref2")
    dist2 = client.create_distribution(DistributionConfig=config)["Distribution"]

    dlist = client.list_distributions()["DistributionList"]
    assert dlist["Quantity"] == 2
    assert len(dlist["Items"]) == 2

    item1 = dlist["Items"][0]
    assert item1["Id"] == dist1["Id"]
    assert "ARN" in item1
    assert item1["Status"] == "Deployed"

    item2 = dlist["Items"][1]
    assert item2["Id"] == dist2["Id"]
    assert "ARN" in item2
    assert item2["Status"] == "Deployed"


@mock_aws
def test_get_distribution():
    client = boto3.client("cloudfront", region_name="us-east-1")

    # Create standard distribution
    config = scaffold.example_distribution_config(ref="ref")
    dist = client.create_distribution(DistributionConfig=config)
    dist_id = dist["Distribution"]["Id"]

    resp = client.get_distribution(Id=dist_id)

    headers = resp["ResponseMetadata"]["HTTPHeaders"]
    assert len(headers["etag"]) == 13
    dist = resp["Distribution"]
    assert dist["Id"] == dist_id
    assert dist["Status"] == "Deployed"
    assert dist["DomainName"] == dist["DomainName"]

    config = dist["DistributionConfig"]
    assert config["CallerReference"] == "ref"


@mock_aws
def test_get_unknown_distribution():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        # Should have a second param, IfMatch, that contains the ETag of the most recent GetDistribution-request
        client.get_distribution(Id="unknown")

    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 404
    err = exc.value.response["Error"]
    assert err["Code"] == "NoSuchDistribution"
    assert err["Message"] == "The specified distribution does not exist."


@mock_aws
def test_delete_unknown_distribution():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        client.delete_distribution(Id="unknown", IfMatch="..")

    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 404
    err = exc.value.response["Error"]
    assert err["Code"] == "NoSuchDistribution"
    assert err["Message"] == "The specified distribution does not exist."


@mock_aws
def test_delete_distribution_without_ifmatch():
    client = boto3.client("cloudfront", region_name="us-west-1")

    with pytest.raises(ClientError) as exc:
        # Should have a second param, IfMatch, that contains the ETag of the most recent GetDistribution-request
        client.delete_distribution(Id="...")

    metadata = exc.value.response["ResponseMetadata"]
    assert metadata["HTTPStatusCode"] == 400
    err = exc.value.response["Error"]
    assert err["Code"] == "InvalidIfMatchVersion"
    assert (
        err["Message"]
        == "The If-Match version is missing or not valid for the resource."
    )


@mock_aws
def test_delete_distribution_random_etag():
    """
    Etag validation is not implemented yet
    Calling the delete-method with any etag will pass
    """
    client = boto3.client("cloudfront", region_name="us-east-1")

    # Create standard distribution
    config = scaffold.example_distribution_config(ref="ref")
    dist1 = client.create_distribution(DistributionConfig=config)
    dist_id = dist1["Distribution"]["Id"]

    client.delete_distribution(Id=dist_id, IfMatch="anything")

    with pytest.raises(ClientError) as exc:
        client.get_distribution(Id=dist_id)
    err = exc.value.response["Error"]
    assert err["Code"] == "NoSuchDistribution"


@mock_aws
def test_get_distribution_config():
    client = boto3.client("cloudfront", region_name="us-east-1")

    # Create standard distribution
    config = scaffold.example_distribution_config(ref="ref")
    dist = client.create_distribution(DistributionConfig=config)
    dist_id = dist["Distribution"]["Id"]

    config = client.get_distribution_config(Id=dist_id)["DistributionConfig"]
    assert config["CallerReference"] == "ref"

    assert config["Aliases"]["Quantity"] == 0

    origins = config["Origins"]
    assert origins["Quantity"] == 1
    assert len(origins["Items"]) == 1
    origin = origins["Items"][0]
    assert origin["Id"] == "origin1"
    assert origin["DomainName"] == "asdf.s3.us-east-1.amazonaws.com"
    assert origin["OriginPath"] == "/example"

    assert origin["CustomHeaders"]["Quantity"] == 0

    assert origin["ConnectionAttempts"] == 3
    assert origin["ConnectionTimeout"] == 10
    assert origin["OriginShield"] == {"Enabled": False}

    assert config["OriginGroups"] == {"Quantity": 0}

    default_cache = config["DefaultCacheBehavior"]
    assert default_cache["TargetOriginId"] == "origin1"

    signers = default_cache["TrustedSigners"]
    assert signers["Enabled"] is False
    assert signers["Quantity"] == 0

    groups = default_cache["TrustedKeyGroups"]
    assert groups["Enabled"] is False
    assert groups["Quantity"] == 0

    assert default_cache["ViewerProtocolPolicy"] == "allow-all"

    methods = default_cache["AllowedMethods"]
    assert methods["Quantity"] == 2
    assert set(methods["Items"]) == {"HEAD", "GET"}

    cached_methods = methods["CachedMethods"]
    assert cached_methods["Quantity"] == 2
    assert set(cached_methods["Items"]) == {"HEAD", "GET"}

    assert default_cache["SmoothStreaming"] is False
    assert default_cache["Compress"] is True
    assert default_cache["LambdaFunctionAssociations"] == {"Quantity": 0}
    assert default_cache["FunctionAssociations"] == {"Quantity": 0}
    assert default_cache["FieldLevelEncryptionId"] == ""
    assert "CachePolicyId" in default_cache

    assert config["CacheBehaviors"] == {"Quantity": 0}
    assert config["CustomErrorResponses"] == {"Quantity": 0}
    assert config["Comment"] == "an optional comment that's not actually optional"

    logging = config["Logging"]
    assert logging["Enabled"] is False
    assert logging["IncludeCookies"] is False
    assert logging["Bucket"] == ""
    assert logging["Prefix"] == ""

    assert config["PriceClass"] == "PriceClass_All"
    assert config["Enabled"] is False
    assert "WebACLId" in config
    assert config["HttpVersion"] == "http2"
    assert config["IsIPV6Enabled"] is True

    cert = config["ViewerCertificate"]
    assert cert["CloudFrontDefaultCertificate"] is True
    assert cert["MinimumProtocolVersion"] == "TLSv1"
    assert cert["CertificateSource"] == "cloudfront"

    assert config["Restrictions"]["GeoRestriction"] == {
        "RestrictionType": "none",
        "Quantity": 0,
    }

    assert config["WebACLId"] == ""
