#!/usr/env/bin python

'''
Gitea API wrapper for Python.
API doc: https://try.gitea.io/api/swagger
'''

import parse
import requests

from pygitea.resources import resources


class PygiteaRequestException(Exception):
    pass


class API(object):
    '''
    Gitea API wrapper.
    '''

    _api_baseroute = '/api/v1'

    def __init__(self, baseuri, token=None):
        if baseuri.endswith('/'):
            baseuri = baseuri[0:-1]

        self._baseuri = ''.join([baseuri, self._api_baseroute])
        self._token = token

    # Aliases to `call`, this should be prefered entry-points
    def post(self, path, params=None):
        return self.call(path, method='post', params=params)

    def get(self, path, params=None):
        return self.call(path, method='get', params=params)

    def delete(self, path, params=None):
        return self.call(path, method='delete', params=params)

    def patch(self, path, params=None):
        return self.call(path, method='patch', params=params)

    def put(self, path, params=None):
        return self.call(path, method='put', params=params)


    def call(self, path, method, params=None):
        '''
        Compute, check and execute request to API.
        Returns requests.Response object.
        '''
        # Handle parameters
        if params is None:
            params = {}
        method = method.lower()
        resource = self.get_resource(path).copy()

        # Check if request needs auth token
        if path.split('/')[1] == 'admin' and self._token is None:
            raise PygiteaRequestException(
                'Resource \'{}\' require an authentification token.'.format(resource['path'])
            )
        else:
            params['token'] = self._token

        # Check if `resource` expect to be called with `method` HTTP method
        if not self._resource_has_method(resource, method):
            raise PygiteaRequestException('Resource \'{}\' did not expect method {}'.format(
                resource['path'],
                method.upper()
            ))

        # Check if all required parameters are given
        required_params = self.clean_resource_params(resource['path'], resource[method]['parameters'])
        for key in required_params:
            if key not in params.keys():
                raise PygiteaRequestException('Resource \'{}\' with method {} expect parameter \'{}\''.format(
                    resource['path'],
                    method.upper(),
                    key
                ))

        func = getattr(requests, method)
        final_uri = ''.join([self._baseuri, path])
        return func(final_uri, params=params)


    def clean_resource_params(self, resource_path, params):
        '''
        Remove params in resource hash already given in resource's uri.
        Example:
            /foo/{bar}/{baz} expect `bar` and `baz`, but since they are in uri we don't want
            to check that again.

        Returns cleaned params hash
        '''
        to_rm = []
        for name in params:
            if '{{{}}}'.format(name) in resource_path:
                to_rm.append(name)

        for key in to_rm:
            params.pop(key, None)

        return params


    def get_resource(self, path):
        '''
        Returns resource's hash from URI (path)
        Augment resource's hash with non-formated path for convenience.
        Returns augmented resource hash.
        '''
        key = self._get_resource_path(path)
        if key is None:
            raise PygiteaRequestException('Path \'{}\' did not match with any resource'.format(path))

        resource = resources[key].copy()
        resource['path'] = key
        return resource


    def _resource_has_method(self, resource, method):
        '''
        Check if `resource` (path to resource) accept `method` (HTTP method).
        '''
        if method not in resource.keys():
            return False
        return True

    def _get_resource_path(self, path):
        '''
        Get raw resource's path (/foo/{bar}) from formatted one (/foo/bar).
        '''
        # Two or more routes can match with `path`
        # The one with more characters is the good one.
        # I guess?..
        possibilities = []
        for method in resources:
            compiled = parse.compile(method)
            reparsed = compiled.parse(path)
            if reparsed is not None:
                possibilities.append(method)

        if len(possibilities) == 0:
            return None

        # Return only best possibility
        return sorted(possibilities, key=len)[-1]

