from botocore.handlers import (
    ETree,
    XMLParseError,
    _get_cross_region_presigned_url,
    _get_presigned_url_source_and_destination_regions,
    logger,
)


async def check_for_200_error(response, **kwargs):
    # From: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html
    # There are two opportunities for a copy request to return an error. One
    # can occur when Amazon S3 receives the copy request and the other can
    # occur while Amazon S3 is copying the files. If the error occurs before
    # the copy operation starts, you receive a standard Amazon S3 error. If the
    # error occurs during the copy operation, the error response is embedded in
    # the 200 OK response. This means that a 200 OK response can contain either
    # a success or an error. Make sure to design your application to parse the
    # contents of the response and handle it appropriately.
    #
    # So this handler checks for this case.  Even though the server sends a
    # 200 response, conceptually this should be handled exactly like a
    # 500 response (with respect to raising exceptions, retries, etc.)
    # We're connected *before* all the other retry logic handlers, so as long
    # as we switch the error code to 500, we'll retry the error as expected.
    if response is None:
        # A None response can happen if an exception is raised while
        # trying to retrieve the response.  See Endpoint._get_response().
        return
    http_response, parsed = response
    if await _looks_like_special_case_error(http_response):
        logger.debug(
            "Error found for response with 200 status code, "
            "errors: %s, changing status code to "
            "500.",
            parsed,
        )
        http_response.status_code = 500


async def _looks_like_special_case_error(http_response):
    if http_response.status_code == 200:
        try:
            parser = ETree.XMLParser(
                target=ETree.TreeBuilder(), encoding='utf-8'
            )
            parser.feed(await http_response.content)
            root = parser.close()
        except XMLParseError:
            # In cases of network disruptions, we may end up with a partial
            # streamed response from S3. We need to treat these cases as
            # 500 Service Errors and try again.
            return True
        if root.tag == 'Error':
            return True
    return False


async def inject_presigned_url_ec2(params, request_signer, model, **kwargs):
    # The customer can still provide this, so we should pass if they do.
    if 'PresignedUrl' in params['body']:
        return
    src, dest = _get_presigned_url_source_and_destination_regions(
        request_signer, params['body']
    )
    url = await _get_cross_region_presigned_url(
        request_signer, params, model, src, dest
    )
    params['body']['PresignedUrl'] = url
    # EC2 Requires that the destination region be sent over the wire in
    # addition to the source region.
    params['body']['DestinationRegion'] = dest


async def inject_presigned_url_rds(params, request_signer, model, **kwargs):
    # SourceRegion is not required for RDS operations, so it's possible that
    # it isn't set. In that case it's probably a local copy so we don't need
    # to do anything else.
    if 'SourceRegion' not in params['body']:
        return

    src, dest = _get_presigned_url_source_and_destination_regions(
        request_signer, params['body']
    )

    # Since SourceRegion isn't actually modeled for RDS, it needs to be
    # removed from the request params before we send the actual request.
    del params['body']['SourceRegion']

    if 'PreSignedUrl' in params['body']:
        return

    url = await _get_cross_region_presigned_url(
        request_signer, params, model, src, dest
    )
    params['body']['PreSignedUrl'] = url


async def parse_get_bucket_location(parsed, http_response, **kwargs):
    # s3.GetBucketLocation cannot be modeled properly.  To
    # account for this we just manually parse the XML document.
    # The "parsed" passed in only has the ResponseMetadata
    # filled out.  This handler will fill in the LocationConstraint
    # value.
    if http_response.raw is None:
        return
    response_body = await http_response.content
    parser = ETree.XMLParser(target=ETree.TreeBuilder(), encoding='utf-8')
    parser.feed(response_body)
    root = parser.close()
    region = root.text
    parsed['LocationConstraint'] = region
