from __future__ import absolute_import
import os
import requests
import requests.adapters
import six
import logging
import munch
import warnings


LOG = logging.getLogger(__name__)


# A const to define an optional argument that was not set
# and will be removed from the request.
NOT_SET = object()


def url_join(*args):
    return '/'.join(arg.strip('/') for arg in args)


{{file_class}}

class {{ class_name }}(object):

    ROUTE_CLI_COMMAND_MAP = {'login': False, 'logout': False, 'refresh': False}

    def __init__(self, hostname=None, port=None, url={% if default_url %}'{{ default_url }}'{% else %}''{% endif %}, token=None, timeout=None, headers=None, retries=None, session=None):
        """
        :param url: url to connect to
        :param token: a token to be used for requests
        :param timeout: timeout for a request
        :param headers: custom headers to send with each request
        :param retries: max-retries as an int, or a urllib3.Retry object, 
        :param requests.Session session: an external request session to use
        """
        self._url = url
        if hostname and port:
            warnings.warn('using arguments hostname and port in client.__init__ is deprecated, use url instead', UserWarning)
            self._url = "http://%s:%d" % (hostname, port)
        self._client = requests.Session() if not session else session
        self._client.mount(self._url, requests.adapters.HTTPAdapter(max_retries=retries))
        self._timeout = timeout
        self._set_token(token)
        self._client.headers.update(headers or {})
{%- for resource_name, resource_class, resource_path in resources_names %}
        {% if resource_name %}self.{{ resource_name }} = self.{{ resource_class }}(self._fetch, '{{ resource_path }}'){% endif %}
{%- endfor %}

    def close(self):
        self._client.close()

    def _fetch(self, method, url, json=None, file_stream=None, success_code=200, kwargs=None, url_kw=None, headers=None):
        eventually_close_file_object = None
        url_kw = url_kw or {}
        url = url_join(self._url, url).format(**url_kw)
        if json and not isinstance(json, list):
            json.update(kwargs or {})
            json = {key: value for key, value in six.iteritems(json) if value != NOT_SET}
        _kwargs = {
            "timeout": self._timeout,
            "stream": True,
        }
        if method.upper() not in {{ url_params_methods }}:
            if file_stream:
                if hasattr(file_stream, 'stream') and hasattr(file_stream, 'content_length'):
                    stream = File(file_stream.stream, file_stream.content_length)
                else:
                    if isinstance(file_stream, six.string_types) and not file_stream.startswith('http'):
                        file_stream = open(file_stream, 'rb')
                        eventually_close_file_object = file_stream
                    if hasattr(file_stream, 'read'):
                        if hasattr(file_stream, 'tell') and hasattr(file_stream, 'seek'):
                            file_stream.seek(0, os.SEEK_END)
                            content_length = file_stream.tell()
                            file_stream.seek(0, os.SEEK_SET)
                        elif hasattr(file_stream, 'name'):
                            content_length = os.stat(file_stream).st_size
                        else:
                            raise RuntimeError('File stream does not have required attributes.')
                        stream = File(file_stream, content_length)
                    elif isinstance(file_stream, six.string_types) and file_stream.startswith('http'):
                        http_request = requests.get(file_stream, stream=True)
                        stream = File(http_request.raw, http_request.headers['content-length'])
                        eventually_close_file_object = http_request
                    else:
                        raise RuntimeError('File stream should be a file object or a path')
                _kwargs["data"] = stream
                _kwargs["headers"] = {"content-type": "{{ type_octet_stream }}"}
                if json:
                    _kwargs["params"] = json
            else:
                if json:
                    _kwargs["json"] = json
        else:
            _kwargs["params"] = json
        if headers:
            _kwargs.setdefault('headers', {}).update(headers)
        LOG.debug("Sending %s request: %s %s", method, url, json)
        try:
            response = getattr(self._client, method.lower())(url, **_kwargs)
            result = None
            if '{{ type_json }}' in response.headers.get('content-type', ''):
                result = self._jsonify(response)
                LOG.debug("Got response status %s, body: %s", response.status_code, result)
            elif '{{ type_octet_stream }}' in response.headers.get('content-type', ''):
                result = response.raw
            else:
                result = response.text
            if response.status_code != success_code:
                LOG.debug(
                    "Response status %d does not match expected success status %d",
                    response.status_code, success_code
                )
            response.raise_for_status()
            return result
        finally:
            if eventually_close_file_object:
                eventually_close_file_object.close()

    @property
    def token(self):
        return self._client.headers.get("{{ token_entry }}")

    def login(self, username, password, tenant):
        """
        Login into the system
        :param username: name to login with
        :param password: your secret password
        :param tenant: The tenant you belong to
        :return dict: Login information.
        """
        self._check_auth()
        token = self.auth.login(username, password, tenant)
        self._set_token(token.token)
        return token

    def logout(self, token=None):
        """
        Logout from session
        :param token: A token to revoke, default is the HEADERS token.
        """
        self._check_auth()
        result = self.auth.logout(token)
        if not token:
            self._set_token(None)
        return result

    def refresh(self, token=None):
        """
        Refresh a login session
        :param token: A token to refresh, default is the HEADERS token.
        :return dict: A new login session information
        """
        self._check_auth()
        new_token = self.auth.refresh(token)
        if not token:
            self._set_token(new_token.token)
        return new_token

    def _check_auth(self):
        if not hasattr(self, 'auth'):
            raise AttributeError('No auth resource available...')

    def _set_token(self, token):
        if token:
            self._client.headers.update({"{{ token_entry }}": token})
        elif "{{ token_entry }}" in self._client.headers:
            del self._client.headers["{{ token_entry }}"]

    def _jsonify(self, response):
        try:
            result = response.json()
            if type(result) == dict:
                result = munch.Munch(result)
            elif type(result) == list:
                result = [item if type(item) != dict else munch.Munch(item) for item in result]
            return result
        except ValueError:
            if response.text:
                LOG.warning("Could not parse json from '%s'", response.text)

{{ resource_classes|join("") }}
if __name__ == '__main__':
    import sys
    import hammock.cli
    hammock.cli.App.prompt = '{} > '.format('{{ class_name }}')
    hammock.cli.App.IGNORE_COMMAND_NAME_FALSE = True
    {%- if default_url %}
    hammock.cli.App.DEFAULT_URL = '{{ default_url }}'
    {%- endif %}
    hammock.cli.App({{ class_name }}).run(sys.argv[1:])
