import asyncio
import hashlib
from dill.source import getsource
from itertools import chain

import pytest
from yarl import URL

from aiobotocore.endpoint import ClientResponseProxy

import aiohttp
from aiohttp.client import ClientResponse
import botocore
from botocore.args import ClientArgsCreator
from botocore.client import ClientCreator, BaseClient, Config
from botocore.endpoint import convert_to_response_dict, Endpoint, \
    EndpointCreator
from botocore.paginate import PageIterator, ResultKeyIterator
from botocore.session import Session, get_session
from botocore.waiter import NormalizedOperationMethod, Waiter, \
    create_waiter_with_client
from botocore.eventstream import EventStream
from botocore.parsers import ResponseParserFactory, PROTOCOL_PARSERS, \
    RestXMLParser, EC2QueryParser, QueryParser, JSONParser, RestJSONParser
from botocore.response import StreamingBody
from botocore.signers import RequestSigner, add_generate_presigned_url, \
    generate_presigned_url, S3PostPresigner, add_generate_presigned_post, \
    generate_presigned_post, generate_db_auth_token, add_generate_db_auth_token
from botocore.hooks import EventAliaser, HierarchicalEmitter
from botocore.utils import ContainerMetadataFetcher, IMDSFetcher, \
    InstanceMetadataFetcher
from botocore.credentials import Credentials, RefreshableCredentials, \
    CachedCredentialFetcher, AssumeRoleCredentialFetcher, EnvProvider, \
    ContainerProvider, InstanceMetadataProvider, ProfileProviderBuilder, \
    ConfigProvider, SharedCredentialProvider, ProcessProvider, CredentialResolver, \
    AssumeRoleWithWebIdentityProvider, AssumeRoleProvider, \
    CanonicalNameCredentialSourcer, BotoProvider, OriginalEC2Provider, \
    create_credential_resolver, get_credentials, create_mfa_serial_refresher, \
    AssumeRoleWithWebIdentityCredentialFetcher

# This file ensures that our private patches will work going forward.  If a
# method gets updated this will assert and someone will need to validate:
# 1) If our code needs to be updated
# 2) If our minimum botocore version needs to be updated
# 3) If we need to replace the below hash (not backwards compatible) or add
#    to the set

# The follow is for our monkeypatches for read_timeout:
#    github.com/aio-libs/aiobotocore/pull/248
_AIOHTTP_DIGESTS = {
    # for using _body
    ClientResponse: {'e178726065b609c69a1c02e8bb78f22efce90792'},
}

# These are guards to our main patches

# !!! README: HOW TO UPDATE THESE !!!
# -----------------------------------
# (tests break with new version of aiohttp/botocore)
#
# 1) Adding support for more versions of aiohttp/botocore
#    In this scenario you need to ensure that aiobotocore supports the changes
#    that broke these tests along with the old versions of the libraries
#    and APPEND to the set of hashes that we support for each object you
#    validated.
# 2) Bumping up the base version of aiohttp/botocore that we support
#    In this scenario ensure aiobotocore supports the new version of the libs
#    and REPLACE all entries with the current hashes with the new libs.

# REPLACE = backwards incompatible change
# APPEND = officially supporting more versions of botocore/aiohttp

# If you're changing these, most likely need to update setup.py as well.
_API_DIGESTS = {
    # args.py
    ClientArgsCreator.get_client_args: {'e3a44e6f50159e8e31c3d76f5e8a1110dda495fa'},

    # client.py
    ClientCreator.create_client: {'ee63a3d60b5917879cb644c1b0aa3fe34538b915'},
    ClientCreator._create_client_class: {'5e493d069eedbf314e40e12a7886bbdbcf194335'},
    ClientCreator._get_client_args: {'555e1e41f93df7558c8305a60466681e3a267ef3'},

    BaseClient._make_api_call: {'0c59329d4c8a55b88250b512b5e69239c42246fb'},
    BaseClient._make_request: {'033a386f7d1025522bea7f2bbca85edc5c8aafd2'},
    BaseClient._convert_to_request_dict: {'0071c2a37c3c696d9b0fba5f54b2985489c76b78'},
    BaseClient._emit_api_params: {'2bfadaaa70671b63c50b1beed6d6c66e85813e9b'},
    BaseClient.get_paginator: {'c69885f5f73fae048c0b93b43bbfcd1f9c6168b8'},
    BaseClient.get_waiter: {'23d57598555bfbc4c6e7ec93406d05771f108d9e'},
    BaseClient.__getattr__: {'63f8ad095789d47880867f18537a277195845111'},

    # config.py
    Config.merge: {'c3dd8c3ffe0da86953ceba4a35267dfb79c6a2c8'},
    Config: {'2dcc44190a3dc2a4b26ab0ed9410daefcd7c93c1'},

    # credentials.py
    create_mfa_serial_refresher: {'180b81fc40c91d1cf40de1a28e32ae7d601e1d50'},
    Credentials.get_frozen_credentials: {'08af57df08ee9953e440aa7aca58137ed936cdb6'},
    RefreshableCredentials.__init__: {'c685fd2c62eb60096fdf8bb885fb642df1819f7f'},
    # We've overridden some properties
    RefreshableCredentials.__dict__['access_key'].fset:
        {'edc4a25baef877a9662f68cd9ccefcd33a81bab7'},
    RefreshableCredentials.__dict__['access_key'].fget:
        {'f6c823210099db99dd343d9e1fae6d4eb5aa5fce'},
    RefreshableCredentials.__dict__['secret_key'].fset:
        {'b19fe41d66822c72bd6ae2e60de5c5d27367868a'},
    RefreshableCredentials.__dict__['secret_key'].fget:
        {'3e27331a037549104b8669e225bbbb2c465a16d4'},
    RefreshableCredentials.__dict__['token'].fset:
        {'1f8a308d4bf21e666f8054a0546e91541661da7b'},
    RefreshableCredentials.__dict__['token'].fget:
        {'005c1b44b616f37739ce9276352e4e83644d8220'},
    RefreshableCredentials._refresh: {'f4759b7ef0d1f0d8af07855dcd9ca49ef12c2e7b'},
    RefreshableCredentials._protected_refresh:
        {'432409f81601dbeea9ec187d433d190ab7c5ab2f'},
    RefreshableCredentials.get_frozen_credentials:
        {'f661c84a8b759786e011f0b1e8a468a0c6294e36'},

    CachedCredentialFetcher._get_credentials:
        {'02a7d13599d972e3f258d2b53f87eeda4cc3e3a4'},
    CachedCredentialFetcher.fetch_credentials:
        {'0dd2986a4cbb38764ec747075306a33117e86c3d'},
    CachedCredentialFetcher._get_cached_credentials:
        {'a9f8c348d226e62122972da9ccc025365b6803d6'},
    AssumeRoleCredentialFetcher._get_credentials:
        {'5c575634bc0a713c10e5668f28fbfa8779d5a1da'},
    AssumeRoleCredentialFetcher._create_client:
        {'27c76f07bd43e665899ca8d21b6ba2038b276fbb'},
    # Referenced by AioAssumeRoleWithWebIdentityCredentialFetcher
    AssumeRoleWithWebIdentityCredentialFetcher.__init__:
        {'85c022a7237a3500ca973b2f7f91bffe894e4577'},
    AssumeRoleWithWebIdentityCredentialFetcher._get_credentials:
        {'02eba9d4e846474910cb076710070348e395a819'},
    AssumeRoleWithWebIdentityCredentialFetcher._assume_role_kwargs:
        {'8fb4fefe8664b7d82a67e0fd6d6812c1c8d92285'},
    # Ensure that the load method doesn't do anything we should asyncify
    EnvProvider.load: {'07cff5032b39b568505779774a1ca66efc513abb'},

    ContainerProvider.__init__: {'ea6aafb2e12730066af930fb5a27f7659c1736a1'},
    ContainerProvider.load: {'57c35569050b45c1e9e33fcdb3b49da9e342fdcf'},
    ContainerProvider._retrieve_or_fail:
        {'7c14f1cdee07217f847a71068866bdd10c3fa0fa'},
    ContainerProvider._create_fetcher:
        {'09a3ffded0fc20a574f3b34fa432a1569d5e729f'},
    InstanceMetadataProvider.load: {'4a27eb94fe220fba2b46c97bdd9e16de199ce004'},
    ProfileProviderBuilder._create_process_provider:
        {'c5eea47bcfc449a6d73a9892bd0e1897f6be0c20'},
    ProfileProviderBuilder._create_shared_credential_provider:
        {'33f99c6a0ef71a92b0c52ccc59c8ca7e33fa0890'},
    ProfileProviderBuilder._create_config_provider:
        {'f9a40d4211f6e663ba2ae9682fba5306152178c5'},
    ProfileProviderBuilder._create_web_identity_provider:
        {'0907c1ad5573bc5c0fc87efb601a6c4c3fcf34ae'},
    ConfigProvider.load: {'8fb32140086dce65fa28be8edd3ac0d22698c3ae'},
    SharedCredentialProvider.load: {'c0be1fe376d25952461ca18d9bef4b4340203441'},
    ProcessProvider.__init__: {'2e870ec0c6b0bc8483fa9b1159ef68bbd7a12c56'},
    ProcessProvider.load: {'aac90e2c8823939f09936b9c883e67503128e438'},
    ProcessProvider._retrieve_credentials_using:
        {'ffc27c7cba0e37cf6db3a3eacfd54be8bd99d3a9'},
    CredentialResolver.load_credentials:
        {'ef31ba8817f84c1f61f36259da1cc6e597b8625a'},
    AssumeRoleWithWebIdentityProvider.load:
        {'8f48f6cadf08a09cf5a22b1cc668e60bc4ea389d'},
    AssumeRoleWithWebIdentityProvider._assume_role_with_web_identity:
        {'32c9d720ab5f12054583758b5cd5d287f652ccd3'},
    AssumeRoleProvider.load: {'ee9ddb43e25eb1105185253c0963a2f5add49a95'},
    AssumeRoleProvider._load_creds_via_assume_role:
        {'9fdba45a8dd16b885dea7c1fafc7d02609870fa7'},
    AssumeRoleProvider._resolve_source_credentials:
        {'105c0c011e23d76a3b8bd3d9b91b6d945c8307a1'},
    AssumeRoleProvider._resolve_credentials_from_profile:
        {'402a1a6b3e0a29c234b7883e5b855110eb655830'},
    AssumeRoleProvider._resolve_static_credentials_from_profile:
        {'58f04986bb1027d548212b7769034e5dae5cc30f'},
    AssumeRoleProvider._resolve_credentials_from_source:
        {'6f76ae62f477279a2297565f80a5cfbe5ea30eaf'},
    CanonicalNameCredentialSourcer.source_credentials:
        {'602930a78e0e64e3b313a046aab5edc3bcf5c2d9'},
    CanonicalNameCredentialSourcer._get_provider:
        {'c028b9776383cc566be10999745b6082f458d902'},
    BotoProvider.load: {'9351b8565c2c969937963fc1d3fbc8b3b6d8ccc1'},
    OriginalEC2Provider.load: {'bde9af019f01acf3848a6eda125338b2c588c1ab'},
    create_credential_resolver: {'5ff7fe49d7636b795a50202ff5c089611f4e27c1'},
    get_credentials: {'ff0c735a388ac8dd7fe300a32c1e36cdf33c0f56'},

    # endpoint.py
    convert_to_response_dict: {'2c73c059fa63552115314b079ae8cbf5c4e78da0'},

    Endpoint.create_request: {'4ccc14de2fd52f5c60017e55ff8e5b78bbaabcec'},
    Endpoint._send_request: {'50ab33d6f16e75594d01ab1c2ec6b7c7903798db'},
    Endpoint._get_response: {'46c3a8cb4ff7672b75193ce5571dbea48aa9da75'},
    Endpoint._do_get_response: {'df29f099d26dc057834c7b25d3b5217f1f7acbe4'},
    Endpoint._needs_retry: {'0f40f52d8c90c6e10b4c9e1c4a5ca00ef2c72850'},
    Endpoint._send: {'644c7e5bb88fecaa0b2a204411f8c7e69cc90bf1'},

    EndpointCreator.create_endpoint: {'36065caa2398573be229bee500e27303bc362348'},

    # eventstream.py
    EventStream._create_raw_event_generator: {
        'cc101f3ca2bca4f14ccd6b385af900a15f96967b'},
    EventStream.__iter__: {'8a9b454943f8ef6e81f5794d641adddd1fdd5248'},

    # hooks.py
    HierarchicalEmitter._emit: {'5d9a6b1aea1323667a9310e707a9f0a006f8f6e8'},
    HierarchicalEmitter.emit_until_response:
        {'23670e04e0b09a9575c7533442bca1b2972ade82'},
    EventAliaser.emit_until_response: {'0d635bf7ae5022b1fdde891cd9a91cd4c449fd49'},

    # paginate.py
    PageIterator.__iter__: {'56b3a1e30f488e2f1f5d5309db42fd5ad8a3895d'},
    PageIterator.result_key_iters: {'04d3c647bd98caba3687df80e650fea517a0068e'},
    PageIterator.build_full_result: {'afe8cd8daad2cf32ae34f877985ab79501bf7742'},
    ResultKeyIterator: {'f71d98959ccda5e05e35cf3cf224fbc9310d33bb'},

    # parsers.py
    ResponseParserFactory.create_parser: {'5cf11c9acecd1f60a013f6facbe0f294daa3f390'},
    RestXMLParser._create_event_stream: {'0564ba55383a71cc1ba3e5be7110549d7e9992f5'},
    EC2QueryParser._create_event_stream: {'0564ba55383a71cc1ba3e5be7110549d7e9992f5'},
    QueryParser._create_event_stream: {'0564ba55383a71cc1ba3e5be7110549d7e9992f5'},
    JSONParser._create_event_stream: {'0564ba55383a71cc1ba3e5be7110549d7e9992f5'},
    RestJSONParser._create_event_stream: {'0564ba55383a71cc1ba3e5be7110549d7e9992f5'},

    # response.py
    StreamingBody: {'bb4d872649b0c118c9a3d5e44961e1bea92eb79c'},

    # session.py
    Session.__init__: {'ccf156a76beda3425fb54363f3b2718dc0445f6d'},
    Session._register_response_parser_factory:
        {'d6cd5a8b1b473b0ec3b71db5f621acfb12cc412c'},
    Session.create_client: {'36f4e718fc4bada66808c2f98fa71835c09076f7'},
    Session._create_credential_resolver: {'87e98d201c72d06f7fbdb4ebee2dce1c09de0fb2'},
    Session.get_credentials: {'c0de970743b6b9dd91b5a71031db8a495fde53e4'},
    get_session: {'c47d588f5da9b8bde81ccc26eaef3aee19ddd901'},
    Session.get_service_data: {'e28f2de9ebaf13214f1606d33349dfa8e2555923'},
    Session.get_service_model: {'1c8f93e6fb9913e859e43aea9bc2546edbea8365'},
    Session.get_available_regions: {'bc455d24d98fbc112ff22325ebfd12a6773cb7d4'},

    # signers.py
    RequestSigner.handler: {'371909df136a0964ef7469a63d25149176c2b442'},
    RequestSigner.sign: {'7df841d3df3f4015763523c1932652aef754287a'},
    RequestSigner.get_auth: {'4f8099bef30f9a72fa3bcaa1bd3d22c4fbd224a8'},
    RequestSigner.get_auth_instance: {'4f8099bef30f9a72fa3bcaa1bd3d22c4fbd224a8'},
    RequestSigner._choose_signer: {'d1e0e3196ada449d3ae0ec09b8ae9b5868c50d4e'},
    RequestSigner.generate_presigned_url: {'2acffdfd926b7b6f6cc4b70b90c0587e7f424888'},
    add_generate_presigned_url: {'5820f74ac46b004eb79e00eea1adc467bcf4defe'},
    generate_presigned_url: {'9c471f957210c0a71a11f5c73be9fed844ecb5bb'},
    S3PostPresigner.generate_presigned_post:
        {'b91d50bae4122d7ab540653865ec9294520ac0e1'},
    add_generate_presigned_post: {'e30360f2bd893fabf47f5cdb04b0de420ccd414d'},
    generate_presigned_post: {'85e9ebe0412cb10716bf84a1533798882f3fc79f'},
    add_generate_db_auth_token: {'f61014e6fac4b5c7ee7ac2d2bec15fb16fa9fbe5'},
    generate_db_auth_token: {'5f5a758458c007107a23124192339f747472dc75'},

    # utils.py
    ContainerMetadataFetcher.__init__:
        {'46d90a7249ba8389feb487779b0a02e6faa98e57'},
    ContainerMetadataFetcher.retrieve_full_uri:
        {'2c7080f7d6ee5a3dacc1b690945c045dba1b1d21'},
    ContainerMetadataFetcher.retrieve_uri:
        {'4ee8aa704cf0a378d68ef9a7b375a1aa8840b000'},
    ContainerMetadataFetcher._retrieve_credentials:
        {'f5294f9f811cb3cc370e4824ca106269ea1f44f9'},
    ContainerMetadataFetcher._get_response:
        {'7e5acdd2cf0167a047e3d5ee1439565a2f79f6a6'},
    # Overrided session and dealing with proxy support
    IMDSFetcher.__init__: {'690e37140ccdcd67c7a85ce5d36331491a79954e'},
    IMDSFetcher._get_request: {'96a0e580cab5a21deb4d2cd7e904aa17d5e1e504'},
    IMDSFetcher._fetch_metadata_token: {'4fdad673b4997b1268c6d9dff09a4b99c1cb5e0d'},

    InstanceMetadataFetcher.retrieve_iam_role_credentials:
        {'76737f6add82a1b9a0dc590cf10bfac0c7026a2e'},
    InstanceMetadataFetcher._get_iam_role: {'80073d7adc9fb604bc6235af87241f5efc296ad7'},
    InstanceMetadataFetcher._get_credentials:
        {'1a64f59a3ca70b83700bd14deeac25af14100d58'},

    # waiter.py
    NormalizedOperationMethod.__call__: {'79723632d023739aa19c8a899bc2b814b8ab12ff'},
    Waiter.wait: {'5502a89ed740fb5d6238a6f72a3a08efc1a9f43b'},
    create_waiter_with_client: {'c3d12c9a4293105cc8c2ecfc7e69a2152ad564de'},
}


_PROTOCOL_PARSER_CONTENT = {'ec2', 'query', 'json', 'rest-json', 'rest-xml'}


@pytest.mark.moto
def test_protocol_parsers():
    # Check that no new parsers have been added
    current_parsers = set(PROTOCOL_PARSERS.keys())
    assert current_parsers == _PROTOCOL_PARSER_CONTENT


# NOTE: this doesn't require moto but needs to be marked to run with coverage
@pytest.mark.moto
def test_patches():
    print("Botocore version: {} aiohttp version: {}".format(
        botocore.__version__, aiohttp.__version__))

    success = True
    for obj, digests in chain(_AIOHTTP_DIGESTS.items(), _API_DIGESTS.items()):
        digest = hashlib.sha1(getsource(obj).encode('utf-8')).hexdigest()
        if digest not in digests:
            print("Digest of {}:{} not found in: {}".format(
                obj.__qualname__, digest, digests))
            success = False

    assert success


# NOTE: this doesn't require moto but needs to be marked to run with coverage
@pytest.mark.moto
@pytest.mark.asyncio
async def test_set_status_code():
    resp = ClientResponseProxy(
        'GET', URL('http://foo/bar'),
        loop=asyncio.get_event_loop(),
        writer=None, continue100=None, timer=None,
        request_info=None,
        traces=None,
        session=None)
    resp.status_code = 500
    assert resp.status_code == 500