import json
import logging

from . import exceptions

logger = logging.getLogger(__name__)


class BaseJSONData(dict):
    """
        A dict in which you can access items as attributes

    >>> obj = JSONData(key=True)
    >>> obj['key'] is obj.key
    True
    """

    def __getattr__(self, key):
        if key in self:
            return self[key]

        raise AttributeError("%s has no property named %s." %
                             (self.__class__.__name__, key))

    def __delattr__(self, item):
        del self[item]

    def __setattr__(self, key, value):
        self[key] = value


class JSONData(BaseJSONData):
    """
        A dict that lets you get the full data of the tweet without having
        to check if the data is truncated
    """

    def __contains__(self, key):
        if key == 'text':
            return super().__contains__('text') or 'full_text' in self

        elif super().__contains__(key):
            return True

        if 'extended_tweet' in self.keys():
            return key in self.extended_tweet

        return False

    def __getitem__(self, key):
        if key == 'text' and 'full_text' in self.keys():
            return super().__getitem__('full_text')

        if key == 'extended_tweet':
            return super().__getitem__(key)

        if 'extended_tweet' in self.keys():
            if key in self.extended_tweet:
                return self.extended_tweet[key]

        return super().__getitem__(key)

    def get(self, key, default=None):
        # it seems like the get method still called another __getitem__
        # than that of the instance
        if key in self:
            return self[key]

        return default


class PeonyResponse:
    """
        Response objects

    In these object you can access the headers, the request, the url
    and the data
    getting an attribute/item of this object will get the corresponding
    attribute/item of the data

    >>> peonyresponse = PeonyResponse(
    ...     data=JSONData(key="test"), headers={},
    ...     url="http://google.com", request={}
    ... )
    >>> peonyresponse.key is peonyresponse.data.key  # returns True
    >>>
    >>> peonyresponse = PeonyResponse(
    ...     data=[JSONData(key="test"), JSONData(key=1)], headers={},
    ...     url="http://google.com", request={}
    ... )
    >>> # iterate over peonyresponse.data
    >>> for key in peonyresponse:
    ...     pass  # do whatever you want

    Parameters
    ----------
    data : JSONData, dict or list
        Data object
    headers : dict
        Headers of the response
    url : str
        URL of the request
    request : dict
        Requests arguments
    """

    def __init__(self, data, headers, url, request):
        super().__setattr__('data', data)
        super().__setattr__('headers', headers)
        super().__setattr__('url', url)
        super().__setattr__('request', request)

    def __getattr__(self, key):
        """ get attributes from the data """
        return getattr(self.data, key)

    def __getitem__(self, key):
        """ get items from the data """
        return self.data[key]

    def __contains__(self, item):
        return item in self.data

    def __iter__(self):
        """ iterate over the data """
        return iter(self.data)

    def __str__(self):
        """ use the string of the data """
        return str(self.data)

    def __repr__(self):
        """ use the representation of the data """
        return repr(self.data)

    def __len__(self):
        """ get the length of the data """
        return len(self.data)

    def __setitem__(self, key, value):
        self.data[key] = value
    __setattr__ = __setitem__

    def __delitem__(self, key):
        del self.data[key]
    __delattr__ = __delitem__


def loads(json_data, encoding="utf-8", **kwargs):
    """
        Custom loads function with an object_hook and automatic decoding

    Parameters
    ----------
    json_data : str
        The JSON data to decode
    *args
        Positional arguments, passed to :func:`json.loads`
    encoding : :obj:`str`, optional
        The encoding of the bytestring
    **kwargs
        Keyword arguments passed to :func:`json.loads`

    Returns
    -------
    :obj:`dict` or :obj:`list`
        Decoded json data
    """
    if isinstance(json_data, bytes):
        json_data = json_data.decode(encoding)

    return json.loads(json_data, object_hook=JSONData, **kwargs)


async def read(response, loads=loads, encoding=None):
    """
        read the data of the response

    Parameters
    ----------
    response : aiohttp.ClientResponse
        response
    loads : callable
        json loads function
    encoding : :obj:`str`, optional
        character encoding of the response, if set to None
        aiohttp should guess the right encoding

    Returns
    -------
    :obj:`bytes`, :obj:`str`, :obj:`dict` or :obj:`list`
        the data returned depends on the response
    """
    ctype = response.headers.get('Content-Type', "").lower()

    try:
        if "application/json" in ctype:
            logger.info("decoding data as json")
            return await response.json(encoding=encoding, loads=loads)

        if "text" in ctype:
            logger.info("decoding data as text")
            return await response.text(encoding=encoding)

    except (UnicodeDecodeError, json.JSONDecodeError) as exc:
        data = await response.read()
        raise exceptions.PeonyDecodeError(response=response,
                                          data=data,
                                          exception=exc)

    return await response.read()