import json

import aiohttp

from gfypy.const import REDIRECT_URI
from gfypy.exceptions import GfypyAuthException, GfypyApiException
from gfypy.http.abstract_http import AbstractHttpClient
from gfypy.route import Route


class BearerAuth(aiohttp.BasicAuth):
    def __init__(self, token):
        self.token = token

    def encode(self):
        return "Bearer " + self.token


class AsyncHttpClient(AbstractHttpClient):
    def __init__(self, client_id, client_secret):
        self._client_id = client_id
        self._client_secret = client_secret
        self._session = aiohttp.ClientSession()
        self._auth = None
        self.creds = {
            'access_token': None,
            'expires_in': None,
            'refresh_token': None,
            'refresh_token_expires_in': None,
            'resource_owner': None
        }

    async def close(self):
        await self._session.close()

    async def request(self, route, **kwargs):
        no_auth = kwargs.pop('no_auth', False)
        refresh = kwargs.pop('refresh', True)

        async with self._session.request(route.method, route.url, **kwargs,
                                         auth=self._auth if not no_auth else None) as resp:
            content_type = resp.headers.get('content-type')
            if content_type == 'application/json':
                content = await resp.json()
            else:
                content = await resp.text()

            if 200 <= resp.status < 300:
                return content
            elif resp.status in [401, 403]:
                if refresh:
                    # try to refresh the oauth token in case it's become invalid
                    await self.refresh_oauth_token()

                    return await self.request(route, **kwargs, refresh=False)
                else:
                    if 'message' in content:
                        raise GfypyAuthException(content['message'], resp.status, None)
                    else:
                        raise GfypyAuthException(content['errorMessage']['description'], resp.status,
                                                 content['errorMessage']['code'])
            else:
                raise GfypyApiException(content['errorMessage'] if 'message' in content else content, resp.status)

    async def get_oauth_token(self, code):
        payload = {
            'code': code,
            'client_id': self._client_id,
            'client_secret': self._client_secret,
            'grant_type': 'authorization_code',
            'redirect_uri': REDIRECT_URI
        }

        resp = await self.request(Route('POST', '/oauth/token'), data=json.dumps(payload),
                                  headers={'content-type': 'application/json'})

        self.creds = resp
        self.auth = resp['access_token']

    async def refresh_oauth_token(self, **kwargs):
        payload = {
            'refresh_token': self.creds['refresh_token'],
            'client_id': self._client_id,
            'client_secret': self._client_secret,
            'grant_type': 'refresh',
        }

        resp = await self.request(Route('POST', '/oauth/token'), data=json.dumps(payload),
                                  headers={'content-type': 'application/json'}, **kwargs)

        self.creds = resp
        self.auth = resp['access_token']

    @property
    def auth(self):
        return self._auth

    @auth.setter
    def auth(self, token):
        self._auth = BearerAuth(token)
