import boto3
import requests
import pytest
import sure  # noqa # pylint: disable=unused-import

from botocore.client import ClientError
from moto.s3.responses import DEFAULT_REGION_NAME


from moto import mock_s3


@mock_s3
def test_get_bucket_tagging_unknown_bucket():
    client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)

    with pytest.raises(ClientError) as ex:
        client.get_bucket_tagging(Bucket="foobar")
    ex.value.response["Error"]["Code"].should.equal("NoSuchBucket")
    ex.value.response["Error"]["Message"].should.equal(
        "The specified bucket does not exist"
    )


@mock_s3
def test_put_object_with_tagging():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    key = "key-with-tags"
    s3.create_bucket(Bucket=bucket_name)

    # using system tags will fail
    with pytest.raises(ClientError) as err:
        s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="aws:foo=bar")

    e = err.value
    e.response["Error"]["Code"].should.equal("InvalidTag")

    s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="foo=bar")

    s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.contain(
        {"Key": "foo", "Value": "bar"}
    )

    resp = s3.get_object(Bucket=bucket_name, Key=key)
    resp.should.have.key("TagCount").equals(1)

    s3.delete_object_tagging(Bucket=bucket_name, Key=key)

    s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.equal([])


@mock_s3
def test_put_bucket_tagging():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    s3.create_bucket(Bucket=bucket_name)

    # With 1 tag:
    resp = s3.put_bucket_tagging(
        Bucket=bucket_name, Tagging={"TagSet": [{"Key": "TagOne", "Value": "ValueOne"}]}
    )
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

    # With multiple tags:
    resp = s3.put_bucket_tagging(
        Bucket=bucket_name,
        Tagging={
            "TagSet": [
                {"Key": "TagOne", "Value": "ValueOne"},
                {"Key": "TagTwo", "Value": "ValueTwo"},
            ]
        },
    )

    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

    # No tags is also OK:
    resp = s3.put_bucket_tagging(Bucket=bucket_name, Tagging={"TagSet": []})
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

    # With duplicate tag keys:
    with pytest.raises(ClientError) as err:
        s3.put_bucket_tagging(
            Bucket=bucket_name,
            Tagging={
                "TagSet": [
                    {"Key": "TagOne", "Value": "ValueOne"},
                    {"Key": "TagOne", "Value": "ValueOneAgain"},
                ]
            },
        )
    e = err.value
    e.response["Error"]["Code"].should.equal("InvalidTag")
    e.response["Error"]["Message"].should.equal(
        "Cannot provide multiple Tags with the same key"
    )

    # Cannot put tags that are "system" tags - i.e. tags that start with "aws:"
    with pytest.raises(ClientError) as ce:
        s3.put_bucket_tagging(
            Bucket=bucket_name,
            Tagging={"TagSet": [{"Key": "aws:sometag", "Value": "nope"}]},
        )
    e = ce.value
    e.response["Error"]["Code"].should.equal("InvalidTag")
    e.response["Error"]["Message"].should.equal(
        "System tags cannot be added/updated by requester"
    )

    # This is OK though:
    s3.put_bucket_tagging(
        Bucket=bucket_name,
        Tagging={"TagSet": [{"Key": "something:aws:stuff", "Value": "this is fine"}]},
    )


@mock_s3
def test_get_bucket_tagging():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    s3.create_bucket(Bucket=bucket_name)
    s3.put_bucket_tagging(
        Bucket=bucket_name,
        Tagging={
            "TagSet": [
                {"Key": "TagOne", "Value": "ValueOne"},
                {"Key": "TagTwo", "Value": "ValueTwo"},
            ]
        },
    )

    # Get the tags for the bucket:
    resp = s3.get_bucket_tagging(Bucket=bucket_name)
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
    len(resp["TagSet"]).should.equal(2)

    # With no tags:
    s3.put_bucket_tagging(Bucket=bucket_name, Tagging={"TagSet": []})

    with pytest.raises(ClientError) as err:
        s3.get_bucket_tagging(Bucket=bucket_name)

    e = err.value
    e.response["Error"]["Code"].should.equal("NoSuchTagSet")
    e.response["Error"]["Message"].should.equal("The TagSet does not exist")


@mock_s3
def test_delete_bucket_tagging():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    s3.create_bucket(Bucket=bucket_name)

    s3.put_bucket_tagging(
        Bucket=bucket_name,
        Tagging={
            "TagSet": [
                {"Key": "TagOne", "Value": "ValueOne"},
                {"Key": "TagTwo", "Value": "ValueTwo"},
            ]
        },
    )

    resp = s3.delete_bucket_tagging(Bucket=bucket_name)
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(204)

    with pytest.raises(ClientError) as err:
        s3.get_bucket_tagging(Bucket=bucket_name)

    e = err.value
    e.response["Error"]["Code"].should.equal("NoSuchTagSet")
    e.response["Error"]["Message"].should.equal("The TagSet does not exist")


@mock_s3
def test_put_object_tagging():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    key = "key-with-tags"
    s3.create_bucket(Bucket=bucket_name)

    with pytest.raises(ClientError) as err:
        s3.put_object_tagging(
            Bucket=bucket_name,
            Key=key,
            Tagging={
                "TagSet": [
                    {"Key": "item1", "Value": "foo"},
                    {"Key": "item2", "Value": "bar"},
                ]
            },
        )

    e = err.value
    e.response["Error"].should.equal(
        {
            "Code": "NoSuchKey",
            "Message": "The specified key does not exist.",
            "Key": "key-with-tags",
            "RequestID": "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE",
        }
    )

    s3.put_object(Bucket=bucket_name, Key=key, Body="test")

    # using system tags will fail
    with pytest.raises(ClientError) as err:
        s3.put_object_tagging(
            Bucket=bucket_name,
            Key=key,
            Tagging={"TagSet": [{"Key": "aws:item1", "Value": "foo"}]},
        )

    e = err.value
    e.response["Error"]["Code"].should.equal("InvalidTag")

    resp = s3.put_object_tagging(
        Bucket=bucket_name,
        Key=key,
        Tagging={
            "TagSet": [
                {"Key": "item1", "Value": "foo"},
                {"Key": "item2", "Value": "bar"},
            ]
        },
    )

    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)


@mock_s3
def test_put_object_tagging_on_earliest_version():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    key = "key-with-tags"
    s3.create_bucket(Bucket=bucket_name)
    s3_resource = boto3.resource("s3")
    bucket_versioning = s3_resource.BucketVersioning(bucket_name)
    bucket_versioning.enable()
    bucket_versioning.status.should.equal("Enabled")

    with pytest.raises(ClientError) as err:
        s3.put_object_tagging(
            Bucket=bucket_name,
            Key=key,
            Tagging={
                "TagSet": [
                    {"Key": "item1", "Value": "foo"},
                    {"Key": "item2", "Value": "bar"},
                ]
            },
        )

    e = err.value
    e.response["Error"].should.equal(
        {
            "Code": "NoSuchKey",
            "Message": "The specified key does not exist.",
            "Key": "key-with-tags",
            "RequestID": "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE",
        }
    )

    s3.put_object(Bucket=bucket_name, Key=key, Body="test")
    s3.put_object(Bucket=bucket_name, Key=key, Body="test_updated")

    object_versions = list(s3_resource.Bucket(bucket_name).object_versions.all())
    first_object = object_versions[0]
    second_object = object_versions[1]

    resp = s3.put_object_tagging(
        Bucket=bucket_name,
        Key=key,
        Tagging={
            "TagSet": [
                {"Key": "item1", "Value": "foo"},
                {"Key": "item2", "Value": "bar"},
            ]
        },
        VersionId=first_object.id,
    )

    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

    # Older version has tags while the most recent does not
    resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=first_object.id)
    resp["VersionId"].should.equal(first_object.id)
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
    sorted_tagset = sorted(resp["TagSet"], key=lambda t: t["Key"])
    sorted_tagset.should.equal(
        [{"Key": "item1", "Value": "foo"}, {"Key": "item2", "Value": "bar"}]
    )

    resp = s3.get_object_tagging(
        Bucket=bucket_name, Key=key, VersionId=second_object.id
    )
    resp["VersionId"].should.equal(second_object.id)
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
    resp["TagSet"].should.equal([])


@mock_s3
def test_put_object_tagging_on_both_version():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    key = "key-with-tags"
    s3.create_bucket(Bucket=bucket_name)
    s3_resource = boto3.resource("s3")
    bucket_versioning = s3_resource.BucketVersioning(bucket_name)
    bucket_versioning.enable()
    bucket_versioning.status.should.equal("Enabled")

    with pytest.raises(ClientError) as err:
        s3.put_object_tagging(
            Bucket=bucket_name,
            Key=key,
            Tagging={
                "TagSet": [
                    {"Key": "item1", "Value": "foo"},
                    {"Key": "item2", "Value": "bar"},
                ]
            },
        )

    e = err.value
    e.response["Error"].should.equal(
        {
            "Code": "NoSuchKey",
            "Message": "The specified key does not exist.",
            "Key": "key-with-tags",
            "RequestID": "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE",
        }
    )

    s3.put_object(Bucket=bucket_name, Key=key, Body="test")
    s3.put_object(Bucket=bucket_name, Key=key, Body="test_updated")

    object_versions = list(s3_resource.Bucket(bucket_name).object_versions.all())
    first_object = object_versions[0]
    second_object = object_versions[1]

    resp = s3.put_object_tagging(
        Bucket=bucket_name,
        Key=key,
        Tagging={
            "TagSet": [
                {"Key": "item1", "Value": "foo"},
                {"Key": "item2", "Value": "bar"},
            ]
        },
        VersionId=first_object.id,
    )
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

    resp = s3.put_object_tagging(
        Bucket=bucket_name,
        Key=key,
        Tagging={
            "TagSet": [
                {"Key": "item1", "Value": "baz"},
                {"Key": "item2", "Value": "bin"},
            ]
        },
        VersionId=second_object.id,
    )
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

    resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=first_object.id)
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
    sorted_tagset = sorted(resp["TagSet"], key=lambda t: t["Key"])
    sorted_tagset.should.equal(
        [{"Key": "item1", "Value": "foo"}, {"Key": "item2", "Value": "bar"}]
    )

    resp = s3.get_object_tagging(
        Bucket=bucket_name, Key=key, VersionId=second_object.id
    )
    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
    sorted_tagset = sorted(resp["TagSet"], key=lambda t: t["Key"])
    sorted_tagset.should.equal(
        [{"Key": "item1", "Value": "baz"}, {"Key": "item2", "Value": "bin"}]
    )


@mock_s3
def test_put_object_tagging_with_single_tag():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    key = "key-with-tags"
    s3.create_bucket(Bucket=bucket_name)

    s3.put_object(Bucket=bucket_name, Key=key, Body="test")

    resp = s3.put_object_tagging(
        Bucket=bucket_name,
        Key=key,
        Tagging={"TagSet": [{"Key": "item1", "Value": "foo"}]},
    )

    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)


@mock_s3
def test_get_object_tagging():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    bucket_name = "mybucket"
    key = "key-with-tags"
    s3.create_bucket(Bucket=bucket_name)

    s3.put_object(Bucket=bucket_name, Key=key, Body="test")

    resp = s3.get_object_tagging(Bucket=bucket_name, Key=key)
    resp["TagSet"].should.have.length_of(0)

    s3.put_object_tagging(
        Bucket=bucket_name,
        Key=key,
        Tagging={
            "TagSet": [
                {"Key": "item1", "Value": "foo"},
                {"Key": "item2", "Value": "bar"},
            ]
        },
    )
    resp = s3.get_object_tagging(Bucket=bucket_name, Key=key)

    resp["TagSet"].should.have.length_of(2)
    resp["TagSet"].should.contain({"Key": "item1", "Value": "foo"})
    resp["TagSet"].should.contain({"Key": "item2", "Value": "bar"})


@mock_s3
def test_objects_tagging_with_same_key_name():
    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
    key_name = "file.txt"

    bucket1 = "bucket-1"
    s3.create_bucket(Bucket=bucket1)
    tagging = "variable=one"

    s3.put_object(Bucket=bucket1, Body=b"test", Key=key_name, Tagging=tagging)

    bucket2 = "bucket-2"
    s3.create_bucket(Bucket=bucket2)
    tagging2 = "variable=two"

    s3.put_object(Bucket=bucket2, Body=b"test", Key=key_name, Tagging=tagging2)

    variable1 = s3.get_object_tagging(Bucket=bucket1, Key=key_name)["TagSet"][0][
        "Value"
    ]
    variable2 = s3.get_object_tagging(Bucket=bucket2, Key=key_name)["TagSet"][0][
        "Value"
    ]

    assert variable1 == "one"
    assert variable2 == "two"


@mock_s3
def test_generate_url_for_tagged_object():
    s3 = boto3.client("s3")
    s3.create_bucket(Bucket="my-bucket")
    s3.put_object(
        Bucket="my-bucket", Key="test.txt", Body=b"abc", Tagging="MyTag=value"
    )
    url = s3.generate_presigned_url(
        "get_object", Params={"Bucket": "my-bucket", "Key": "test.txt"}
    )
    response = requests.get(url)
    response.content.should.equal(b"abc")
    response.headers["x-amz-tagging-count"].should.equal("1")
