import copy

import botocore.parsers
import botocore.serialize
from botocore.args import ClientArgsCreator, EPRBuiltins

from .config import AioConfig
from .endpoint import DEFAULT_HTTP_SESSION_CLS, AioEndpointCreator
from .parsers import create_parser
from .regions import AioEndpointRulesetResolver
from .signers import AioRequestSigner


class AioClientArgsCreator(ClientArgsCreator):
    # NOTE: we override this so we can pull out the custom AioConfig params and
    #       use an AioEndpointCreator
    async def get_client_args(
        self,
        service_model,
        region_name,
        is_secure,
        endpoint_url,
        verify,
        credentials,
        scoped_config,
        client_config,
        endpoint_bridge,
        auth_token=None,
        endpoints_ruleset_data=None,
        partition_data=None,
    ):
        final_args = self.compute_client_args(
            service_model,
            client_config,
            endpoint_bridge,
            region_name,
            endpoint_url,
            is_secure,
            scoped_config,
        )

        service_name = final_args['service_name']  # noqa
        parameter_validation = final_args['parameter_validation']
        endpoint_config = final_args['endpoint_config']
        protocol = final_args['protocol']
        config_kwargs = final_args['config_kwargs']
        s3_config = final_args['s3_config']
        partition = endpoint_config['metadata'].get('partition', None)
        socket_options = final_args['socket_options']
        configured_endpoint_url = final_args['configured_endpoint_url']
        signing_region = endpoint_config['signing_region']
        endpoint_region_name = endpoint_config['region_name']
        account_id_endpoint_mode = config_kwargs['account_id_endpoint_mode']

        event_emitter = copy.copy(self._event_emitter)
        signer = AioRequestSigner(
            service_model.service_id,
            signing_region,
            endpoint_config['signing_name'],
            endpoint_config['signature_version'],
            credentials,
            event_emitter,
            auth_token,
        )

        config_kwargs['s3'] = s3_config

        # aiobotocore addition
        if isinstance(client_config, AioConfig):
            connector_args = client_config.connector_args
            http_session_cls = client_config.http_session_cls
        else:
            connector_args = None
            http_session_cls = DEFAULT_HTTP_SESSION_CLS

        new_config = AioConfig(connector_args, **config_kwargs)
        endpoint_creator = AioEndpointCreator(event_emitter)

        endpoint = endpoint_creator.create_endpoint(
            service_model,
            region_name=endpoint_region_name,
            endpoint_url=endpoint_config['endpoint_url'],
            verify=verify,
            response_parser_factory=self._response_parser_factory,
            timeout=(new_config.connect_timeout, new_config.read_timeout),
            max_pool_connections=new_config.max_pool_connections,
            http_session_cls=http_session_cls,
            proxies=new_config.proxies,
            socket_options=socket_options,
            client_cert=new_config.client_cert,
            proxies_config=new_config.proxies_config,
            connector_args=new_config.connector_args,
        )

        # Emit event to allow service-specific or customer customization of serializer kwargs
        event_name = f'creating-serializer.{service_name}'
        serializer_kwargs = {
            'timestamp_precision': botocore.serialize.TIMESTAMP_PRECISION_DEFAULT
        }
        await event_emitter.emit(
            event_name,
            protocol_name=protocol,
            service_model=service_model,
            serializer_kwargs=serializer_kwargs,
        )

        serializer = botocore.serialize.create_serializer(
            protocol,
            parameter_validation,
            timestamp_precision=serializer_kwargs['timestamp_precision'],
        )
        response_parser = create_parser(protocol)

        ruleset_resolver = self._build_endpoint_resolver(
            endpoints_ruleset_data,
            partition_data,
            client_config,
            service_model,
            endpoint_region_name,
            region_name,
            configured_endpoint_url,
            endpoint,
            is_secure,
            endpoint_bridge,
            event_emitter,
            credentials,
            account_id_endpoint_mode,
        )

        # Copy the session's user agent factory and adds client configuration.
        client_ua_creator = self._session_ua_creator.with_client_config(
            new_config
        )
        supplied_ua = client_config.user_agent if client_config else None
        new_config._supplied_user_agent = supplied_ua

        return {
            'serializer': serializer,
            'endpoint': endpoint,
            'response_parser': response_parser,
            'event_emitter': event_emitter,
            'request_signer': signer,
            'service_model': service_model,
            'loader': self._loader,
            'client_config': new_config,
            'partition': partition,
            'exceptions_factory': self._exceptions_factory,
            'endpoint_ruleset_resolver': ruleset_resolver,
            'user_agent_creator': client_ua_creator,
        }

    def _build_endpoint_resolver(
        self,
        endpoints_ruleset_data,
        partition_data,
        client_config,
        service_model,
        endpoint_region_name,
        region_name,
        endpoint_url,
        endpoint,
        is_secure,
        endpoint_bridge,
        event_emitter,
        credentials,
        account_id_endpoint_mode,
    ):
        if endpoints_ruleset_data is None:
            return None

        # The legacy EndpointResolver is global to the session, but
        # EndpointRulesetResolver is service-specific. Builtins for
        # EndpointRulesetResolver must not be derived from the legacy
        # endpoint resolver's output, including final_args, s3_config,
        # etc.
        s3_config_raw = self.compute_s3_config(client_config) or {}
        service_name_raw = service_model.endpoint_prefix
        # Maintain complex logic for s3 and sts endpoints for backwards
        # compatibility.
        if service_name_raw in ['s3', 'sts'] or region_name is None:
            eprv2_region_name = endpoint_region_name
        else:
            eprv2_region_name = region_name
        resolver_builtins = self.compute_endpoint_resolver_builtin_defaults(
            region_name=eprv2_region_name,
            service_name=service_name_raw,
            s3_config=s3_config_raw,
            endpoint_bridge=endpoint_bridge,
            client_endpoint_url=endpoint_url,
            legacy_endpoint_url=endpoint.host,
            credentials=credentials,
            account_id_endpoint_mode=account_id_endpoint_mode,
        )

        # replace with async version
        resolver_builtins[EPRBuiltins.ACCOUNT_ID] = (
            credentials.get_account_id if credentials else None
        )

        # Client context params for s3 conflict with the available settings
        # in the `s3` parameter on the `Config` object. If the same parameter
        # is set in both places, the value in the `s3` parameter takes priority.
        if client_config is not None:
            client_context = client_config.client_context_params or {}
        else:
            client_context = {}
        if self._is_s3_service(service_name_raw):
            client_context.update(s3_config_raw)

        sig_version = (
            client_config.signature_version
            if client_config is not None
            else None
        )
        return AioEndpointRulesetResolver(
            endpoint_ruleset_data=endpoints_ruleset_data,
            partition_data=partition_data,
            service_model=service_model,
            builtins=resolver_builtins,
            client_context=client_context,
            event_emitter=event_emitter,
            use_ssl=is_secure,
            requested_auth_scheme=sig_version,
        )
