from time import time

import anyjson
from moira.graphite.datalib import createRequestContext
from moira.graphite.evaluator import evaluateTarget
from twisted.internet import defer
from twisted.web import http, server

from moira.checker.expression import getExpression
from moira.checker import state
from moira.trigger import trigger_reformat
from moira.logs import log


def bad_request(request, message):
    request.setResponseCode(http.BAD_REQUEST)
    request.write(message)
    request.finish()
    return message


def check_json(f):
    @defer.inlineCallbacks
    def decorator(*args, **kwargs):
        request = args[1]
        try:
            request.body = request.content.getvalue()
            request.body_json = anyjson.deserialize(request.body)
        except Exception as e:
            log.error("Invalid trigger json [{json}]: {e}", json=request.body, e=e)
            defer.returnValue(bad_request(request, "Content is not json"))
        yield f(*args, **kwargs)
    return decorator


def is_simple_target(requestContext):
    if len(requestContext['graphite_patterns']) > 1:
        return False

    complexPatternFound = False
    for pattern in requestContext['graphite_patterns'].iterkeys():
        if '*' in pattern or '{' in pattern:
            complexPatternFound = True
            break

    return not complexPatternFound


@defer.inlineCallbacks
def resolve_patterns(request, expression_values):
    now = int(time())
    context = createRequestContext(str(now - 600), str(now), allowRealTimeAlerting=True)
    resolved = set()
    target_num = 1
    context['time_series_names'] = set()
    is_simple_trigger = True
    if len(request.body_json["targets"]) > 1:
        is_simple_trigger = False
    for target in request.body_json["targets"]:
        time_series = yield evaluateTarget(context, target)
        if is_simple_trigger and not is_simple_target(context):
            is_simple_trigger = False
        target_name = "t%s" % target_num
        for ts in time_series:
            context['time_series_names'].add(ts.name)
        expression_values[target_name] = 42
        target_num += 1
        for pattern, resolve in context['graphite_patterns'].iteritems():
            for r in resolve:
                if r != pattern:
                    resolved.add(r)
    request.body_json["patterns"] = [pattern for pattern in context['graphite_patterns']
                                     if pattern not in resolved]
    request.body_json["is_simple_trigger"] = is_simple_trigger
    request.context = context


def check_trigger(f):
    @defer.inlineCallbacks
    def decorator(*args, **kwargs):
        request = args[1]
        json = request.body_json
        request.graphite_patterns = []
        for field, alt in [("targets", None), ("warn_value", "expression"), ("error_value", "expression")]:
            if json.get(field) is None and json.get(alt) is None:
                defer.returnValue(bad_request(request, "%s is required" % field))
        if type(json["targets"]) is not list:
            defer.returnValue(bad_request(request, "Invalid trigger targets"))
        try:
            request.body_json = trigger_reformat(json, json.get("id"), json.get("tags", []))
        except Exception as e:
            log.error("Invalid trigger format [{json}]: {e}", json=json, e=e)
            defer.returnValue(bad_request(request, "Invalid trigger format"))
        expression_values = {'warn_value': json.get('warn_value'),
                             'error_value': json.get('error_value'),
                             'PREV_STATE': state.NODATA}
        try:
            yield resolve_patterns(request, expression_values)
        except Exception as e:
            log.error("Invalid graphite targets [{targets}]: {e}", targets=request.body_json["targets"], e=e)
            defer.returnValue(bad_request(request, "Invalid graphite targets"))
        try:
            getExpression(json.get("expression"), **expression_values)
        except Exception as e:
            log.error("Invalid expression [{expression}]: {e}", expression=json.get("expression"), e=e)
            defer.returnValue(bad_request(request, "Invalid expression"))
        yield f(*args, **kwargs)
    return decorator


def delayed(f):
    def decorator(resource, request):
        @defer.inlineCallbacks
        def wrapper():
            try:
                yield f(resource, request)
            except Exception as e:
                log.error("Error in delayed decorator wrapped function: {e}", e=e)
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                request.finish()
        wrapper()
        return server.NOT_DONE_YET
    return decorator