import concurrent.futures  # noqa
from functools import partial
from six import add_metaclass
from typing import Any, Dict, Type

from requests_futures.sessions import FuturesSession

from popget.conf import settings
from popget.client import (
    APIClient as BaseAPIClient,
    APIClientMetaclass,
    ConfigClass as BaseConfigClass,
    method_factory,
)
from popget.endpoint import APIEndpoint
from popget.extratypes import ResponseTypes  # noqa
from popget.utils import classproperty


class ConfigClass(BaseConfigClass):

    _async_session = None  # type: FuturesSession

    async_method_template = 'async_{}'

    def __init__(self, config):
        self.async_method_template = getattr(config, 'async_method_template', self.async_method_template)
        super(ConfigClass, self).__init__(config)

    @classproperty
    def async_session(cls):
        # type: () -> FuturesSession
        if not cls._async_session:
            cls._async_session = FuturesSession(session=cls.session)
        return cls._async_session


class AsyncAPIClientMetaclass(APIClientMetaclass):

    config_class = ConfigClass

    @staticmethod
    def add_methods_for_endpoint(methods, name, endpoint, config):
        # type: (Dict[str, classmethod], str, APIEndpoint, Any) -> None
        methods[name] = classmethod(method_factory(endpoint, '_make_request'))
        methods[config.async_method_template.format(name)] = classmethod(
            method_factory(endpoint, '_make_async_request')
        )


@add_metaclass(AsyncAPIClientMetaclass)
class APIClient(BaseAPIClient):

    _config = None  # type: ConfigClass

    @classmethod
    def _make_async_request(cls, method, url, *args, **kwargs):
        # type: (str, str, *Any, **Any) -> concurrent.futures.Future
        """
        Don't call this directly. Instead, add APIEndpoint instances to your
        APIClient sub-class definition. Accessor methods will be generated by
        the APIClientMetaclass magic.

        Kwargs:
            method: name of a method on the `requests` lib, such as 'get'
            url: url of endpoint to call, with all params substituted in
            *args
            **kargs
                passed through to underlying `requests` method

        Returns:
            Future object. Use the standard concurrent.futures API to await
            the result.
        """
        cls._request_kwargs(method, url, args, kwargs)

        call = partial(getattr(cls._config.session, method), url, *args, **kwargs)
        return cls._config.async_session.executor.submit(cls.handle, call, url)
