import os
import re
import string
from datetime import datetime, timedelta, timezone
from unittest import SkipTest
from uuid import uuid4

import boto3
import pytest
from botocore.exceptions import ClientError, ParamValidationError
from dateutil.tz import tzlocal
from freezegun import freeze_time

from moto import mock_lambda, mock_secretsmanager, settings
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID

DEFAULT_SECRET_NAME = "test-secret7"


@mock_secretsmanager
def test_get_secret_value():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="java-util-test-password", SecretString="foosecret")
    result = conn.get_secret_value(SecretId="java-util-test-password")
    assert result["SecretString"] == "foosecret"


@mock_secretsmanager
def test_secret_arn():
    region = "us-west-2"
    conn = boto3.client("secretsmanager", region_name=region)

    create_dict = conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString="secret_string",
    )
    assert re.match(
        f"arn:aws:secretsmanager:{region}:{ACCOUNT_ID}:secret:{DEFAULT_SECRET_NAME}-"
        + r"\w{6}",
        create_dict["ARN"],
    )


@mock_secretsmanager
def test_create_secret_with_client_request_token():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
    create_dict = conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString="secret_string",
        ClientRequestToken=version_id,
    )
    assert create_dict
    assert create_dict["VersionId"] == version_id


@mock_secretsmanager
def test_get_secret_value_by_arn():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    name = "java-util-test-password"
    secret_value = "test_get_secret_value_by_arn"
    result = conn.create_secret(Name=name, SecretString=secret_value)
    arn = result["ARN"]
    assert re.match(
        f"^arn:aws:secretsmanager:us-west-2:{ACCOUNT_ID}:secret:{name}", arn
    )

    result = conn.get_secret_value(SecretId=arn)
    assert result["SecretString"] == secret_value


@mock_secretsmanager
def test_get_secret_value_binary():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="java-util-test-password", SecretBinary=b"foosecret")
    result = conn.get_secret_value(SecretId="java-util-test-password")
    assert result["SecretBinary"] == b"foosecret"


@mock_secretsmanager
def test_get_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError) as cm:
        conn.get_secret_value(SecretId="i-dont-exist")

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_get_secret_that_does_not_match():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="java-util-test-password", SecretString="foosecret")

    with pytest.raises(ClientError) as cm:
        conn.get_secret_value(SecretId="i-dont-match")

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_get_secret_value_that_is_marked_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError):
        conn.get_secret_value(SecretId="test-secret")


@mock_secretsmanager
def test_get_secret_that_has_no_value():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="secret-no-value")

    with pytest.raises(ClientError) as cm:
        conn.get_secret_value(SecretId="secret-no-value")

    assert (
        "Secrets Manager can't find the specified secret value for staging label: AWSCURRENT"
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_get_secret_version_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    result = conn.create_secret(Name="java-util-test-password", SecretString="v")
    secret_arn = result["ARN"]
    missing_version_id = "00000000-0000-0000-0000-000000000000"

    with pytest.raises(ClientError) as cm:
        conn.get_secret_value(SecretId=secret_arn, VersionId=missing_version_id)

    assert (
        "Secrets Manager can't find the specified "
        "secret value for VersionId: 00000000-0000-0000-0000-000000000000"
    ) == cm.value.response["Error"]["Message"]


@mock_secretsmanager
def test_get_secret_version_stage_mismatch():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    result = conn.create_secret(Name="test-secret", SecretString="secret")
    secret_arn = result["ARN"]

    rotated_secret = conn.rotate_secret(
        SecretId=secret_arn, RotationRules={"AutomaticallyAfterDays": 42}
    )

    desc_secret = conn.describe_secret(SecretId=secret_arn)
    versions_to_stages = desc_secret["VersionIdsToStages"]
    version_for_test = rotated_secret["VersionId"]
    stages_for_version = versions_to_stages[version_for_test]

    assert "AWSPENDING" not in stages_for_version
    with pytest.raises(ClientError) as cm:
        conn.get_secret_value(
            SecretId=secret_arn, VersionId=version_for_test, VersionStage="AWSPENDING"
        )

    assert (
        "You provided a VersionStage that is not associated to the provided VersionId."
    ) == cm.value.response["Error"]["Message"]


@mock_secretsmanager
def test_create_secret():
    conn = boto3.client("secretsmanager", region_name="us-east-1")

    result = conn.create_secret(Name="test-secret", SecretString="foosecret")
    assert result["ARN"]
    assert result["Name"] == "test-secret"
    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "foosecret"


@mock_secretsmanager
def test_create_secret_with_tags():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    secret_name = "test-secret-with-tags"

    result = conn.create_secret(
        Name=secret_name,
        SecretString="foosecret",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )
    assert result["ARN"]
    assert result["Name"] == secret_name
    secret_value = conn.get_secret_value(SecretId=secret_name)
    assert secret_value["SecretString"] == "foosecret"
    secret_details = conn.describe_secret(SecretId=secret_name)
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]


@mock_secretsmanager
def test_create_secret_with_description():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    secret_name = "test-secret-with-tags"

    result = conn.create_secret(
        Name=secret_name, SecretString="foosecret", Description="desc"
    )
    assert result["ARN"]
    assert result["Name"] == secret_name
    secret_value = conn.get_secret_value(SecretId=secret_name)
    assert secret_value["SecretString"] == "foosecret"
    secret_details = conn.describe_secret(SecretId=secret_name)
    assert secret_details["Description"] == "desc"


@mock_secretsmanager
def test_create_secret_with_tags_and_description():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    secret_name = "test-secret-with-tags"

    result = conn.create_secret(
        Name=secret_name,
        SecretString="foosecret",
        Description="desc",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )
    assert result["ARN"]
    assert result["Name"] == secret_name
    secret_value = conn.get_secret_value(SecretId=secret_name)
    assert secret_value["SecretString"] == "foosecret"
    secret_details = conn.describe_secret(SecretId=secret_name)
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]
    assert secret_details["Description"] == "desc"


@mock_secretsmanager
def test_create_secret_without_value():
    conn = boto3.client("secretsmanager", region_name="us-east-2")
    secret_name = f"secret-{str(uuid4())[0:6]}"

    create = conn.create_secret(Name=secret_name)
    assert set(create.keys()) == {"ARN", "Name", "ResponseMetadata"}

    describe = conn.describe_secret(SecretId=secret_name)
    assert set(describe.keys()) == {
        "ARN",
        "Name",
        "LastChangedDate",
        "CreatedDate",
        "ResponseMetadata",
    }

    with pytest.raises(ClientError) as exc:
        conn.get_secret_value(SecretId=secret_name)
    err = exc.value.response["Error"]
    assert err["Code"] == "ResourceNotFoundException"

    updated = conn.update_secret(
        SecretId=secret_name,
        Description="new desc",
    )
    assert set(updated.keys()) == {"ARN", "Name", "ResponseMetadata"}

    describe = conn.describe_secret(SecretId=secret_name)
    assert set(describe.keys()) == {
        "ARN",
        "Name",
        "Description",
        "LastChangedDate",
        "CreatedDate",
        "ResponseMetadata",
    }

    deleted = conn.delete_secret(SecretId=secret_name)
    assert set(deleted.keys()) == {"ARN", "Name", "DeletionDate", "ResponseMetadata"}


@mock_secretsmanager
def test_create_secret_that_has_no_value_and_then_update():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="secret-no-value")

    conn.update_secret(
        SecretId="secret-no-value",
        SecretString="barsecret",
        Description="desc",
    )

    secret = conn.get_secret_value(SecretId="secret-no-value")
    assert secret["SecretString"] == "barsecret"


@mock_secretsmanager
def test_update_secret_without_value():
    conn = boto3.client("secretsmanager", region_name="us-east-2")
    secret_name = f"secret-{str(uuid4())[0:6]}"

    create = conn.create_secret(Name=secret_name, SecretString="foosecret")
    assert set(create.keys()) == {"ARN", "Name", "VersionId", "ResponseMetadata"}
    version_id = create["VersionId"]

    describe1 = conn.describe_secret(SecretId=secret_name)
    assert set(describe1.keys()) == {
        "ARN",
        "Name",
        "LastChangedDate",
        "VersionIdsToStages",
        "CreatedDate",
        "ResponseMetadata",
    }

    conn.get_secret_value(SecretId=secret_name)

    updated = conn.update_secret(SecretId=secret_name, Description="desc")
    assert set(updated.keys()) == {"ARN", "Name", "ResponseMetadata"}

    describe2 = conn.describe_secret(SecretId=secret_name)
    # AWS also includes 'LastAccessedDate'
    assert set(describe2.keys()) == {
        "ARN",
        "Name",
        "Description",
        "LastChangedDate",
        "VersionIdsToStages",
        "CreatedDate",
        "ResponseMetadata",
    }
    assert describe1["VersionIdsToStages"] == describe2["VersionIdsToStages"]

    value = conn.get_secret_value(SecretId=secret_name)
    assert value["SecretString"] == "foosecret"
    assert value["VersionId"] == version_id

    conn.delete_secret(SecretId=secret_name)


@mock_secretsmanager
def test_delete_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    deleted_secret = conn.delete_secret(SecretId="test-secret")

    assert deleted_secret["ARN"]
    assert deleted_secret["Name"] == "test-secret"
    assert deleted_secret["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)

    secret_details = conn.describe_secret(SecretId="test-secret")

    assert secret_details["ARN"]
    assert secret_details["Name"] == "test-secret"
    assert secret_details["DeletedDate"] > datetime.fromtimestamp(1, timezone.utc)


@mock_secretsmanager
def test_delete_secret_by_arn():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    secret = conn.create_secret(Name="test-secret", SecretString="foosecret")

    deleted_secret = conn.delete_secret(SecretId=secret["ARN"])

    assert deleted_secret["ARN"] == secret["ARN"]
    assert deleted_secret["Name"] == "test-secret"
    assert deleted_secret["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)

    secret_details = conn.describe_secret(SecretId="test-secret")

    assert secret_details["ARN"] == secret["ARN"]
    assert secret_details["Name"] == "test-secret"
    assert secret_details["DeletedDate"] > datetime.fromtimestamp(1, timezone.utc)


@mock_secretsmanager
def test_delete_secret_force():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    result = conn.delete_secret(SecretId="test-secret", ForceDeleteWithoutRecovery=True)

    assert result["ARN"]
    assert result["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)
    assert result["Name"] == "test-secret"

    with pytest.raises(ClientError):
        conn.get_secret_value(SecretId="test-secret")


@mock_secretsmanager
def test_delete_secret_force_no_such_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    deleted_secret = conn.delete_secret(
        SecretId=DEFAULT_SECRET_NAME, ForceDeleteWithoutRecovery=True
    )
    assert deleted_secret
    assert deleted_secret["Name"] == DEFAULT_SECRET_NAME


@mock_secretsmanager
def test_delete_secret_force_with_arn():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    create_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")

    result = conn.delete_secret(
        SecretId=create_secret["ARN"], ForceDeleteWithoutRecovery=True
    )

    assert result["ARN"]
    assert result["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)
    assert result["Name"] == "test-secret"

    with pytest.raises(ClientError):
        conn.get_secret_value(SecretId="test-secret")


@mock_secretsmanager
def test_delete_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        conn.delete_secret(SecretId="i-dont-exist")


@mock_secretsmanager
def test_delete_secret_fails_with_both_force_delete_flag_and_recovery_window_flag():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        conn.delete_secret(
            SecretId="test-secret",
            RecoveryWindowInDays=1,
            ForceDeleteWithoutRecovery=True,
        )


@mock_secretsmanager
def test_delete_secret_recovery_window_invalid_values():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    for nr in [0, 2, 6, 31, 100]:
        with pytest.raises(ClientError) as exc:
            conn.delete_secret(SecretId="test-secret", RecoveryWindowInDays=nr)
        err = exc.value.response["Error"]
        assert err["Code"] == "InvalidParameterException"
        assert (
            "RecoveryWindowInDays value must be between 7 and 30 days (inclusive)"
            in err["Message"]
        )


@mock_secretsmanager
def test_delete_secret_force_no_such_secret_with_invalid_recovery_window():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    for nr in [0, 2, 6, 31, 100]:
        with pytest.raises(ClientError) as exc:
            conn.delete_secret(
                SecretId="test-secret",
                RecoveryWindowInDays=nr,
                ForceDeleteWithoutRecovery=True,
            )
        err = exc.value.response["Error"]
        assert err["Code"] == "InvalidParameterException"
        assert (
            "RecoveryWindowInDays value must be between 7 and 30 days (inclusive)"
            in err["Message"]
        )


@mock_secretsmanager
def test_delete_secret_that_is_marked_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError):
        conn.delete_secret(SecretId="test-secret")


@mock_secretsmanager
def test_get_random_password_default_length():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password()
    assert len(random_password["RandomPassword"]) == 32


@mock_secretsmanager
def test_get_random_password_default_requirements():
    # When require_each_included_type, default true
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password()
    # Should contain lowercase, upppercase, digit, special character
    assert any(c.islower() for c in random_password["RandomPassword"])
    assert any(c.isupper() for c in random_password["RandomPassword"])
    assert any(c.isdigit() for c in random_password["RandomPassword"])
    assert any(c in string.punctuation for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_password_custom_length():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=50)
    assert len(random_password["RandomPassword"]) == 50


@mock_secretsmanager
def test_get_random_exclude_lowercase():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=55, ExcludeLowercase=True)
    assert not any(c.islower() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_exclude_uppercase():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=55, ExcludeUppercase=True)
    assert not any(c.isupper() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_exclude_characters_and_symbols():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(
        PasswordLength=20, ExcludeCharacters="xyzDje@?!."
    )
    assert not any(c in "xyzDje@?!." for c in random_password["RandomPassword"])
    assert len(random_password["RandomPassword"]) == 20


@mock_secretsmanager
def test_get_random_exclude_numbers():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=100, ExcludeNumbers=True)
    assert not any(c.isdigit() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_exclude_punctuation():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(
        PasswordLength=100, ExcludePunctuation=True
    )
    assert not any(c in string.punctuation for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_include_space_false():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=300)
    assert not any(c.isspace() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_include_space_true():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=4, IncludeSpace=True)
    assert any(c.isspace() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_require_each_included_type():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(
        PasswordLength=4, RequireEachIncludedType=True
    )
    assert any(c in string.punctuation for c in random_password["RandomPassword"])
    assert any(c in string.ascii_lowercase for c in random_password["RandomPassword"])
    assert any(c in string.ascii_uppercase for c in random_password["RandomPassword"])
    assert any(c in string.digits for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_too_short_password():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        conn.get_random_password(PasswordLength=3)


@mock_secretsmanager
def test_get_random_too_long_password():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(Exception):
        conn.get_random_password(PasswordLength=5555)


@mock_secretsmanager
def test_describe_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.create_secret(Name="test-secret-2", SecretString="barsecret")

    secret_description = conn.describe_secret(SecretId="test-secret")
    secret_description_2 = conn.describe_secret(SecretId="test-secret-2")

    assert secret_description  # Returned dict is not empty
    assert secret_description["Name"] == ("test-secret")
    assert secret_description["ARN"] != ""  # Test arn not empty
    assert secret_description_2["Name"] == ("test-secret-2")
    assert secret_description_2["ARN"] != ""  # Test arn not empty
    assert secret_description["CreatedDate"] <= datetime.now(tz=tzlocal())
    assert secret_description["CreatedDate"] > datetime.fromtimestamp(1, timezone.utc)
    assert secret_description_2["CreatedDate"] <= datetime.now(tz=tzlocal())
    assert secret_description_2["CreatedDate"] > datetime.fromtimestamp(1, timezone.utc)
    assert secret_description["LastChangedDate"] <= datetime.now(tz=tzlocal())
    assert secret_description["LastChangedDate"] > datetime.fromtimestamp(
        1, timezone.utc
    )
    assert secret_description_2["LastChangedDate"] <= datetime.now(tz=tzlocal())
    assert secret_description_2["LastChangedDate"] > datetime.fromtimestamp(
        1, timezone.utc
    )


@mock_secretsmanager
@pytest.mark.parametrize("name", ["testsecret", "test-secret"])
def test_describe_secret_with_arn(name):
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    results = conn.create_secret(Name=name, SecretString="foosecret")

    secret_description = conn.describe_secret(SecretId=results["ARN"])

    assert secret_description  # Returned dict is not empty
    assert secret_description["Name"] == name
    assert secret_description["ARN"] == results["ARN"]
    assert conn.list_secrets()["SecretList"][0]["ARN"] == results["ARN"]

    # We can also supply a partial ARN
    partial_arn = f"arn:aws:secretsmanager:us-west-2:{ACCOUNT_ID}:secret:{name}"
    resp = conn.get_secret_value(SecretId=partial_arn)
    assert resp["Name"] == name

    resp = conn.describe_secret(SecretId=partial_arn)
    assert resp["Name"] == name


@mock_secretsmanager
def test_describe_secret_with_KmsKeyId():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    results = conn.create_secret(
        Name="test-secret", SecretString="foosecret", KmsKeyId="dummy_arn"
    )

    secret_description = conn.describe_secret(SecretId=results["ARN"])

    assert secret_description["KmsKeyId"] == "dummy_arn"
    assert conn.list_secrets()["SecretList"][0]["KmsKeyId"] == (
        secret_description["KmsKeyId"]
    )


@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        conn.get_secret_value(SecretId="i-dont-exist")


@mock_secretsmanager
def test_describe_secret_that_does_not_match():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        conn.get_secret_value(SecretId="i-dont-match")


@mock_secretsmanager
def test_restore_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    described_secret_before = conn.describe_secret(SecretId="test-secret")
    assert described_secret_before["DeletedDate"] > datetime.fromtimestamp(
        1, timezone.utc
    )

    restored_secret = conn.restore_secret(SecretId="test-secret")
    assert restored_secret["ARN"]
    assert restored_secret["Name"] == "test-secret"

    described_secret_after = conn.describe_secret(SecretId="test-secret")
    assert "DeletedDate" not in described_secret_after


@mock_secretsmanager
def test_restore_secret_that_is_not_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    restored_secret = conn.restore_secret(SecretId="test-secret")
    assert restored_secret["ARN"]
    assert restored_secret["Name"] == "test-secret"


@mock_secretsmanager
def test_restore_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        conn.restore_secret(SecretId="i-dont-exist")


@mock_secretsmanager
def test_cancel_rotate_secret_with_invalid_secret_id():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    with pytest.raises(ClientError):
        conn.cancel_rotate_secret(SecretId="invalid_id")


@mock_secretsmanager
def test_cancel_rotate_secret_after_delete():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
    )
    conn.delete_secret(
        SecretId=DEFAULT_SECRET_NAME,
        RecoveryWindowInDays=7,
        ForceDeleteWithoutRecovery=False,
    )
    with pytest.raises(ClientError):
        conn.cancel_rotate_secret(SecretId=DEFAULT_SECRET_NAME)


@mock_secretsmanager
def test_cancel_rotate_secret_before_enable():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
    )
    with pytest.raises(ClientError):
        conn.cancel_rotate_secret(SecretId=DEFAULT_SECRET_NAME)


@mock_secretsmanager
def test_cancel_rotate_secret():
    if not settings.TEST_SERVER_MODE:
        raise SkipTest("rotation requires a server to be running")
    from tests.test_awslambda.utilities import get_role_name

    lambda_conn = boto3.client(
        "lambda", region_name="us-east-1", endpoint_url="http://localhost:5000"
    )
    func = lambda_conn.create_function(
        FunctionName="testFunction",
        Runtime="python3.11",
        Role=get_role_name(),
        Handler="lambda_function.lambda_handler",
        Code={"ZipFile": get_rotation_zip_file()},
        Description="Secret rotator",
        Timeout=3,
        MemorySize=128,
        Publish=True,
    )
    secrets_conn = boto3.client("secretsmanager", region_name="us-east-1")
    secrets_conn.create_secret(
        Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
    )
    secrets_conn.rotate_secret(
        SecretId=DEFAULT_SECRET_NAME,
        RotationLambdaARN=func["FunctionArn"],
        RotationRules={"AutomaticallyAfterDays": 30},
    )
    secrets_conn.cancel_rotate_secret(SecretId=DEFAULT_SECRET_NAME)
    cancelled_rotation = secrets_conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)

    assert not cancelled_rotation["RotationEnabled"]
    # The function config should be preserved
    assert cancelled_rotation["RotationLambdaARN"]


@mock_secretsmanager
def test_rotate_secret():
    # Setup
    frozen_time = datetime(2023, 5, 20, 10, 20, 30, tzinfo=tzlocal())
    rotate_after_days = 10
    with freeze_time(frozen_time):
        conn = boto3.client("secretsmanager", region_name="us-west-2")
        conn.create_secret(
            Name=DEFAULT_SECRET_NAME,
            SecretString="foosecret",
            Description="foodescription",
        )

        # Execute
        rotated_secret = conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME,
            RotationRules={"AutomaticallyAfterDays": rotate_after_days},
        )
        describe_secret = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)

        # Verify
        assert rotated_secret
        assert rotated_secret["ARN"] != ""  # Test arn not empty
        assert rotated_secret["Name"] == DEFAULT_SECRET_NAME
        assert rotated_secret["VersionId"] != ""
        assert describe_secret["Description"] == "foodescription"
        assert "NextRotationDate" in describe_secret
        assert "LastRotatedDate" in describe_secret

        # can't do freeze time tests in servermode tests
        if settings.TEST_SERVER_MODE:
            return

        assert describe_secret["LastChangedDate"] == frozen_time
        assert describe_secret["NextRotationDate"] == frozen_time + timedelta(
            days=rotate_after_days
        )


@mock_secretsmanager
def test_rotate_secret_without_secretstring():
    # This test just verifies that Moto does not fail
    conn = boto3.client("secretsmanager", region_name="us-east-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, Description="foodescription")

    # AWS will always require a Lambda ARN to do the actual rotating
    rotated_secret = conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME)
    assert rotated_secret["Name"] == DEFAULT_SECRET_NAME

    # Without secret-value, and without actual rotating, we can't verify much
    # Just that the secret exists/can be described
    # We cannot verify any versions info (as that is not created without a secret-value)
    describe_secret = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert describe_secret["Description"] == "foodescription"


@mock_secretsmanager
def test_rotate_secret_enable_rotation():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    initial_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert "RotationEnabled" not in initial_description

    conn.rotate_secret(
        SecretId=DEFAULT_SECRET_NAME, RotationRules={"AutomaticallyAfterDays": 42}
    )

    rotated_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert rotated_description
    assert rotated_description["RotationEnabled"] is True
    assert rotated_description["RotationRules"]["AutomaticallyAfterDays"] == 42


@mock_secretsmanager
def test_rotate_secret_that_is_marked_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError):
        conn.rotate_secret(SecretId="test-secret")


@mock_secretsmanager
def test_rotate_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", "us-west-2")

    with pytest.raises(ClientError):
        conn.rotate_secret(SecretId="i-dont-exist")


@mock_secretsmanager
def test_rotate_secret_that_does_not_match():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        conn.rotate_secret(SecretId="i-dont-match")


@mock_secretsmanager
def test_rotate_secret_client_request_token_too_short():
    # Test is intentionally empty. Boto3 catches too short ClientRequestToken
    # and raises ParamValidationError before Moto can see it.
    # test_server actually handles this error.
    assert True


@mock_secretsmanager
def test_rotate_secret_client_request_token_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    client_request_token = (
        "ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C-ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C"
    )
    with pytest.raises(ClientError):
        conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME, ClientRequestToken=client_request_token
        )


@mock_secretsmanager
def test_rotate_secret_rotation_lambda_arn_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    rotation_lambda_arn = "85B7-446A-B7E4" * 147  # == 2058 characters
    with pytest.raises(ClientError):
        conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME, RotationLambdaARN=rotation_lambda_arn
        )


@mock_secretsmanager
def test_rotate_secret_rotation_period_zero():
    # Test is intentionally empty. Boto3 catches zero day rotation period
    # and raises ParamValidationError before Moto can see it.
    # test_server actually handles this error.
    assert True


@mock_secretsmanager
def test_rotate_secret_rotation_period_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    rotation_rules = {"AutomaticallyAfterDays": 1001}
    with pytest.raises(ClientError):
        conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME, RotationRules=rotation_rules)


def get_rotation_zip_file():
    from tests.test_awslambda.utilities import _process_lambda

    func_str = """
import boto3
import json

def lambda_handler(event, context):
    arn = event['SecretId']
    token = event['ClientRequestToken']
    step = event['Step']

    client = boto3.client("secretsmanager", region_name="us-west-2", endpoint_url="http://motoserver:5000")
    metadata = client.describe_secret(SecretId=arn)
    value = client.get_secret_value(SecretId=arn, VersionId=token, VersionStage="AWSPENDING")

    if not metadata['RotationEnabled']:
        print("Secret %s is not enabled for rotation." % arn)
        raise ValueError("Secret %s is not enabled for rotation." % arn)
    versions = metadata['VersionIdsToStages']
    if token not in versions:
        print("Secret version %s has no stage for rotation of secret %s." % (token, arn))
        raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn))
    if "AWSCURRENT" in versions[token]:
        print("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn))
        return
    elif "AWSPENDING" not in versions[token]:
        print("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
        raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))

    if step == 'createSecret':
        try:
            client.get_secret_value(SecretId=arn, VersionId=token, VersionStage='AWSPENDING')
        except client.exceptions.ResourceNotFoundException:
            client.put_secret_value(
                SecretId=arn,
                ClientRequestToken=token,
                SecretString=json.dumps({'create': True}),
                VersionStages=['AWSPENDING']
            )

    if step == 'setSecret':
        client.put_secret_value(
            SecretId=arn,
            ClientRequestToken=token,
            SecretString='UpdatedValue',
            VersionStages=["AWSPENDING"]
        )

    elif step == 'finishSecret':
        current_version = next(
            version
            for version, stages in metadata['VersionIdsToStages'].items()
            if 'AWSCURRENT' in stages
        )
        print("current: %s new: %s" % (current_version, token))
        client.update_secret_version_stage(
            SecretId=arn,
            VersionStage='AWSCURRENT',
            MoveToVersionId=token,
            RemoveFromVersionId=current_version
        )
        client.update_secret_version_stage(
            SecretId=arn,
            VersionStage='AWSPENDING',
            RemoveFromVersionId=token
        )
    """
    return _process_lambda(func_str)


if settings.TEST_SERVER_MODE:

    @mock_lambda
    @mock_secretsmanager
    def test_rotate_secret_using_lambda():
        from tests.test_awslambda.utilities import get_role_name

        # Passing a `RotationLambdaARN` value to `rotate_secret` should invoke lambda
        lambda_conn = boto3.client(
            "lambda", region_name="us-west-2", endpoint_url="http://localhost:5000"
        )
        func = lambda_conn.create_function(
            FunctionName="testFunction",
            Runtime="python3.11",
            Role=get_role_name(),
            Handler="lambda_function.lambda_handler",
            Code={"ZipFile": get_rotation_zip_file()},
            Description="Secret rotator",
            Timeout=3,
            MemorySize=128,
            Publish=True,
        )

        secrets_conn = boto3.client(
            "secretsmanager",
            region_name="us-west-2",
            endpoint_url="http://localhost:5000",
        )
        secret = secrets_conn.create_secret(
            Name=DEFAULT_SECRET_NAME, SecretString="InitialValue"
        )
        initial_version = secret["VersionId"]

        rotated_secret = secrets_conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME,
            RotationLambdaARN=func["FunctionArn"],
            RotationRules={"AutomaticallyAfterDays": 30},
        )

        # Ensure we received an updated VersionId from `rotate_secret`
        assert rotated_secret["VersionId"] != initial_version

        updated_secret = secrets_conn.get_secret_value(
            SecretId=DEFAULT_SECRET_NAME, VersionStage="AWSCURRENT"
        )
        rotated_version = updated_secret["VersionId"]

        assert initial_version != rotated_version
        metadata = secrets_conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
        assert metadata["VersionIdsToStages"][initial_version] == ["AWSPREVIOUS"]
        assert metadata["VersionIdsToStages"][rotated_version] == ["AWSCURRENT"]
        assert updated_secret["SecretString"] == "UpdatedValue"


@mock_secretsmanager
def test_put_secret_value_on_non_existing_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    with pytest.raises(ClientError) as cm:
        conn.put_secret_value(
            SecretId=DEFAULT_SECRET_NAME,
            SecretString="foosecret",
            VersionStages=["AWSCURRENT"],
        )

    assert cm.value.response["Error"]["Message"] == (
        "Secrets Manager can't find the specified secret."
    )


@mock_secretsmanager
def test_put_secret_value_puts_new_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="foosecret",
        VersionStages=["AWSCURRENT"],
    )
    version_id = put_secret_value_dict["VersionId"]

    get_secret_value_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionId=version_id, VersionStage="AWSCURRENT"
    )

    assert get_secret_value_dict
    assert get_secret_value_dict["SecretString"] == "foosecret"


@mock_secretsmanager
def test_put_secret_binary_value_puts_new_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretBinary=b"foosecret",
        VersionStages=["AWSCURRENT"],
    )
    version_id = put_secret_value_dict["VersionId"]

    get_secret_value_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionId=version_id, VersionStage="AWSCURRENT"
    )

    assert get_secret_value_dict
    assert get_secret_value_dict["SecretBinary"] == b"foosecret"


@mock_secretsmanager
def test_create_and_put_secret_binary_value_puts_new_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret_update"
    )

    latest_secret = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)

    assert latest_secret
    assert latest_secret["SecretBinary"] == b"foosecret_update"


@mock_secretsmanager
def test_put_secret_binary_requires_either_string_or_binary():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    with pytest.raises(ClientError) as ire:
        conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME)

    assert ire.value.response["Error"]["Code"] == "InvalidRequestException"
    assert ire.value.response["Error"]["Message"] == (
        "You must provide either SecretString or SecretBinary."
    )


@mock_secretsmanager
def test_put_secret_value_can_get_first_version_if_put_twice():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="first_secret",
        VersionStages=["AWSCURRENT"],
    )
    first_version_id = put_secret_value_dict["VersionId"]
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret",
        VersionStages=["AWSCURRENT"],
    )

    first_secret_value_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionId=first_version_id
    )
    first_secret_value = first_secret_value_dict["SecretString"]

    assert first_secret_value == "first_secret"


@mock_secretsmanager
def test_put_secret_value_versions_differ_if_same_secret_put_twice():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    first_version_id = put_secret_value_dict["VersionId"]
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    second_version_id = put_secret_value_dict["VersionId"]

    assert first_version_id != second_version_id


@mock_secretsmanager
def test_put_secret_value_maintains_description_and_tags():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    previous_response = conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString="foosecret",
        Description="desc",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )
    previous_version_id = previous_response["VersionId"]

    conn = boto3.client("secretsmanager", region_name="us-west-2")
    current_response = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    current_version_id = current_response["VersionId"]

    secret_details = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]
    assert secret_details["Description"] == "desc"
    assert secret_details["VersionIdsToStages"] is not None
    assert previous_version_id in secret_details["VersionIdsToStages"]
    assert current_version_id in secret_details["VersionIdsToStages"]
    assert secret_details["VersionIdsToStages"][previous_version_id] == ["AWSPREVIOUS"]
    assert secret_details["VersionIdsToStages"][current_version_id] == ["AWSCURRENT"]


@mock_secretsmanager
def test_can_list_secret_version_ids():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    first_version_id = put_secret_value_dict["VersionId"]
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    second_version_id = put_secret_value_dict["VersionId"]

    versions_list = conn.list_secret_version_ids(SecretId=DEFAULT_SECRET_NAME)

    returned_version_ids = [v["VersionId"] for v in versions_list["Versions"]]

    assert [first_version_id, second_version_id].sort() == returned_version_ids.sort()


@mock_secretsmanager
def test_put_secret_value_version_stages_response():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    # Creation.
    first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString="first_secret_string",
        ClientRequestToken=first_version_id,
    )

    # Use PutSecretValue to push a new version with new version stages.
    second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
    second_version_stages = ["SAMPLESTAGE1", "SAMPLESTAGE0"]
    second_put_res_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret_string",
        VersionStages=second_version_stages,
        ClientRequestToken=second_version_id,
    )
    assert second_put_res_dict
    assert second_put_res_dict["VersionId"] == second_version_id
    assert second_put_res_dict["VersionStages"] == second_version_stages


@mock_secretsmanager
def test_put_secret_value_version_stages_pending_response():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    # Creation.
    first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString="first_secret_string",
        ClientRequestToken=first_version_id,
    )

    # Use PutSecretValue to push a new version with new version stages.
    second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
    second_version_stages = ["AWSPENDING"]
    second_put_res_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret_string",
        VersionStages=second_version_stages,
        ClientRequestToken=second_version_id,
    )
    assert second_put_res_dict
    assert second_put_res_dict["VersionId"] == second_version_id
    assert second_put_res_dict["VersionStages"] == second_version_stages


@mock_secretsmanager
def test_after_put_secret_value_version_stages_can_get_current():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    # Creation.
    first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
    first_secret_string = "first_secret_string"
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString=first_secret_string,
        ClientRequestToken=first_version_id,
    )

    # Use PutSecretValue to push a new version with new version stages.
    second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret_string",
        VersionStages=["SAMPLESTAGE1", "SAMPLESTAGE0"],
        ClientRequestToken=second_version_id,
    )

    # Get current.
    get_dict = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)
    assert get_dict
    assert get_dict["VersionId"] == first_version_id
    assert get_dict["SecretString"] == first_secret_string
    assert get_dict["VersionStages"] == ["AWSCURRENT"]


@mock_secretsmanager
def test_after_put_secret_value_version_stages_can_get_current_with_custom_version_stage():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    # Creation.
    first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
    first_secret_string = "first_secret_string"
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString=first_secret_string,
        ClientRequestToken=first_version_id,
    )

    # Use PutSecretValue to push a new version with new version stages.
    second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret_string",
        VersionStages=["SAMPLESTAGE1", "SAMPLESTAGE0"],
        ClientRequestToken=second_version_id,
    )
    # Create a third version with one of the old stages
    third_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce73"
    third_secret_string = "third_secret_string"
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString=third_secret_string,
        VersionStages=["SAMPLESTAGE1"],
        ClientRequestToken=third_version_id,
    )

    # Get current with the stage label of the third version.
    get_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionStage="SAMPLESTAGE1"
    )
    versions = conn.list_secret_version_ids(SecretId=DEFAULT_SECRET_NAME)["Versions"]
    versions_by_key = {version["VersionId"]: version for version in versions}
    # Check if indeed the third version is returned
    assert get_dict
    assert get_dict["VersionId"] == third_version_id
    assert get_dict["SecretString"] == third_secret_string
    assert get_dict["VersionStages"] == ["SAMPLESTAGE1"]
    # Check if all the versions have the proper labels
    assert versions_by_key[first_version_id]["VersionStages"] == ["AWSCURRENT"]
    assert versions_by_key[second_version_id]["VersionStages"] == ["SAMPLESTAGE0"]
    assert versions_by_key[third_version_id]["VersionStages"] == ["SAMPLESTAGE1"]


@mock_secretsmanager
def test_after_put_secret_value_version_stages_pending_can_get_current():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    # Creation.
    first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
    first_secret_string = "first_secret_string"
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString=first_secret_string,
        ClientRequestToken=first_version_id,
    )

    # Use PutSecretValue to push a new version with new version stages.
    pending_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret_string",
        VersionStages=["AWSPENDING"],
        ClientRequestToken=pending_version_id,
    )

    # Get current.
    get_dict = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)
    assert get_dict
    assert get_dict["VersionId"] == first_version_id
    assert get_dict["SecretString"] == first_secret_string
    assert get_dict["VersionStages"] == ["AWSCURRENT"]


@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_update_secret(pass_arn):
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")

    assert created_secret["ARN"]
    assert created_secret["Name"] == "test-secret"
    assert created_secret["VersionId"] != ""

    secret_id = created_secret["ARN"] if pass_arn else "test-secret"

    secret = conn.get_secret_value(SecretId=secret_id)
    assert secret["SecretString"] == "foosecret"

    updated_secret = conn.update_secret(
        SecretId=secret_id,
        SecretString="barsecret",
        Description="new desc",
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId=secret_id)
    assert secret["SecretString"] == "barsecret"
    assert created_secret["VersionId"] != updated_secret["VersionId"]

    assert conn.describe_secret(SecretId=secret_id)["Description"] == "new desc"


@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_update_secret_updates_last_changed_dates(pass_arn):
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    # create a secret
    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
    secret_id = created_secret["ARN"] if pass_arn else "test-secret"

    # save details for secret before modification
    secret_details_1 = conn.describe_secret(SecretId=secret_id)
    # check if only LastChangedDate changed, CreatedDate should stay the same
    with freeze_time(timedelta(minutes=1)):
        conn.update_secret(SecretId="test-secret", Description="new-desc")
        secret_details_2 = conn.describe_secret(SecretId=secret_id)
        assert secret_details_1["CreatedDate"] == secret_details_2["CreatedDate"]
        if os.environ.get("TEST_SERVER_MODE", "false").lower() == "false":
            assert (
                secret_details_1["LastChangedDate"]
                < secret_details_2["LastChangedDate"]
            )
        else:
            # Can't manipulate time in server mode, so use weaker constraints here
            assert (
                secret_details_1["LastChangedDate"]
                <= secret_details_2["LastChangedDate"]
            )


@mock_secretsmanager
def test_update_secret_with_tags_and_description():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(
        Name="test-secret",
        SecretString="foosecret",
        Description="desc",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )

    assert created_secret["ARN"]
    assert created_secret["Name"] == "test-secret"
    assert created_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "foosecret"

    updated_secret = conn.update_secret(
        SecretId="test-secret", SecretString="barsecret"
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "barsecret"
    assert created_secret["VersionId"] != updated_secret["VersionId"]
    secret_details = conn.describe_secret(SecretId="test-secret")
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]
    assert secret_details["Description"] == "desc"


@mock_secretsmanager
def test_update_secret_with_KmsKeyId():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(
        Name="test-secret", SecretString="foosecret", KmsKeyId="foo_arn"
    )

    assert created_secret["ARN"]
    assert created_secret["Name"] == "test-secret"
    assert created_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "foosecret"

    secret_details = conn.describe_secret(SecretId="test-secret")
    assert secret_details["KmsKeyId"] == "foo_arn"

    updated_secret = conn.update_secret(
        SecretId="test-secret", SecretString="barsecret", KmsKeyId="bar_arn"
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "barsecret"
    assert created_secret["VersionId"] != updated_secret["VersionId"]

    secret_details = conn.describe_secret(SecretId="test-secret")
    assert secret_details["KmsKeyId"] == "bar_arn"


@mock_secretsmanager
def test_update_secret_which_does_not_exit():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError) as cm:
        conn.update_secret(SecretId="test-secret", SecretString="barsecret")

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_update_secret_marked_as_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")
    conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError) as cm:
        conn.update_secret(SecretId="test-secret", SecretString="barsecret")

    assert (
        "because it was marked for deletion." in cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_update_secret_marked_as_deleted_after_restoring():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")
    conn.delete_secret(SecretId="test-secret")
    conn.restore_secret(SecretId="test-secret")

    updated_secret = conn.update_secret(
        SecretId="test-secret", SecretString="barsecret"
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""


@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_tag_resource(pass_arn):
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
    secret_id = created_secret["ARN"] if pass_arn else "test-secret"
    conn.tag_resource(
        SecretId=secret_id, Tags=[{"Key": "FirstTag", "Value": "SomeValue"}]
    )
    conn.tag_resource(
        SecretId="test-secret", Tags=[{"Key": "FirstTag", "Value": "SomeOtherValue"}]
    )
    conn.tag_resource(
        SecretId=secret_id, Tags=[{"Key": "SecondTag", "Value": "AnotherValue"}]
    )

    secrets = conn.list_secrets()
    assert secrets["SecretList"][0].get("Tags") == [
        {"Key": "FirstTag", "Value": "SomeOtherValue"},
        {"Key": "SecondTag", "Value": "AnotherValue"},
    ]

    with pytest.raises(ClientError) as cm:
        conn.tag_resource(
            SecretId="dummy-test-secret",
            Tags=[{"Key": "FirstTag", "Value": "SomeValue"}],
        )

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_untag_resource(pass_arn):
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
    secret_id = created_secret["ARN"] if pass_arn else "test-secret"
    conn.tag_resource(
        SecretId=secret_id,
        Tags=[
            {"Key": "FirstTag", "Value": "SomeValue"},
            {"Key": "SecondTag", "Value": "SomeValue"},
        ],
    )

    conn.untag_resource(SecretId=secret_id, TagKeys=["FirstTag"])
    secrets = conn.list_secrets()
    assert secrets["SecretList"][0].get("Tags") == [
        {"Key": "SecondTag", "Value": "SomeValue"},
    ]

    with pytest.raises(ClientError) as cm:
        conn.untag_resource(SecretId="dummy-test-secret", TagKeys=["FirstTag"])

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_secret_versions_to_stages_attribute_discrepancy():
    client = boto3.client("secretsmanager", region_name="us-west-2")

    resp = client.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
    previous_version_id = resp["VersionId"]

    resp = client.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    current_version_id = resp["VersionId"]

    secret = client.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    describe_vtos = secret["VersionIdsToStages"]
    assert describe_vtos[current_version_id] == ["AWSCURRENT"]
    assert describe_vtos[previous_version_id] == ["AWSPREVIOUS"]

    secret = client.list_secrets(
        Filters=[{"Key": "name", "Values": [DEFAULT_SECRET_NAME]}]
    ).get("SecretList")[0]
    list_vtos = secret["SecretVersionsToStages"]
    assert list_vtos[current_version_id] == ["AWSCURRENT"]
    assert list_vtos[previous_version_id] == ["AWSPREVIOUS"]

    assert describe_vtos == list_vtos


@mock_secretsmanager
def test_update_secret_with_client_request_token():
    client = boto3.client("secretsmanager", region_name="us-west-2")
    secret_name = "test-secret"
    client_request_token = str(uuid4())

    client.create_secret(Name=secret_name, SecretString="first-secret")
    updated_secret = client.update_secret(
        SecretId=secret_name,
        SecretString="second-secret",
        ClientRequestToken=client_request_token,
    )
    assert client_request_token == updated_secret["VersionId"]
    updated_secret = client.update_secret(
        SecretId=secret_name, SecretString="third-secret"
    )
    assert client_request_token != updated_secret["VersionId"]
    invalid_request_token = "test-token"
    with pytest.raises(ParamValidationError) as pve:
        client.update_secret(
            SecretId=secret_name,
            SecretString="fourth-secret",
            ClientRequestToken=invalid_request_token,
        )
        assert pve.value.response["Error"]["Code"] == "InvalidParameterException"
        assert pve.value.response["Error"]["Message"] == (
            "ClientRequestToken must be 32-64 characters long."
        )