'''
StackExchangeAPI wrapper.
Current version support:
    Site:
        -StackOverflow
    Methods:
        -Taking collection of all Posts data for:
            -user identified by user_id
            -user identified by OAuth2 access_token

Usage examples:
    posts = so.user(user_id).get_posts(sort='creation', order='asc')
    posts = so.me(access_token).get_posts(sort='activity', order='desc')

Exceptions:
    -Raises StackExchangeAPIException on taking data from API error

Verison: 1.0
Author: Artur Gutman
'''
from abc import ABC
import requests

SE_API_BASE_URL = 'https://api.stackexchange.com/2.2'
PAGESIZE = 100


class StackExchangeAPIException(Exception):
    '''Base class for StackExchangeAPIException'''
    pass


class StackExchangeAPI(ABC):
    '''Base class for all StackExchange compatible APIs'''
    def __init__(self, site):
        self.site = site


    def init_app(self, app):
        self.key = app.config['SE_KEY']


    def user(self, user_id):
        return self.User(self, user_id)


    def me(self, access_token):
        return self.Me(self, access_token)


    def get_items_collection(self, endpoint, custom_params={}):
        url = SE_API_BASE_URL + endpoint
        page = 0
        params = {
            'site': self.site,
            'key': self.key,
            'page': 0,
            'pagesize': PAGESIZE
        }
        params.update(custom_params)
        collection = []
        has_more = True
        while has_more:
            params['page'] += 1
            try:
                r = requests.get(url, params=params)
            except requests.exceptions.RequestException as e:
                raise StackExchangeAPIException(e)
            if r.status_code != requests.codes.ok:
                raise StackExchangeAPIException('{} returned {} status code'.format(url, r.status_code))
            try:
                data = r.json()
                if ('items' not in data) or ('has_more' not in data)\
                         or not isinstance(data['items'], list):
                    raise ValueError
                collection.extend(data['items'])
                has_more = data['has_more']
            except ValueError:
                raise StackExchangeAPIException('{} returned usupported payload'.format(url))
        return collection


    class BaseUser(ABC):
        '''Base abstract class for User and Me classes'''

        def __init__(self, stack_exchange_api):
            self.stack_exchange_api = stack_exchange_api
            self.custom_params = {}


        def get_posts(self, order='desc', sort='activity'):
            order_set = {'desc', 'asc'}
            sort_set = {'activity', 'creation', 'votes'}
            if (order not in order_set) or (sort not in sort_set):
                raise ValueError
            custom_params = {
                'order': order,
                'sort': sort
            }
            custom_params.update(self.custom_params)
            endpoint = self.endpoint_base + '/posts'
            return self.stack_exchange_api.get_items_collection(endpoint,
                custom_params=custom_params)


    class User(BaseUser):
        '''Class representing /users/{id} endpoint behaviour'''
        def __init__(self, stack_exchange_api, user_id):
            super().__init__(stack_exchange_api)
            if not isinstance(user_id, int):
                raise ValueError
            self.user_id = user_id
            self.endpoint_base = '/users/' + str(user_id)


    class Me(BaseUser):
        '''Class representing /me endpoint behaviour'''
        def __init__(self, stack_exchange_api, access_token):
            super().__init__(stack_exchange_api)
            self.endpoint_base = '/me'
            self.custom_params.update({
                'access_token': access_token
            })


class StackOverflowAPI(StackExchangeAPI):
    '''StackOverflow API interface'''
    def __init__(self, *arg, **kwargs):
        super().__init__(site='stackoverflow', *arg, **kwargs)


so = StackOverflowAPI()
