import uuid
import json
import os
import stat
from datetime import datetime
from os.path import join, isfile, isdir, exists

try:
    from urllib import quote_plus
except ImportError:
    from urllib.parse import quote_plus

import requests

from binstar_client.utils.config import TOKEN_DIRS


class TeamEditionAPI:
    """
    This is the interface for working with Anaconda Team Edition API.
    """
    DEFAULT_CONTENT_TYPE = {'Content-Type': 'application/json'}

    def __init__(self, base_url, config, verify_ssl=True):

        self._urls = {
            'repo': '{}/api/repo'.format(base_url),
            'login': '{}/api/auth/login'.format(base_url),
            'account': '{}/api/account'.format(base_url),
            'tokens': '{}/api/account/tokens'.format(base_url),
            'channels': '{}/api/channels'.format(base_url)
        }
        self._config = config
        self._verify_ssl = verify_ssl
        self.token = None
        self.domain = base_url

    def authenticate(self, username, password):
        """
        Login using direct grant and returns the JWT token.

        :param str username: The username to authenticate.
        :param str password: The password to authenticate.

        :return str: The dumped json string.
        """
        data = {
            'username': username,
            'password': password
        }
        s = requests.Session()
        resp = s.post(
            self._urls['login'],
            data=json.dumps(data),
            headers=self.DEFAULT_CONTENT_TYPE,
            verify=self._verify_ssl
        )

        if resp.status_code != 200:
            return json.dumps(resp.json())

        token = resp.json()
        token['jwt_token'] = token['token']
        token['jwt_token_refresh'] = token['refresh_token']

        return json.dumps(token)

    def logout(self):
        self.remove_token()

    def _get_team_edition_api_url(self):
        return 'jwt_{}'.format(self._config.get('main', 'team_edition_api_url'))

    def load_token(self):
        """
        Loads the JWT token to be used in further requests to authenticate user.

        :return str:
        """
        for token_dir in TOKEN_DIRS:
            url = self._get_team_edition_api_url()

            tokenfile = join(token_dir, '%s.token' % quote_plus(url))
            _is_file = exists(tokenfile)

            if _is_file:
                with open(tokenfile) as fd:
                    token = fd.read().strip()

                if token:
                    return token
                else:
                    os.unlink(tokenfile)

        return "{}"

    def store_binstar_token(self, token):
        """
        Stores the binstar token required to work with Team Edition
        through 'native' conda interface.

        :param str token: The string as a token.
        """
        for token_dir in TOKEN_DIRS:
            if not isdir(token_dir):
                os.makedirs(token_dir)
            tokenfile = join(token_dir, '%s.token' % quote_plus(self._urls['repo']))

            if isfile(tokenfile):
                os.unlink(tokenfile)

            with open(tokenfile, 'w') as fd:
                fd.write(token)
            os.chmod(tokenfile, stat.S_IWRITE | stat.S_IREAD)

    def store_token(self, token):
        """
        Stores the passed JWT token locally to be used in further
        user authentication.

        :param str token: The json data dumped in the string.
        """
        for token_dir in TOKEN_DIRS:
            url = self._get_team_edition_api_url()

            if not isdir(token_dir):
                os.makedirs(token_dir)
            tokenfile = join(token_dir, '%s.token' % quote_plus(url))

            if isfile(tokenfile):
                os.unlink(tokenfile)

            with open(tokenfile, 'w') as fd:
                fd.write(token)
            os.chmod(tokenfile, stat.S_IWRITE | stat.S_IREAD)

    def remove_token(self):
        """
        Removes the existing JWT token from local space.
        """
        url = self._get_team_edition_api_url()

        for token_dir in TOKEN_DIRS:
            tokenfile = join(token_dir, '%s.token' % quote_plus(url))
            if isfile(tokenfile):
                os.unlink(tokenfile)

            tokenfile = join(token_dir, '%s.token' % quote_plus(join(url, 'repo')))
            if isfile(tokenfile):
                os.unlink(tokenfile)

    def user(self, login=None):
        """
        Gets the user account info.

        :return dict[str, str]: The user data.
        """
        token = json.loads(self.load_token())

        s = requests.Session()
        resp = s.get(
            self._urls['account'],
            headers=self.DEFAULT_CONTENT_TYPE,
            cookies=token,
            verify=self._verify_ssl
        )

        data = resp.json()

        return {
            'login': data['username'],
        }

    def create_access_token(self, token):
        """
        Creates the access token (binstar) for the Team Edition API to provide
        access to channels listed in the repo.

        Returns the token and the token id.

        :param dict[str, str] token: The JWT token to be used for authentication.

        :return dict: The access token info.
        """
        request_data = {
            'name': 'navigator-token-{}-{}'.format(datetime.now().strftime('%Y-%m-%d'), uuid.uuid4()),
            'expires_at': '{}-12-31'.format(datetime.now().year),
            'scopes': [
                'channel:view',
                'channel:view-artifacts',
                'subchannel:view',
                'subchannel:view-artifacts',
                'artifact:view',
                'artifact:download'
            ]
        }

        s = requests.Session()
        resp = s.post(
            self._urls['tokens'],
            headers=self.DEFAULT_CONTENT_TYPE,
            data=json.dumps(request_data),
            cookies=token,
            verify=self._verify_ssl
        )

        response_data = resp.json()
        self.store_binstar_token(response_data.get('token'))

        return response_data

    def remove_access_token(self, access_token_id):
        """
        Deletes the created access token (binstar) from the Team Edition database.

        :param str access_token_id: The token ID to be removed.
        """
        token = json.loads(self.load_token())

        s = requests.Session()
        s.delete(
            '{}/{}'.format(self._urls['tokens'], access_token_id),
            headers=self.DEFAULT_CONTENT_TYPE,
            cookies=token,
            verify=self._verify_ssl
        )
