# Copyright (c) 2015, Sergiusz Bazanski <q3k@q3k.org>
# Copyright (c) 2015, Remigiusz Marcinkiewicz <enleth@enleth.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import hmac
import json
import datetime
from functools import wraps
from sqlalchemy import and_

from flask import request, abort, Response

from webapp import models, app, mc

class APIError(Exception):
    def __init__(self, message, code=500):
        self.message = message
        self.code = code


def _public_api_method(path):
    """A decorator that adds a public, GET based method at /api/<path>.json.

    The resulting data is JSON-serialized."""
    def decorator2(original):
        @wraps(original)
        def wrapper_json(*args, **kwargs):
            try:
                content = original(*args, **kwargs)
                status = "ok"
                code = 200
            except APIError as e:
                content = e.message
                code = e.code
                status = "error"
            except Exception as e:
                raise
                content = "Internal server error."
                code = 500
                status = "error"
            
            last_transfer = models.Transfer.query.order_by(models.Transfer.date.desc()).first()
            modified = str(last_transfer.date)

            r = {}
            r["status"] = status
            r["content"] = content
            r["modified"] = modified
            return Response(json.dumps(r), mimetype="application/json"), code
        return app.route("/api/" + path + ".json", methods=["GET"])(wrapper_json)
    return decorator2
            

def _private_api_method(path):
    """A decorator that adds a private, HMACed, POST based method at /api/path.
    The  JSON-decoded POSTbody is stored as request.decoded.
    The resulting data is also JSON-encoded.

    It also that ensures that the request is authorized if 'private' is True.
    If so, it also adds a request.api_member object that points to a member if an
    API key should be limited to that member (for example, when handing over
    keys to normal members)."""
    def decorator(original):
        @wraps(original)
        def wrapper(*args, **kwargs):
            if request.data.count(",") != 1:
                abort(400)
            message64, mac64 = request.data.split(",")
            try:
                message = message64.decode("base64")
                mac = mac64.decode("base64")
            except:
                abort(400)

            for key in models.APIKey.query.all():
                mac_verify = hmac.new(key.secret.encode("utf-8"))
                mac_verify.update(message)
                if mac_verify.digest() == mac:
                    break
            else:
                abort(403)

            if key.member:
                request.api_member = key.member
            else:
                request.api_member = None
            try:
                if request.data:
                    request.decoded = json.loads(request.data.decode("base64"))
                else:
                    request.decoded = {}
            except Exception as e:
                print request.data
                print e
                abort(400)

            return json.dumps(original(*args, **kwargs))
        return app.route("/api/" + path, methods=["POST"])(wrapper)
    return decorator

@_private_api_method("list_members")
def api_members():
    if request.api_member:
        abort(403)

    members = [member.username for member in models.Member.query.all()]
    return members


@_private_api_method("get_member_info")
def api_member():
    mid = request.decoded["member"]
    if request.api_member and request.api_member.username != mid:
        abort(403)

    member = models.Member.query.filter_by(username=mid).join(models.Member.transfers).\
        join(models.MemberTransfer.transfer).first()
    mts = member.transfers
    response = {}
    response["paid"] = []
    for mt in mts:
        t = {}
        t["year"] = mt.year
        t["month"] = mt.month
        transfer = {}
        transfer["uid"] = mt.transfer.uid
        transfer["amount"] = mt.transfer.amount
        transfer["title"] = mt.transfer.title
        transfer["account"] = mt.transfer.account_from
        transfer["from"] = mt.transfer.name_from
        t["transfer"] = transfer
        response["paid"].append(t)
    response["months_due"] = member.get_months_due()
    response["membership"] = member.type

    return response

def _stats_for_month(year, month):
    cache_key = 'kasownik-stats_for_month-{}-{}'.format(year, month)
    cache_data = mc.get(cache_key)
    if cache_data:
        cache_data = json.loads(cache_data)
        return cache_data[0], cache_data[1]
    # TODO: export this to the config
    money_required = 4217+615+615
    money_paid = 0
    mts = models.MemberTransfer.query.filter_by(year=year, month=month).\
        join(models.MemberTransfer.transfer).all()
    for mt in mts:
        amount_all = mt.transfer.amount
        amount = amount_all / len(mt.transfer.member_transfers)
        money_paid += amount
    mc.set(cache_key, json.dumps([money_required, money_paid/100]))
    return money_required, money_paid/100

@_public_api_method("month/<year>/<month>")
def api_month(year=None, month=None):
    money_required, money_paid = _stats_for_month(year, month)
    return dict(required=money_required, paid=money_paid)

@_public_api_method("mana")
def api_manamana(year=None, month=None):
    """To-odee doo-dee-doo!"""
    now = datetime.datetime.now()