import collections
import functools
import json
import flask
from passlib import hash
from flask.ext.restful import Api
from flask_jwt import JWTError


class MyApi(Api):
    """A simple class to keep the default flask_jwt.JWTError behaviour."""

    def handle_error(self, e):
        if isinstance(e, JWTError):
            return flask.jsonify(
                collections.OrderedDict([
                    ('status_code', e.status_code),
                    ('error', e.error),
                    ('description', e.description),
                ])
            ), e.status_code, e.headers
        return super(MyApi, self).handle_error(e)


def get_hash_algorithm(hash_algorithm):
    """Returns a object with methods to encrypt a string and verify a hash.

    :hash_algorithm: a string object
    :returns: hash function

    """

    available_methods = {
        'SHA256': hash.sha256_crypt,
        'SHA512': hash.sha512_crypt,
    }

    if not isinstance(hash_algorithm, str):
        raise ValueError('The parameter "hash_algorithm" should be a string.')

    if hash_algorithm.upper() not in available_methods:
        raise ValueError('Invalid hash method.')

    return available_methods[hash_algorithm]


def encrypt_password(plaintext):
    """Generate a hash based on current_app HASH_ALGORITHM.

    :plaintext: a string object.
    :returns: a string object.

    """

    method = get_hash_algorithm(flask.current_app.config['HASH_ALGORITHM'])

    return method.encrypt(
        plaintext, salt=flask.current_app.config['HASH_SALT'])


def verify_password(password, hash):
    """Verify if a hash was generated by the password specified.

    :password: a string object (plaintext).
    :hash: a string object.
    :returns: True or False.

    """

    method = get_hash_algorithm(flask.current_app.config['HASH_ALGORITHM'])

    return method.verify(password, hash)


def standardize_api_response(function):
    """ Creates a standardized response. This function should be used as a deco
    rator.
    :function: The function decorated should return a dict with one of
    the keys  bellow:
        success -> GET, 200
        error -> Bad Request, 400
        created -> POST, 201
        updated -> PUT, 200
        deleted -> DELETE, 200
        no-data -> No Content, 204

    :returns: json.dumps(response), status code
    """

    available_result_keys = [
        'success', 'error', 'created', 'updated', 'deleted', 'no-data']

    status_code_and_descriptions = {
        'success': (200, 'Successful Operation'),
        'error': (400, 'Bad Request'),
        'created': (201, 'Successfully created'),
        'updated': (200, 'Successfully updated'),
        'deleted': (200, 'Successfully deleted'),
        'no-data': (204, '')
    }

    @functools.wraps(function)
    def make_response(*args, **kwargs):

        result = function(*args, **kwargs)

        if not set(available_result_keys) & set(result):
            raise ValueError('Invalid result key.')

        status_code, description = status_code_and_descriptions[
            next(iter(result.keys()))
        ]

        status_code = ('status_code', status_code)
        description = (
            ('description', description) if status_code[1] != 400 else
            ('error', description)
        )
        data = (
            ('data', next(iter(result.values()))) if status_code[1] != 204 else
            ('data', '')
        )

        return json.dumps(collections.OrderedDict([
            status_code, description, data])), status_code[-1]

    return make_response
