from moto.core.exceptions import RESTError

ERROR_WITH_BUCKET_NAME = """{% extends 'single_error' %}
{% block extra %}<BucketName>{{ bucket }}</BucketName>{% endblock %}
"""

ERROR_WITH_KEY_NAME = """{% extends 'single_error' %}
{% block extra %}<Key>{{ key }}</Key>{% endblock %}
"""

ERROR_WITH_ARGUMENT = """{% extends 'single_error' %}
{% block extra %}<ArgumentName>{{ name }}</ArgumentName>
<ArgumentValue>{{ value }}</ArgumentValue>{% endblock %}
"""

ERROR_WITH_UPLOADID = """{% extends 'single_error' %}
{% block extra %}<UploadId>{{ upload_id }}</UploadId>{% endblock %}
"""

ERROR_WITH_CONDITION_NAME = """{% extends 'single_error' %}
{% block extra %}<Condition>{{ condition }}</Condition>{% endblock %}
"""

ERROR_WITH_RANGE = """{% extends 'single_error' %}
{% block extra %}<ActualObjectSize>{{ actual_size }}</ActualObjectSize>
<RangeRequested>{{ range_requested }}</RangeRequested>{% endblock %}
"""

ERROR_WITH_STORAGE_CLASS = """{% extends 'single_error' %}
{% block extra %}<StorageClass>{{ storage_class }}</StorageClass>{% endblock %}
"""


class S3ClientError(RESTError):
    # S3 API uses <RequestID> as the XML tag in response messages
    request_id_tag_name = "RequestID"

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("template", "single_error")
        self.templates["bucket_error"] = ERROR_WITH_BUCKET_NAME
        super().__init__(*args, **kwargs)


class InvalidArgumentError(S3ClientError):
    code = 400

    def __init__(self, message, name, value, *args, **kwargs):
        kwargs.setdefault("template", "argument_error")
        kwargs["name"] = name
        kwargs["value"] = value
        self.templates["argument_error"] = ERROR_WITH_ARGUMENT
        super().__init__("InvalidArgument", message, *args, **kwargs)


class BucketError(S3ClientError):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault("template", "bucket_error")
        self.templates["bucket_error"] = ERROR_WITH_BUCKET_NAME
        super().__init__(*args, **kwargs)


class BucketAlreadyExists(BucketError):
    code = 409

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("template", "bucket_error")
        self.templates["bucket_error"] = ERROR_WITH_BUCKET_NAME
        super().__init__(
            "BucketAlreadyExists",
            (
                "The requested bucket name is not available. The bucket "
                "namespace is shared by all users of the system. Please "
                "select a different name and try again"
            ),
            *args,
            **kwargs,
        )


class MissingBucket(BucketError):
    code = 404

    def __init__(self, *args, **kwargs):
        super().__init__(
            "NoSuchBucket", "The specified bucket does not exist", *args, **kwargs
        )


class MissingKey(S3ClientError):
    code = 404

    def __init__(self, **kwargs):
        kwargs.setdefault("template", "key_error")
        self.templates["key_error"] = ERROR_WITH_KEY_NAME
        super().__init__("NoSuchKey", "The specified key does not exist.", **kwargs)


class MissingVersion(S3ClientError):
    code = 404

    def __init__(self, *args, **kwargs):
        super().__init__(
            "NoSuchVersion", "The specified version does not exist.", *args, **kwargs
        )


class InvalidVersion(S3ClientError):
    code = 400

    def __init__(self, version_id, *args, **kwargs):
        kwargs.setdefault("template", "argument_error")
        kwargs["name"] = "versionId"
        kwargs["value"] = version_id
        self.templates["argument_error"] = ERROR_WITH_ARGUMENT
        super().__init__(
            "InvalidArgument", "Invalid version id specified", *args, **kwargs
        )


class ObjectNotInActiveTierError(S3ClientError):
    code = 403

    def __init__(self, key_name):
        super().__init__(
            "ObjectNotInActiveTierError",
            "The source object of the COPY operation is not in the active tier and is only stored in Amazon Glacier.",
            Key=key_name,
        )


class InvalidPartOrder(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidPartOrder",
            (
                "The list of parts was not in ascending order. The parts "
                "list must be specified in order by part number."
            ),
            *args,
            **kwargs,
        )


class InvalidPart(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidPart",
            (
                "One or more of the specified parts could not be found. "
                "The part might not have been uploaded, or the specified "
                "entity tag might not have matched the part's entity tag."
            ),
            *args,
            **kwargs,
        )


class EntityTooSmall(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "EntityTooSmall",
            "Your proposed upload is smaller than the minimum allowed object size.",
            *args,
            **kwargs,
        )


class InvalidRequest(S3ClientError):
    code = 400

    def __init__(self, method, *args, **kwargs):
        super().__init__(
            "InvalidRequest",
            "Found unsupported HTTP method in CORS config. Unsupported method is {}".format(
                method
            ),
            *args,
            **kwargs,
        )


class IllegalLocationConstraintException(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "IllegalLocationConstraintException",
            "The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.",
            *args,
            **kwargs,
        )


class MalformedXML(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "MalformedXML",
            "The XML you provided was not well-formed or did not validate against our published schema",
            *args,
            **kwargs,
        )


class MalformedACLError(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "MalformedACLError",
            "The XML you provided was not well-formed or did not validate against our published schema",
            *args,
            **kwargs,
        )


class InvalidTargetBucketForLogging(S3ClientError):
    code = 400

    def __init__(self, msg):
        super().__init__("InvalidTargetBucketForLogging", msg)


class CrossLocationLoggingProhibitted(S3ClientError):
    code = 403

    def __init__(self):
        super().__init__(
            "CrossLocationLoggingProhibitted", "Cross S3 location logging not allowed."
        )


class InvalidMaxPartArgument(S3ClientError):
    code = 400

    def __init__(self, arg, min_val, max_val):
        error = "Argument {} must be an integer between {} and {}".format(
            arg, min_val, max_val
        )
        super().__init__("InvalidArgument", error)


class InvalidMaxPartNumberArgument(InvalidArgumentError):
    code = 400

    def __init__(self, value, *args, **kwargs):
        error = "Part number must be an integer between 1 and 10000, inclusive"
        super().__init__(message=error, name="partNumber", value=value, *args, **kwargs)


class NotAnIntegerException(InvalidArgumentError):
    code = 400

    def __init__(self, name, value, *args, **kwargs):
        error = f"Provided {name} not an integer or within integer range"
        super().__init__(message=error, name=name, value=value, *args, **kwargs)


class InvalidNotificationARN(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidArgument", "The ARN is not well formed", *args, **kwargs
        )


class InvalidNotificationDestination(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidArgument",
            "The notification destination service region is not valid for the bucket location constraint",
            *args,
            **kwargs,
        )


class InvalidNotificationEvent(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidArgument",
            "The event is not supported for notifications",
            *args,
            **kwargs,
        )


class InvalidStorageClass(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidStorageClass",
            "The storage class you specified is not valid",
            *args,
            **kwargs,
        )


class InvalidBucketName(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidBucketName", "The specified bucket is not valid.", *args, **kwargs
        )


class DuplicateTagKeys(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidTag",
            "Cannot provide multiple Tags with the same key",
            *args,
            **kwargs,
        )


class S3AccessDeniedError(S3ClientError):
    code = 403

    def __init__(self, *args, **kwargs):
        super().__init__("AccessDenied", "Access Denied", *args, **kwargs)


class BucketAccessDeniedError(BucketError):
    code = 403

    def __init__(self, *args, **kwargs):
        super().__init__("AccessDenied", "Access Denied", *args, **kwargs)


class S3InvalidTokenError(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidToken",
            "The provided token is malformed or otherwise invalid.",
            *args,
            **kwargs,
        )


class S3AclAndGrantError(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidRequest",
            "Specifying both Canned ACLs and Header Grants is not allowed",
            *args,
            **kwargs,
        )


class BucketInvalidTokenError(BucketError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidToken",
            "The provided token is malformed or otherwise invalid.",
            *args,
            **kwargs,
        )


class S3InvalidAccessKeyIdError(S3ClientError):
    code = 403

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidAccessKeyId",
            "The AWS Access Key Id you provided does not exist in our records.",
            *args,
            **kwargs,
        )


class BucketInvalidAccessKeyIdError(S3ClientError):
    code = 403

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidAccessKeyId",
            "The AWS Access Key Id you provided does not exist in our records.",
            *args,
            **kwargs,
        )


class S3SignatureDoesNotMatchError(S3ClientError):
    code = 403

    def __init__(self, *args, **kwargs):
        super().__init__(
            "SignatureDoesNotMatch",
            "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
            *args,
            **kwargs,
        )


class BucketSignatureDoesNotMatchError(S3ClientError):
    code = 403

    def __init__(self, *args, **kwargs):
        super().__init__(
            "SignatureDoesNotMatch",
            "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
            *args,
            **kwargs,
        )


class NoSuchPublicAccessBlockConfiguration(S3ClientError):
    code = 404

    def __init__(self, *args, **kwargs):
        super().__init__(
            "NoSuchPublicAccessBlockConfiguration",
            "The public access block configuration was not found",
            *args,
            **kwargs,
        )


class InvalidPublicAccessBlockConfiguration(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidRequest",
            "Must specify at least one configuration.",
            *args,
            **kwargs,
        )


class WrongPublicAccessBlockAccountIdError(S3ClientError):
    code = 403

    def __init__(self):
        super().__init__("AccessDenied", "Access Denied")


class NoSystemTags(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__(
            "InvalidTag", "System tags cannot be added/updated by requester"
        )


class NoSuchUpload(S3ClientError):
    code = 404

    def __init__(self, upload_id, *args, **kwargs):
        kwargs.setdefault("template", "error_uploadid")
        kwargs["upload_id"] = upload_id
        self.templates["error_uploadid"] = ERROR_WITH_UPLOADID
        super().__init__(
            "NoSuchUpload",
            "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
            *args,
            **kwargs,
        )


class PreconditionFailed(S3ClientError):
    code = 412

    def __init__(self, failed_condition, **kwargs):
        kwargs.setdefault("template", "condition_error")
        self.templates["condition_error"] = ERROR_WITH_CONDITION_NAME
        super().__init__(
            "PreconditionFailed",
            "At least one of the pre-conditions you specified did not hold",
            condition=failed_condition,
            **kwargs,
        )


class InvalidRange(S3ClientError):
    code = 416

    def __init__(self, range_requested, actual_size, **kwargs):
        kwargs.setdefault("template", "range_error")
        self.templates["range_error"] = ERROR_WITH_RANGE
        super().__init__(
            "InvalidRange",
            "The requested range is not satisfiable",
            range_requested=range_requested,
            actual_size=actual_size,
            **kwargs,
        )


class InvalidContinuationToken(S3ClientError):
    code = 400

    def __init__(self, *args, **kwargs):
        super().__init__(
            "InvalidArgument",
            "The continuation token provided is incorrect",
            *args,
            **kwargs,
        )


class InvalidObjectState(BucketError):
    code = 400

    def __init__(self, storage_class, **kwargs):
        kwargs.setdefault("template", "storage_error")
        self.templates["storage_error"] = ERROR_WITH_STORAGE_CLASS
        super().__init__(
            error_type="InvalidObjectState",
            message="The operation is not valid for the object's storage class",
            storage_class=storage_class,
            **kwargs,
        )


class LockNotEnabled(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__("InvalidRequest", "Bucket is missing ObjectLockConfiguration")


class AccessDeniedByLock(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__("AccessDenied", "Access Denied")


class InvalidContentMD5(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__("InvalidContentMD5", "Content MD5 header is invalid")


class BucketNeedsToBeNew(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__("InvalidBucket", "Bucket needs to be empty")


class BucketMustHaveLockeEnabled(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__(
            "InvalidBucketState",
            "Object Lock configuration cannot be enabled on existing buckets",
        )


class CopyObjectMustChangeSomething(S3ClientError):
    code = 400

    def __init__(self):
        super().__init__(
            "InvalidRequest",
            "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.",
        )


class InvalidFilterRuleName(InvalidArgumentError):
    code = 400

    def __init__(self, value, *args, **kwargs):
        super().__init__(
            "filter rule name must be either prefix or suffix",
            "FilterRule.Name",
            value,
            *args,
            **kwargs,
        )


class InvalidTagError(S3ClientError):
    code = 400

    def __init__(self, value, *args, **kwargs):
        super().__init__(
            "InvalidTag", value, *args, **kwargs,
        )


class ObjectLockConfigurationNotFoundError(S3ClientError):
    code = 404

    def __init__(self):
        super().__init__(
            "ObjectLockConfigurationNotFoundError",
            "Object Lock configuration does not exist for this bucket",
        )
