import json
from datetime import datetime

import pytest
from freezegun import freeze_time
import os

import re
import sure  # noqa # pylint: disable=unused-import

import boto3
from botocore.exceptions import ClientError
from dateutil.tz import tzlocal

from moto import mock_ecr
from unittest import SkipTest

from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID

from tests.test_ecr.test_ecr_helpers import (
    _create_image_manifest,
    _create_image_manifest_list,
)


@mock_ecr
def test_create_repository():
    # given
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"

    # when
    response = client.create_repository(repositoryName=repo_name)

    # then
    repo = response["repository"]
    repo["repositoryName"].should.equal(repo_name)
    repo["repositoryArn"].should.equal(
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/{repo_name}"
    )
    repo["registryId"].should.equal(ACCOUNT_ID)
    repo["repositoryUri"].should.equal(
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/{repo_name}"
    )
    repo["createdAt"].should.be.a(datetime)
    repo["imageTagMutability"].should.equal("MUTABLE")
    repo["imageScanningConfiguration"].should.equal({"scanOnPush": False})
    repo["encryptionConfiguration"].should.equal({"encryptionType": "AES256"})


@mock_ecr
def test_create_repository_with_non_default_config():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    kms_key = f"arn:aws:kms:{region_name}:{ACCOUNT_ID}:key/51d81fab-b138-4bd2-8a09-07fd6d37224d"

    # when
    response = client.create_repository(
        repositoryName=repo_name,
        imageTagMutability="IMMUTABLE",
        imageScanningConfiguration={"scanOnPush": True},
        encryptionConfiguration={"encryptionType": "KMS", "kmsKey": kms_key},
        tags=[{"Key": "key-1", "Value": "value-1"}],
    )

    # then
    repo = response["repository"]
    repo["repositoryName"].should.equal(repo_name)
    repo["repositoryArn"].should.equal(
        f"arn:aws:ecr:{region_name}:{ACCOUNT_ID}:repository/{repo_name}"
    )
    repo["registryId"].should.equal(ACCOUNT_ID)
    repo["repositoryUri"].should.equal(
        f"{ACCOUNT_ID}.dkr.ecr.{region_name}.amazonaws.com/{repo_name}"
    )
    repo["createdAt"].should.be.a(datetime)
    repo["imageTagMutability"].should.equal("IMMUTABLE")
    repo["imageScanningConfiguration"].should.equal({"scanOnPush": True})
    repo["encryptionConfiguration"].should.equal(
        {"encryptionType": "KMS", "kmsKey": kms_key}
    )


@mock_ecr
def test_create_repository_in_different_account():
    # given
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"

    # when passing in a custom registry ID
    response = client.create_repository(
        registryId="222222222222", repositoryName=repo_name
    )

    # then we should persist this ID
    repo = response["repository"]
    repo.should.have.key("registryId").equals("222222222222")
    repo.should.have.key("repositoryArn").equals(
        "arn:aws:ecr:us-east-1:222222222222:repository/test-repo"
    )

    # then this repo should be returned with the correct ID
    repo = client.describe_repositories()["repositories"][0]
    repo.should.have.key("registryId").equals("222222222222")

    # then we can search for repos with this ID
    response = client.describe_repositories(registryId="222222222222")
    response.should.have.key("repositories").length_of(1)

    # then this repo is not found when searching for a different ID
    response = client.describe_repositories(registryId=ACCOUNT_ID)
    response.should.have.key("repositories").length_of(0)


@mock_ecr
def test_create_repository_with_aws_managed_kms():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"

    # when
    repo = client.create_repository(
        repositoryName=repo_name, encryptionConfiguration={"encryptionType": "KMS"}
    )["repository"]

    # then
    repo["repositoryName"].should.equal(repo_name)
    repo["encryptionConfiguration"]["encryptionType"].should.equal("KMS")
    repo["encryptionConfiguration"]["kmsKey"].should.match(
        r"arn:aws:kms:eu-central-1:[0-9]{12}:key/[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[ab89][a-f0-9]{3}-[a-f0-9]{12}$"
    )


@mock_ecr
def test_create_repository_error_already_exists():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    with pytest.raises(ClientError) as e:
        client.create_repository(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("CreateRepository")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryAlreadyExistsException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' already exists "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_describe_repositories():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository1")
    _ = client.create_repository(repositoryName="test_repository0")
    response = client.describe_repositories()
    len(response["repositories"]).should.equal(2)

    repository_arns = [
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/test_repository1",
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/test_repository0",
    ]
    sorted(
        [
            response["repositories"][0]["repositoryArn"],
            response["repositories"][1]["repositoryArn"],
        ]
    ).should.equal(sorted(repository_arns))

    repository_uris = [
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/test_repository1",
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/test_repository0",
    ]
    sorted(
        [
            response["repositories"][0]["repositoryUri"],
            response["repositories"][1]["repositoryUri"],
        ]
    ).should.equal(sorted(repository_uris))


@mock_ecr
def test_describe_repositories_1():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository1")
    _ = client.create_repository(repositoryName="test_repository0")
    response = client.describe_repositories(registryId=ACCOUNT_ID)
    len(response["repositories"]).should.equal(2)

    repository_arns = [
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/test_repository1",
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/test_repository0",
    ]
    sorted(
        [
            response["repositories"][0]["repositoryArn"],
            response["repositories"][1]["repositoryArn"],
        ]
    ).should.equal(sorted(repository_arns))

    repository_uris = [
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/test_repository1",
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/test_repository0",
    ]
    sorted(
        [
            response["repositories"][0]["repositoryUri"],
            response["repositories"][1]["repositoryUri"],
        ]
    ).should.equal(sorted(repository_uris))


@mock_ecr
def test_describe_repositories_2():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository1")
    _ = client.create_repository(repositoryName="test_repository0")
    response = client.describe_repositories(registryId="109876543210")
    len(response["repositories"]).should.equal(0)


@mock_ecr
def test_describe_repositories_3():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository1")
    _ = client.create_repository(repositoryName="test_repository0")
    response = client.describe_repositories(repositoryNames=["test_repository1"])
    len(response["repositories"]).should.equal(1)
    repository_arn = f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/test_repository1"
    response["repositories"][0]["repositoryArn"].should.equal(repository_arn)

    repository_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/test_repository1"
    response["repositories"][0]["repositoryUri"].should.equal(repository_uri)


@mock_ecr
def test_describe_repositories_with_image():
    # given
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    # when
    response = client.describe_repositories(repositoryNames=[repo_name])

    # then
    response["repositories"].should.have.length_of(1)

    repo = response["repositories"][0]
    repo["registryId"].should.equal(ACCOUNT_ID)
    repo["repositoryArn"].should.equal(
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/{repo_name}"
    )
    repo["repositoryName"].should.equal(repo_name)
    repo["repositoryUri"].should.equal(
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/{repo_name}"
    )
    repo["createdAt"].should.be.a(datetime)
    repo["imageScanningConfiguration"].should.equal({"scanOnPush": False})
    repo["imageTagMutability"].should.equal("MUTABLE")
    repo["encryptionConfiguration"].should.equal({"encryptionType": "AES256"})


@mock_ecr
def test_delete_repository():
    # given
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    response = client.delete_repository(repositoryName=repo_name)

    # then
    repo = response["repository"]
    repo["repositoryName"].should.equal(repo_name)
    repo["repositoryArn"].should.equal(
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/{repo_name}"
    )
    repo["registryId"].should.equal(ACCOUNT_ID)
    repo["repositoryUri"].should.equal(
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/{repo_name}"
    )
    repo["createdAt"].should.be.a(datetime)
    repo["imageTagMutability"].should.equal("MUTABLE")

    response = client.describe_repositories()
    response["repositories"].should.have.length_of(0)


@mock_ecr
def test_delete_repository_with_force():
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    # when
    # when
    response = client.delete_repository(repositoryName=repo_name, force=True)

    # then
    repo = response["repository"]
    repo["repositoryName"].should.equal(repo_name)
    repo["repositoryArn"].should.equal(
        f"arn:aws:ecr:us-east-1:{ACCOUNT_ID}:repository/{repo_name}"
    )
    repo["registryId"].should.equal(ACCOUNT_ID)
    repo["repositoryUri"].should.equal(
        f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/{repo_name}"
    )
    repo["createdAt"].should.be.a(datetime)
    repo["imageTagMutability"].should.equal("MUTABLE")

    response = client.describe_repositories()
    response["repositories"].should.have.length_of(0)


@mock_ecr
def test_put_image():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    response = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    response["image"]["imageId"]["imageTag"].should.equal("latest")
    response["image"]["imageId"]["imageDigest"].should.contain("sha")
    response["image"]["repositoryName"].should.equal("test_repository")
    response["image"]["registryId"].should.equal(ACCOUNT_ID)


@mock_ecr()
def test_put_manifest_list():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    manifest_list = _create_image_manifest_list()
    for image_manifest in manifest_list["image_manifests"]:
        _ = client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(image_manifest),
        )

    response = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(manifest_list["manifest_list"]),
        imageTag="multiArch",
    )

    response["image"]["imageId"]["imageTag"].should.equal("multiArch")
    response["image"]["imageId"]["imageDigest"].should.contain("sha")
    response["image"]["repositoryName"].should.equal("test_repository")
    response["image"]["registryId"].should.equal(ACCOUNT_ID)
    response["image"].should.have.key("imageManifest")
    image_manifest = json.loads(response["image"]["imageManifest"])
    image_manifest.should.have.key("mediaType")
    image_manifest.should.have.key("manifests")


@mock_ecr
def test_put_image_with_push_date():
    if os.environ.get("TEST_SERVER_MODE", "false").lower() == "true":
        raise SkipTest("Cant manipulate time in server mode")

    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    with freeze_time("2018-08-28 00:00:00"):
        image1_date = datetime.now(tzlocal())
        _ = client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(_create_image_manifest()),
            imageTag="first",
        )

    with freeze_time("2019-05-31 00:00:00"):
        image2_date = datetime.now(tzlocal())
        _ = client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(_create_image_manifest()),
            imageTag="second",
        )

    describe_response = client.describe_images(repositoryName="test_repository")

    type(describe_response["imageDetails"]).should.be(list)
    len(describe_response["imageDetails"]).should.be(2)

    set(
        [
            describe_response["imageDetails"][0]["imagePushedAt"],
            describe_response["imageDetails"][1]["imagePushedAt"],
        ]
    ).should.equal(set([image1_date, image2_date]))


@mock_ecr
def test_put_image_with_multiple_tags():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")
    manifest = _create_image_manifest()
    response = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(manifest),
        imageTag="v1",
    )

    response["image"]["imageId"]["imageTag"].should.equal("v1")
    response["image"]["imageId"]["imageDigest"].should.contain("sha")
    response["image"]["repositoryName"].should.equal("test_repository")
    response["image"]["registryId"].should.equal(ACCOUNT_ID)

    response1 = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(manifest),
        imageTag="latest",
    )

    response1["image"]["imageId"]["imageTag"].should.equal("latest")
    response1["image"]["imageId"]["imageDigest"].should.contain("sha")
    response1["image"]["repositoryName"].should.equal("test_repository")
    response1["image"]["registryId"].should.equal(ACCOUNT_ID)

    response2 = client.describe_images(repositoryName="test_repository")
    type(response2["imageDetails"]).should.be(list)
    len(response2["imageDetails"]).should.be(1)

    response2["imageDetails"][0]["imageDigest"].should.contain("sha")

    response2["imageDetails"][0]["registryId"].should.equal(ACCOUNT_ID)

    response2["imageDetails"][0]["repositoryName"].should.equal("test_repository")

    len(response2["imageDetails"][0]["imageTags"]).should.be(2)
    response2["imageDetails"][0]["imageTags"].should.be.equal(["v1", "latest"])


@mock_ecr
def test_put_multiple_images_with_same_tag():
    repo_name = "testrepo"
    image_tag = "my-tag"

    client = boto3.client("ecr", "us-east-1")
    client.create_repository(repositoryName=repo_name)

    image_1 = client.put_image(
        repositoryName=repo_name,
        imageTag=image_tag,
        imageManifest=json.dumps(_create_image_manifest()),
    )["image"]["imageId"]["imageDigest"]

    # We should overwrite the first image
    image_2 = client.put_image(
        repositoryName=repo_name,
        imageTag=image_tag,
        imageManifest=json.dumps(_create_image_manifest()),
    )["image"]["imageId"]["imageDigest"]

    assert image_1 != image_2

    images = client.describe_images(repositoryName=repo_name)["imageDetails"]

    images.should.have.length_of(1)
    images[0]["imageDigest"].should.equal(image_2)

    # Image with different tags are allowed
    image_3 = client.put_image(
        repositoryName=repo_name,
        imageTag="different-tag",
        imageManifest=json.dumps(_create_image_manifest()),
    )["image"]["imageId"]["imageDigest"]

    images = client.describe_images(repositoryName=repo_name)["imageDetails"]
    images.should.have.length_of(2)
    set([img["imageDigest"] for img in images]).should.equal({image_2, image_3})


@mock_ecr
def test_list_images():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository_1")

    _ = client.create_repository(repositoryName="test_repository_2")

    _ = client.put_image(
        repositoryName="test_repository_1",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    _ = client.put_image(
        repositoryName="test_repository_1",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v1",
    )

    _ = client.put_image(
        repositoryName="test_repository_1",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v2",
    )

    _ = client.put_image(
        repositoryName="test_repository_2",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="oldest",
    )

    response = client.list_images(repositoryName="test_repository_1")
    type(response["imageIds"]).should.be(list)
    len(response["imageIds"]).should.be(3)

    for image in response["imageIds"]:
        image["imageDigest"].should.contain("sha")

    image_tags = ["latest", "v1", "v2"]
    set(
        [
            response["imageIds"][0]["imageTag"],
            response["imageIds"][1]["imageTag"],
            response["imageIds"][2]["imageTag"],
        ]
    ).should.equal(set(image_tags))

    response = client.list_images(repositoryName="test_repository_2")
    type(response["imageIds"]).should.be(list)
    len(response["imageIds"]).should.be(1)
    response["imageIds"][0]["imageTag"].should.equal("oldest")
    response["imageIds"][0]["imageDigest"].should.contain("sha")


@mock_ecr
def test_list_images_from_repository_that_doesnt_exist():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository_1")

    # non existing repo
    error_msg = re.compile(
        r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*",
        re.MULTILINE,
    )
    client.list_images.when.called_with(
        repositoryName="repo-that-doesnt-exist", registryId="123"
    ).should.throw(Exception, error_msg)

    # repo does not exist in specified registry
    error_msg = re.compile(
        r".*The repository with name 'test_repository_1' does not exist in the registry with id '222'.*",
        re.MULTILINE,
    )
    client.list_images.when.called_with(
        repositoryName="test_repository_1", registryId="222"
    ).should.throw(Exception, error_msg)


@mock_ecr
def test_describe_images():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v1",
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v2",
    )

    manifest_list = _create_image_manifest_list()
    for image_manifest in manifest_list["image_manifests"]:
        _ = client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(image_manifest),
        )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(manifest_list["manifest_list"]),
        imageTag="multiArch",
    )

    response = client.describe_images(repositoryName="test_repository")
    type(response["imageDetails"]).should.be(list)
    len(response["imageDetails"]).should.be(7)

    response["imageDetails"][0]["imageManifestMediaType"].should.contain(
        "distribution.manifest.v2+json"
    )
    response["imageDetails"][1]["imageManifestMediaType"].should.contain(
        "distribution.manifest.v2+json"
    )
    response["imageDetails"][2]["imageManifestMediaType"].should.contain(
        "distribution.manifest.v2+json"
    )
    response["imageDetails"][3]["imageManifestMediaType"].should.contain(
        "distribution.manifest.v2+json"
    )
    response["imageDetails"][4]["imageManifestMediaType"].should.contain(
        "distribution.manifest.v2+json"
    )
    response["imageDetails"][5]["imageManifestMediaType"].should.contain(
        "distribution.manifest.v2+json"
    )
    response["imageDetails"][6]["imageManifestMediaType"].should.contain(
        "distribution.manifest.list.v2+json"
    )

    response["imageDetails"][0]["imageDigest"].should.contain("sha")
    response["imageDetails"][1]["imageDigest"].should.contain("sha")
    response["imageDetails"][2]["imageDigest"].should.contain("sha")
    response["imageDetails"][3]["imageDigest"].should.contain("sha")
    response["imageDetails"][4]["imageDigest"].should.contain("sha")
    response["imageDetails"][5]["imageDigest"].should.contain("sha")
    response["imageDetails"][6]["imageDigest"].should.contain("sha")

    response["imageDetails"][0]["registryId"].should.equal(ACCOUNT_ID)
    response["imageDetails"][1]["registryId"].should.equal(ACCOUNT_ID)
    response["imageDetails"][2]["registryId"].should.equal(ACCOUNT_ID)
    response["imageDetails"][3]["registryId"].should.equal(ACCOUNT_ID)
    response["imageDetails"][4]["registryId"].should.equal(ACCOUNT_ID)
    response["imageDetails"][5]["registryId"].should.equal(ACCOUNT_ID)
    response["imageDetails"][6]["registryId"].should.equal(ACCOUNT_ID)

    response["imageDetails"][0]["repositoryName"].should.equal("test_repository")
    response["imageDetails"][1]["repositoryName"].should.equal("test_repository")
    response["imageDetails"][2]["repositoryName"].should.equal("test_repository")
    response["imageDetails"][3]["repositoryName"].should.equal("test_repository")
    response["imageDetails"][4]["repositoryName"].should.equal("test_repository")
    response["imageDetails"][5]["repositoryName"].should.equal("test_repository")
    response["imageDetails"][6]["repositoryName"].should.equal("test_repository")

    response["imageDetails"][0].should_not.have.key("imageTags")
    response["imageDetails"][4].should_not.have.key("imageTags")
    response["imageDetails"][5].should_not.have.key("imageTags")

    len(response["imageDetails"][1]["imageTags"]).should.be(1)
    len(response["imageDetails"][2]["imageTags"]).should.be(1)
    len(response["imageDetails"][3]["imageTags"]).should.be(1)
    len(response["imageDetails"][6]["imageTags"]).should.be(1)

    image_tags = ["latest", "v1", "v2"]
    set(
        [
            response["imageDetails"][1]["imageTags"][0],
            response["imageDetails"][2]["imageTags"][0],
            response["imageDetails"][3]["imageTags"][0],
        ]
    ).should.equal(set(image_tags))

    response["imageDetails"][6].should_not.have.key("imageSizeInBytes")

    response["imageDetails"][0]["imageSizeInBytes"].should.be.greater_than(0)
    response["imageDetails"][1]["imageSizeInBytes"].should.be.greater_than(0)
    response["imageDetails"][2]["imageSizeInBytes"].should.be.greater_than(0)
    response["imageDetails"][3]["imageSizeInBytes"].should.be.greater_than(0)
    response["imageDetails"][4]["imageSizeInBytes"].should.be.greater_than(0)
    response["imageDetails"][5]["imageSizeInBytes"].should.be.greater_than(0)


@mock_ecr
def test_describe_images_by_tag():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    tag_map = {}
    for tag in ["latest", "v1", "v2"]:
        put_response = client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(_create_image_manifest()),
            imageTag=tag,
        )
        tag_map[tag] = put_response["image"]

    for tag, put_response in tag_map.items():
        response = client.describe_images(
            repositoryName="test_repository", imageIds=[{"imageTag": tag}]
        )
        len(response["imageDetails"]).should.be(1)
        image_detail = response["imageDetails"][0]
        image_detail["registryId"].should.equal(ACCOUNT_ID)
        image_detail["repositoryName"].should.equal("test_repository")
        image_detail["imageTags"].should.equal([put_response["imageId"]["imageTag"]])
        image_detail["imageDigest"].should.equal(put_response["imageId"]["imageDigest"])


@mock_ecr
def test_describe_images_tags_should_not_contain_empty_tag1():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()
    client.put_image(
        repositoryName="test_repository", imageManifest=json.dumps(manifest)
    )

    tags = ["v1", "v2", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    response = client.describe_images(
        repositoryName="test_repository", imageIds=[{"imageTag": tag}]
    )
    len(response["imageDetails"]).should.be(1)
    image_detail = response["imageDetails"][0]
    len(image_detail["imageTags"]).should.equal(3)
    image_detail["imageTags"].should.be.equal(tags)


@mock_ecr
def test_describe_images_tags_should_not_contain_empty_tag2():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()
    tags = ["v1", "v2"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    client.put_image(
        repositoryName="test_repository", imageManifest=json.dumps(manifest)
    )

    client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(manifest),
        imageTag="latest",
    )

    response = client.describe_images(
        repositoryName="test_repository", imageIds=[{"imageTag": tag}]
    )
    len(response["imageDetails"]).should.be(1)
    image_detail = response["imageDetails"][0]
    len(image_detail["imageTags"]).should.equal(3)
    image_detail["imageTags"].should.be.equal(["v1", "v2", "latest"])


@mock_ecr
def test_describe_repository_that_doesnt_exist():
    client = boto3.client("ecr", region_name="us-east-1")

    error_msg = re.compile(
        r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*",
        re.MULTILINE,
    )
    client.describe_repositories.when.called_with(
        repositoryNames=["repo-that-doesnt-exist"], registryId="123"
    ).should.throw(ClientError, error_msg)


@mock_ecr
def test_describe_image_that_doesnt_exist():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    error_msg1 = re.compile(
        r".*The image with imageId {imageDigest:'null', imageTag:'testtag'} does not exist within "
        r"the repository with name 'test_repository' in the registry with id '123456789012'.*",
        re.MULTILINE,
    )

    client.describe_images.when.called_with(
        repositoryName="test_repository",
        imageIds=[{"imageTag": "testtag"}],
        registryId=ACCOUNT_ID,
    ).should.throw(client.exceptions.ImageNotFoundException, error_msg1)

    error_msg2 = re.compile(
        r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123456789012'.*",
        re.MULTILINE,
    )
    client.describe_images.when.called_with(
        repositoryName="repo-that-doesnt-exist",
        imageIds=[{"imageTag": "testtag"}],
        registryId=ACCOUNT_ID,
    ).should.throw(ClientError, error_msg2)


@mock_ecr
def test_delete_repository_that_doesnt_exist():
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "repo-that-doesnt-exist"

    # when
    with pytest.raises(ClientError) as e:
        client.delete_repository(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteRepository")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_delete_repository_error_not_empty():
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    # when
    with pytest.raises(ClientError) as e:
        client.delete_repository(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteRepository")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotEmptyException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' "
        f"in registry with id '{ACCOUNT_ID}' "
        "cannot be deleted because it still contains images"
    )


@mock_ecr
def test_describe_images_by_digest():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    tags = ["latest", "v1", "v2"]
    digest_map = {}
    for tag in tags:
        put_response = client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(_create_image_manifest()),
            imageTag=tag,
        )
        digest_map[put_response["image"]["imageId"]["imageDigest"]] = put_response[
            "image"
        ]

    for digest, put_response in digest_map.items():
        response = client.describe_images(
            repositoryName="test_repository", imageIds=[{"imageDigest": digest}]
        )
        len(response["imageDetails"]).should.be(1)
        image_detail = response["imageDetails"][0]
        image_detail["registryId"].should.equal(ACCOUNT_ID)
        image_detail["repositoryName"].should.equal("test_repository")
        image_detail["imageTags"].should.equal([put_response["imageId"]["imageTag"]])
        image_detail["imageDigest"].should.equal(digest)


@mock_ecr
def test_get_authorization_token_assume_region():
    client = boto3.client("ecr", region_name="us-east-1")
    auth_token_response = client.get_authorization_token()

    auth_token_response.should.contain("authorizationData")
    auth_token_response.should.contain("ResponseMetadata")
    auth_token_response["authorizationData"].should.equal(
        [
            {
                "authorizationToken": "QVdTOjEyMzQ1Njc4OTAxMi1hdXRoLXRva2Vu",
                "proxyEndpoint": f"https://{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com",
                "expiresAt": datetime(2015, 1, 1, tzinfo=tzlocal()),
            }
        ]
    )


@mock_ecr
def test_get_authorization_token_explicit_regions():
    client = boto3.client("ecr", region_name="us-east-1")
    auth_token_response = client.get_authorization_token(
        registryIds=["10987654321", "878787878787"]
    )

    auth_token_response.should.contain("authorizationData")
    auth_token_response.should.contain("ResponseMetadata")
    auth_token_response["authorizationData"].should.equal(
        [
            {
                "authorizationToken": "QVdTOjEwOTg3NjU0MzIxLWF1dGgtdG9rZW4=",
                "proxyEndpoint": "https://10987654321.dkr.ecr.us-east-1.amazonaws.com",
                "expiresAt": datetime(2015, 1, 1, tzinfo=tzlocal()),
            },
            {
                "authorizationToken": "QVdTOjg3ODc4Nzg3ODc4Ny1hdXRoLXRva2Vu",
                "proxyEndpoint": "https://878787878787.dkr.ecr.us-east-1.amazonaws.com",
                "expiresAt": datetime(2015, 1, 1, tzinfo=tzlocal()),
            },
        ]
    )


@mock_ecr
def test_batch_get_image():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v1",
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v2",
    )

    response = client.batch_get_image(
        repositoryName="test_repository", imageIds=[{"imageTag": "v2"}]
    )

    type(response["images"]).should.be(list)
    len(response["images"]).should.be(1)

    response["images"][0]["imageManifest"].should.contain(
        "vnd.docker.distribution.manifest.v2+json"
    )
    response["images"][0]["registryId"].should.equal(ACCOUNT_ID)
    response["images"][0]["repositoryName"].should.equal("test_repository")

    response["images"][0]["imageId"]["imageTag"].should.equal("v2")
    response["images"][0]["imageId"]["imageDigest"].should.contain("sha")

    type(response["failures"]).should.be(list)
    len(response["failures"]).should.be(0)


@mock_ecr
def test_batch_get_image_that_doesnt_exist():
    client = boto3.client("ecr", region_name="us-east-1")
    _ = client.create_repository(repositoryName="test_repository")

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v1",
    )

    _ = client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v2",
    )

    response = client.batch_get_image(
        repositoryName="test_repository", imageIds=[{"imageTag": "v5"}]
    )

    type(response["images"]).should.be(list)
    len(response["images"]).should.be(0)

    type(response["failures"]).should.be(list)
    len(response["failures"]).should.be(1)
    response["failures"][0]["failureReason"].should.equal("Requested image not found")
    response["failures"][0]["failureCode"].should.equal("ImageNotFound")
    response["failures"][0]["imageId"]["imageTag"].should.equal("v5")


@mock_ecr
def test_batch_delete_image_by_tag():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()

    tags = ["v1", "v1.0", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    describe_response1 = client.describe_images(repositoryName="test_repository")

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageTag": "latest"}],
    )

    describe_response2 = client.describe_images(repositoryName="test_repository")

    type(describe_response1["imageDetails"][0]["imageTags"]).should.be(list)
    len(describe_response1["imageDetails"][0]["imageTags"]).should.be(3)

    type(describe_response2["imageDetails"][0]["imageTags"]).should.be(list)
    len(describe_response2["imageDetails"][0]["imageTags"]).should.be(2)

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(1)

    batch_delete_response["imageIds"][0]["imageTag"].should.equal("latest")

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(0)


@mock_ecr
def test_batch_delete_image_delete_last_tag():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    client.put_image(
        repositoryName="test_repository",
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="v1",
    )

    describe_response1 = client.describe_images(repositoryName="test_repository")

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageTag": "v1"}],
    )

    describe_response2 = client.describe_images(repositoryName="test_repository")

    type(describe_response1["imageDetails"][0]["imageTags"]).should.be(list)
    len(describe_response1["imageDetails"][0]["imageTags"]).should.be(1)

    type(describe_response2["imageDetails"]).should.be(list)
    len(describe_response2["imageDetails"]).should.be(0)

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(1)

    batch_delete_response["imageIds"][0]["imageTag"].should.equal("v1")

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(0)


@mock_ecr
def test_batch_delete_image_with_nonexistent_tag():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()

    tags = ["v1", "v1.0", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    describe_response = client.describe_images(repositoryName="test_repository")

    missing_tag = "missing-tag"
    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageTag": missing_tag}],
    )

    type(describe_response["imageDetails"][0]["imageTags"]).should.be(list)
    len(describe_response["imageDetails"][0]["imageTags"]).should.be(3)

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(0)

    batch_delete_response["failures"][0]["imageId"]["imageTag"].should.equal(
        missing_tag
    )
    batch_delete_response["failures"][0]["failureCode"].should.equal("ImageNotFound")
    batch_delete_response["failures"][0]["failureReason"].should.equal(
        "Requested image not found"
    )

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(1)


@mock_ecr
def test_batch_delete_image_by_digest():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()

    tags = ["v1", "v2", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    describe_response = client.describe_images(repositoryName="test_repository")
    image_digest = describe_response["imageDetails"][0]["imageDigest"]

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageDigest": image_digest}],
    )

    describe_response = client.describe_images(repositoryName="test_repository")

    type(describe_response["imageDetails"]).should.be(list)
    len(describe_response["imageDetails"]).should.be(0)

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(3)

    batch_delete_response["imageIds"][0]["imageDigest"].should.equal(image_digest)
    batch_delete_response["imageIds"][1]["imageDigest"].should.equal(image_digest)
    batch_delete_response["imageIds"][2]["imageDigest"].should.equal(image_digest)

    set(
        [
            batch_delete_response["imageIds"][0]["imageTag"],
            batch_delete_response["imageIds"][1]["imageTag"],
            batch_delete_response["imageIds"][2]["imageTag"],
        ]
    ).should.equal(set(tags))

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(0)


@mock_ecr
def test_batch_delete_image_with_invalid_digest():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()

    tags = ["v1", "v2", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    invalid_image_digest = "sha256:invalid-digest"

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageDigest": invalid_image_digest}],
    )

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(0)

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(1)

    batch_delete_response["failures"][0]["imageId"]["imageDigest"].should.equal(
        invalid_image_digest
    )
    batch_delete_response["failures"][0]["failureCode"].should.equal(
        "InvalidImageDigest"
    )
    batch_delete_response["failures"][0]["failureReason"].should.equal(
        "Invalid request parameters: image digest should satisfy the regex '[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+'"
    )


@mock_ecr
def test_batch_delete_image_with_missing_parameters():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910", repositoryName="test_repository", imageIds=[{}]
    )

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(0)

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(1)

    batch_delete_response["failures"][0]["failureCode"].should.equal(
        "MissingDigestAndTag"
    )
    batch_delete_response["failures"][0]["failureReason"].should.equal(
        "Invalid request parameters: both tag and digest cannot be null"
    )


@mock_ecr
def test_batch_delete_image_with_matching_digest_and_tag():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()

    tags = ["v1", "v1.0", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    describe_response = client.describe_images(repositoryName="test_repository")
    image_digest = describe_response["imageDetails"][0]["imageDigest"]

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageDigest": image_digest, "imageTag": "v1"}],
    )

    describe_response = client.describe_images(repositoryName="test_repository")

    type(describe_response["imageDetails"]).should.be(list)
    len(describe_response["imageDetails"]).should.be(0)

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(3)

    batch_delete_response["imageIds"][0]["imageDigest"].should.equal(image_digest)
    batch_delete_response["imageIds"][1]["imageDigest"].should.equal(image_digest)
    batch_delete_response["imageIds"][2]["imageDigest"].should.equal(image_digest)

    set(
        [
            batch_delete_response["imageIds"][0]["imageTag"],
            batch_delete_response["imageIds"][1]["imageTag"],
            batch_delete_response["imageIds"][2]["imageTag"],
        ]
    ).should.equal(set(tags))

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(0)


@mock_ecr
def test_batch_delete_image_with_mismatched_digest_and_tag():
    client = boto3.client("ecr", region_name="us-east-1")
    client.create_repository(repositoryName="test_repository")

    manifest = _create_image_manifest()

    tags = ["v1", "latest"]
    for tag in tags:
        client.put_image(
            repositoryName="test_repository",
            imageManifest=json.dumps(manifest),
            imageTag=tag,
        )

    describe_response = client.describe_images(repositoryName="test_repository")
    image_digest = describe_response["imageDetails"][0]["imageDigest"]

    batch_delete_response = client.batch_delete_image(
        registryId="012345678910",
        repositoryName="test_repository",
        imageIds=[{"imageDigest": image_digest, "imageTag": "v2"}],
    )

    type(batch_delete_response["imageIds"]).should.be(list)
    len(batch_delete_response["imageIds"]).should.be(0)

    type(batch_delete_response["failures"]).should.be(list)
    len(batch_delete_response["failures"]).should.be(1)

    batch_delete_response["failures"][0]["imageId"]["imageDigest"].should.equal(
        image_digest
    )
    batch_delete_response["failures"][0]["imageId"]["imageTag"].should.equal("v2")
    batch_delete_response["failures"][0]["failureCode"].should.equal("ImageNotFound")
    batch_delete_response["failures"][0]["failureReason"].should.equal(
        "Requested image not found"
    )


@mock_ecr
def test_delete_batch_image_with_multiple_images():
    client = boto3.client("ecr", region_name="us-east-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # Populate mock repo with images
    for i in range(10):
        client.put_image(
            repositoryName=repo_name,
            imageManifest=json.dumps(_create_image_manifest()),
            imageTag=f"tag{i}",
        )

    # Pull down image digests for each image in the mock repo
    repo_images = client.describe_images(repositoryName=repo_name)["imageDetails"]
    image_digests = [{"imageDigest": image["imageDigest"]} for image in repo_images]

    # Pick a couple of images to delete
    images_to_delete = image_digests[5:7]

    # Delete the images
    response = client.batch_delete_image(
        repositoryName=repo_name, imageIds=images_to_delete
    )
    response["imageIds"].should.have.length_of(2)
    response["failures"].should.equal([])

    # Verify other images still exist
    repo_images = client.describe_images(repositoryName=repo_name)["imageDetails"]
    image_tags = [img["imageTags"][0] for img in repo_images]
    image_tags.should.equal(
        ["tag0", "tag1", "tag2", "tag3", "tag4", "tag7", "tag8", "tag9"]
    )


@mock_ecr
def test_list_tags_for_resource():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    arn = client.create_repository(
        repositoryName=repo_name, tags=[{"Key": "key-1", "Value": "value-1"}]
    )["repository"]["repositoryArn"]

    # when
    tags = client.list_tags_for_resource(resourceArn=arn)["tags"]

    # then
    tags.should.equal([{"Key": "key-1", "Value": "value-1"}])


@mock_ecr
def test_list_tags_for_resource_error_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.list_tags_for_resource(
            resourceArn=f"arn:aws:ecr:{region_name}:{ACCOUNT_ID}:repository/{repo_name}"
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("ListTagsForResource")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_list_tags_for_resource_error_invalid_param():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)

    # when
    with pytest.raises(ClientError) as e:
        client.list_tags_for_resource(resourceArn="invalid")

    # then
    ex = e.value
    ex.operation_name.should.equal("ListTagsForResource")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("InvalidParameterException")
    ex.response["Error"]["Message"].should.equal(
        "Invalid parameter at 'resourceArn' failed to satisfy constraint: "
        "'Invalid ARN'"
    )


@mock_ecr
def test_tag_resource():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    arn = client.create_repository(
        repositoryName=repo_name, tags=[{"Key": "key-1", "Value": "value-1"}]
    )["repository"]["repositoryArn"]

    # when
    client.tag_resource(resourceArn=arn, tags=[{"Key": "key-2", "Value": "value-2"}])

    # then
    tags = client.list_tags_for_resource(resourceArn=arn)["tags"]
    sorted(tags, key=lambda i: i["Key"]).should.equal(
        sorted(
            [
                {"Key": "key-1", "Value": "value-1"},
                {"Key": "key-2", "Value": "value-2"},
            ],
            key=lambda i: i["Key"],
        )
    )


@mock_ecr
def test_tag_resource_error_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.tag_resource(
            resourceArn=f"arn:aws:ecr:{region_name}:{ACCOUNT_ID}:repository/{repo_name}",
            tags=[{"Key": "key-1", "Value": "value-2"}],
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("TagResource")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_untag_resource():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    arn = client.create_repository(
        repositoryName=repo_name,
        tags=[
            {"Key": "key-1", "Value": "value-1"},
            {"Key": "key-2", "Value": "value-2"},
        ],
    )["repository"]["repositoryArn"]

    # when
    client.untag_resource(resourceArn=arn, tagKeys=["key-1"])

    # then
    tags = client.list_tags_for_resource(resourceArn=arn)["tags"]
    tags.should.equal([{"Key": "key-2", "Value": "value-2"}])


@mock_ecr
def test_untag_resource_error_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.untag_resource(
            resourceArn=f"arn:aws:ecr:{region_name}:{ACCOUNT_ID}:repository/{repo_name}",
            tagKeys=["key-1"],
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("UntagResource")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_put_image_tag_mutability():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    response = client.describe_repositories(repositoryNames=[repo_name])
    response["repositories"][0]["imageTagMutability"].should.equal("MUTABLE")

    # when
    response = client.put_image_tag_mutability(
        repositoryName=repo_name, imageTagMutability="IMMUTABLE"
    )

    # then
    response["imageTagMutability"].should.equal("IMMUTABLE")
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)

    response = client.describe_repositories(repositoryNames=[repo_name])
    response["repositories"][0]["imageTagMutability"].should.equal("IMMUTABLE")


@mock_ecr
def test_put_image_tag_mutability_error_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.put_image_tag_mutability(
            repositoryName=repo_name, imageTagMutability="IMMUTABLE"
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("PutImageTagMutability")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_put_image_tag_mutability_error_invalid_param():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    with pytest.raises(ClientError) as e:
        client.put_image_tag_mutability(
            repositoryName=repo_name, imageTagMutability="invalid"
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("PutImageTagMutability")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("InvalidParameterException")
    ex.response["Error"]["Message"].should.equal(
        "Invalid parameter at 'imageTagMutability' failed to satisfy constraint: "
        "'Member must satisfy enum value set: [IMMUTABLE, MUTABLE]'"
    )


@mock_ecr
def test_put_image_scanning_configuration():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    response = client.describe_repositories(repositoryNames=[repo_name])
    response["repositories"][0]["imageScanningConfiguration"].should.equal(
        {"scanOnPush": False}
    )

    # when
    response = client.put_image_scanning_configuration(
        repositoryName=repo_name, imageScanningConfiguration={"scanOnPush": True}
    )

    # then
    response["imageScanningConfiguration"].should.equal({"scanOnPush": True})
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)

    response = client.describe_repositories(repositoryNames=[repo_name])
    response["repositories"][0]["imageScanningConfiguration"].should.equal(
        {"scanOnPush": True}
    )


@mock_ecr
def test_put_image_scanning_configuration_error_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.put_image_scanning_configuration(
            repositoryName=repo_name, imageScanningConfiguration={"scanOnPush": True}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("PutImageScanningConfiguration")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_set_repository_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "root",
                "Effect": "Allow",
                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
                "Action": ["ecr:DescribeImages"],
            }
        ],
    }

    # when
    response = client.set_repository_policy(
        repositoryName=repo_name, policyText=json.dumps(policy)
    )

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    json.loads(response["policyText"]).should.equal(policy)


@mock_ecr
def test_set_repository_policy_error_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "root",
                "Effect": "Allow",
                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
                "Action": ["ecr:DescribeImages"],
            }
        ],
    }

    # when
    with pytest.raises(ClientError) as e:
        client.set_repository_policy(
            repositoryName=repo_name, policyText=json.dumps(policy)
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("SetRepositoryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_set_repository_policy_error_invalid_param():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "Version": "2012-10-17",
        "Statement": [{"Effect": "Allow"}],
    }

    # when
    with pytest.raises(ClientError) as e:
        client.set_repository_policy(
            repositoryName=repo_name, policyText=json.dumps(policy)
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("SetRepositoryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("InvalidParameterException")
    ex.response["Error"]["Message"].should.equal(
        "Invalid parameter at 'PolicyText' failed to satisfy constraint: "
        "'Invalid repository policy provided'"
    )


@mock_ecr
def test_get_repository_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "root",
                "Effect": "Allow",
                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
                "Action": ["ecr:DescribeImages"],
            }
        ],
    }
    client.set_repository_policy(
        repositoryName=repo_name, policyText=json.dumps(policy)
    )

    # when
    response = client.get_repository_policy(repositoryName=repo_name)

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    json.loads(response["policyText"]).should.equal(policy)


@mock_ecr
def test_get_repository_policy_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.get_repository_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("GetRepositoryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_get_repository_policy_error_policy_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    with pytest.raises(ClientError) as e:
        client.get_repository_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("GetRepositoryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryPolicyNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        "Repository policy does not exist "
        f"for the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_delete_repository_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "root",
                "Effect": "Allow",
                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
                "Action": ["ecr:DescribeImages"],
            }
        ],
    }
    client.set_repository_policy(
        repositoryName=repo_name, policyText=json.dumps(policy)
    )

    # when
    response = client.delete_repository_policy(repositoryName=repo_name)

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    json.loads(response["policyText"]).should.equal(policy)

    with pytest.raises(ClientError) as e:
        client.get_repository_policy(repositoryName=repo_name)

    e.value.response["Error"]["Code"].should.contain(
        "RepositoryPolicyNotFoundException"
    )


@mock_ecr
def test_delete_repository_policy_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.delete_repository_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteRepositoryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_delete_repository_policy_error_policy_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    with pytest.raises(ClientError) as e:
        client.delete_repository_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteRepositoryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryPolicyNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        "Repository policy does not exist "
        f"for the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_put_lifecycle_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "rules": [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            }
        ]
    }

    # when
    response = client.put_lifecycle_policy(
        repositoryName=repo_name, lifecyclePolicyText=json.dumps(policy)
    )

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    json.loads(response["lifecyclePolicyText"]).should.equal(policy)


@mock_ecr
def test_put_lifecycle_policy_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"
    policy = {
        "rules": [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            }
        ]
    }

    # when
    with pytest.raises(ClientError) as e:
        client.put_lifecycle_policy(
            repositoryName=repo_name, lifecyclePolicyText=json.dumps(policy)
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("PutLifecyclePolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_get_lifecycle_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "rules": [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            }
        ]
    }
    client.put_lifecycle_policy(
        repositoryName=repo_name, lifecyclePolicyText=json.dumps(policy)
    )

    # when
    response = client.get_lifecycle_policy(repositoryName=repo_name)

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    json.loads(response["lifecyclePolicyText"]).should.equal(policy)
    response["lastEvaluatedAt"].should.be.a(datetime)


@mock_ecr
def test_get_lifecycle_policy_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.get_lifecycle_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("GetLifecyclePolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_get_lifecycle_policy_error_policy_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    with pytest.raises(ClientError) as e:
        client.get_lifecycle_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("GetLifecyclePolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("LifecyclePolicyNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        "Lifecycle policy does not exist "
        f"for the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_delete_lifecycle_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    policy = {
        "rules": [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            }
        ]
    }
    client.put_lifecycle_policy(
        repositoryName=repo_name, lifecyclePolicyText=json.dumps(policy)
    )

    # when
    response = client.delete_lifecycle_policy(repositoryName=repo_name)

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    json.loads(response["lifecyclePolicyText"]).should.equal(policy)
    response["lastEvaluatedAt"].should.be.a(datetime)

    with pytest.raises(ClientError) as e:
        client.get_lifecycle_policy(repositoryName=repo_name)

    e.value.response["Error"]["Code"].should.contain("LifecyclePolicyNotFoundException")


@mock_ecr
def test_delete_lifecycle_policy_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.delete_lifecycle_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteLifecyclePolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_delete_lifecycle_policy_error_policy_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)

    # when
    with pytest.raises(ClientError) as e:
        client.delete_lifecycle_policy(repositoryName=repo_name)

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteLifecyclePolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("LifecyclePolicyNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        "Lifecycle policy does not exist "
        f"for the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
@pytest.mark.parametrize(
    "actions",
    ["ecr:CreateRepository", ["ecr:CreateRepository", "ecr:ReplicateImage"]],
    ids=["single-action", "multiple-actions"],
)
def test_put_registry_policy(actions):
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "AWS": ["arn:aws:iam::111111111111:root", "222222222222"]
                },
                "Action": actions,
                "Resource": "*",
            }
        ],
    }

    # when
    response = client.put_registry_policy(policyText=json.dumps(policy))

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    json.loads(response["policyText"]).should.equal(policy)


@mock_ecr
def test_put_registry_policy_error_invalid_action():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {"AWS": "arn:aws:iam::111111111111:root"},
                "Action": [
                    "ecr:CreateRepository",
                    "ecr:ReplicateImage",
                    "ecr:DescribeRepositories",
                ],
                "Resource": "*",
            }
        ],
    }

    # when
    with pytest.raises(ClientError) as e:
        client.put_registry_policy(policyText=json.dumps(policy))

    # then
    ex = e.value
    ex.operation_name.should.equal("PutRegistryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("InvalidParameterException")
    ex.response["Error"]["Message"].should.equal(
        "Invalid parameter at 'PolicyText' failed to satisfy constraint: "
        "'Invalid registry policy provided'"
    )


@mock_ecr
def test_get_registry_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "AWS": ["arn:aws:iam::111111111111:root", "222222222222"]
                },
                "Action": ["ecr:CreateRepository", "ecr:ReplicateImage"],
                "Resource": "*",
            }
        ],
    }
    client.put_registry_policy(policyText=json.dumps(policy))

    # when
    response = client.get_registry_policy()

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    json.loads(response["policyText"]).should.equal(policy)


@mock_ecr
def test_get_registry_policy_error_policy_not_exists():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")

    # when
    with pytest.raises(ClientError) as e:
        client.get_registry_policy()

    # then
    ex = e.value
    ex.operation_name.should.equal("GetRegistryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RegistryPolicyNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"Registry policy does not exist in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_delete_registry_policy():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "AWS": ["arn:aws:iam::111111111111:root", "222222222222"]
                },
                "Action": ["ecr:CreateRepository", "ecr:ReplicateImage"],
                "Resource": "*",
            }
        ],
    }
    client.put_registry_policy(policyText=json.dumps(policy))

    # when
    response = client.delete_registry_policy()

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    json.loads(response["policyText"]).should.equal(policy)

    with pytest.raises(ClientError) as e:
        client.get_registry_policy()

    e.value.response["Error"]["Code"].should.contain("RegistryPolicyNotFoundException")


@mock_ecr
def test_delete_registry_policy_error_policy_not_exists():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")

    # when
    with pytest.raises(ClientError) as e:
        client.delete_registry_policy()

    # then
    ex = e.value
    ex.operation_name.should.equal("DeleteRegistryPolicy")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RegistryPolicyNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"Registry policy does not exist in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_start_image_scan():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_tag = "latest"
    image_digest = client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )["image"]["imageId"]["imageDigest"]

    # when
    response = client.start_image_scan(
        repositoryName=repo_name, imageId={"imageTag": image_tag}
    )

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    response["imageId"].should.equal(
        {"imageDigest": image_digest, "imageTag": image_tag}
    )
    response["imageScanStatus"].should.equal({"status": "IN_PROGRESS"})


@mock_ecr
def test_start_image_scan_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.start_image_scan(
            repositoryName=repo_name, imageId={"imageTag": "latest"}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("StartImageScan")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_start_image_scan_error_image_not_exists():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_tag = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.start_image_scan(
            repositoryName=repo_name, imageId={"imageTag": image_tag}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("StartImageScan")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("ImageNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The image with imageId {{imageDigest:'null', imageTag:'{image_tag}'}} does not exist "
        f"within the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_start_image_scan_error_image_tag_digest_mismatch():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_digest = client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )["image"]["imageId"]["imageDigest"]
    image_tag = "not-latest"

    # when
    with pytest.raises(ClientError) as e:
        client.start_image_scan(
            repositoryName=repo_name,
            imageId={"imageTag": image_tag, "imageDigest": image_digest},
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("StartImageScan")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("ImageNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The image with imageId {{imageDigest:'{image_digest}', imageTag:'{image_tag}'}} does not exist "
        f"within the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_start_image_scan_error_daily_limit():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_tag = "latest"
    client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )
    client.start_image_scan(repositoryName=repo_name, imageId={"imageTag": image_tag})

    # when
    with pytest.raises(ClientError) as e:
        client.start_image_scan(
            repositoryName=repo_name, imageId={"imageTag": image_tag}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("StartImageScan")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("LimitExceededException")
    ex.response["Error"]["Message"].should.equal(
        "The scan quota per image has been exceeded. Wait and try again."
    )


@mock_ecr
def test_describe_image_scan_findings():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_tag = "latest"
    image_digest = client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag="latest",
    )["image"]["imageId"]["imageDigest"]
    client.start_image_scan(repositoryName=repo_name, imageId={"imageTag": image_tag})

    # when
    response = client.describe_image_scan_findings(
        repositoryName=repo_name, imageId={"imageTag": image_tag}
    )

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["repositoryName"].should.equal(repo_name)
    response["imageId"].should.equal(
        {"imageDigest": image_digest, "imageTag": image_tag}
    )
    response["imageScanStatus"].should.equal(
        {"status": "COMPLETE", "description": "The scan was completed successfully."}
    )
    scan_findings = response["imageScanFindings"]
    scan_findings["imageScanCompletedAt"].should.be.a(datetime)
    scan_findings["vulnerabilitySourceUpdatedAt"].should.be.a(datetime)
    scan_findings["findings"].should.equal(
        [
            {
                "name": "CVE-9999-9999",
                "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-9999-9999",
                "severity": "HIGH",
                "attributes": [
                    {"key": "package_version", "value": "9.9.9"},
                    {"key": "package_name", "value": "moto_fake"},
                    {"key": "CVSS2_VECTOR", "value": "AV:N/AC:L/Au:N/C:P/I:P/A:P"},
                    {"key": "CVSS2_SCORE", "value": "7.5"},
                ],
            }
        ]
    )
    scan_findings["findingSeverityCounts"].should.equal({"HIGH": 1})


@mock_ecr
def test_describe_image_scan_findings_error_repo_not_exists():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    repo_name = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.describe_image_scan_findings(
            repositoryName=repo_name, imageId={"imageTag": "latest"}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("DescribeImageScanFindings")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("RepositoryNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The repository with name '{repo_name}' does not exist "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_describe_image_scan_findings_error_image_not_exists():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_tag = "not-exists"

    # when
    with pytest.raises(ClientError) as e:
        client.describe_image_scan_findings(
            repositoryName=repo_name, imageId={"imageTag": image_tag}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("DescribeImageScanFindings")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("ImageNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"The image with imageId {{imageDigest:'null', imageTag:'{image_tag}'}} does not exist "
        f"within the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_describe_image_scan_findings_error_scan_not_exists():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    repo_name = "test-repo"
    client.create_repository(repositoryName=repo_name)
    image_tag = "latest"
    client.put_image(
        repositoryName=repo_name,
        imageManifest=json.dumps(_create_image_manifest()),
        imageTag=image_tag,
    )

    # when
    with pytest.raises(ClientError) as e:
        client.describe_image_scan_findings(
            repositoryName=repo_name, imageId={"imageTag": image_tag}
        )

    # then
    ex = e.value
    ex.operation_name.should.equal("DescribeImageScanFindings")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("ScanNotFoundException")
    ex.response["Error"]["Message"].should.equal(
        f"Image scan does not exist for the image with '{{imageDigest:'null', imageTag:'{image_tag}'}}' "
        f"in the repository with name '{repo_name}' "
        f"in the registry with id '{ACCOUNT_ID}'"
    )


@mock_ecr
def test_put_replication_configuration():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    config = {
        "rules": [{"destinations": [{"region": "eu-west-1", "registryId": ACCOUNT_ID}]}]
    }

    # when
    response = client.put_replication_configuration(replicationConfiguration=config)

    # then
    response["replicationConfiguration"].should.equal(config)


@mock_ecr
def test_put_replication_configuration_error_feature_disabled():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    config = {
        "rules": [
            {
                "destinations": [
                    {"region": "eu-central-1", "registryId": "111111111111"},
                ]
            },
            {
                "destinations": [
                    {"region": "eu-central-1", "registryId": "222222222222"},
                ]
            },
        ]
    }

    # when
    with pytest.raises(ClientError) as e:
        client.put_replication_configuration(replicationConfiguration=config)

    # then
    ex = e.value
    ex.operation_name.should.equal("PutReplicationConfiguration")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("ValidationException")
    ex.response["Error"]["Message"].should.equal("This feature is disabled")


@mock_ecr
def test_put_replication_configuration_error_same_source():
    # given
    region_name = "eu-central-1"
    client = boto3.client("ecr", region_name=region_name)
    config = {
        "rules": [
            {"destinations": [{"region": region_name, "registryId": ACCOUNT_ID}]},
        ]
    }

    # when
    with pytest.raises(ClientError) as e:
        client.put_replication_configuration(replicationConfiguration=config)

    # then
    ex = e.value
    ex.operation_name.should.equal("PutReplicationConfiguration")
    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
    ex.response["Error"]["Code"].should.contain("InvalidParameterException")
    ex.response["Error"]["Message"].should.equal(
        "Invalid parameter at 'replicationConfiguration' failed to satisfy constraint: "
        "'Replication destination cannot be the same as the source registry'"
    )


@mock_ecr
def test_describe_registry():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")

    # when
    response = client.describe_registry()

    # then
    response["registryId"].should.equal(ACCOUNT_ID)
    response["replicationConfiguration"].should.equal({"rules": []})


@mock_ecr
def test_describe_registry_after_update():
    # given
    client = boto3.client("ecr", region_name="eu-central-1")
    config = {
        "rules": [
            {"destinations": [{"region": "eu-west-1", "registryId": ACCOUNT_ID}]},
        ]
    }
    client.put_replication_configuration(replicationConfiguration=config)

    # when
    response = client.describe_registry()

    # then
    response["replicationConfiguration"].should.equal(config)
