from flask import request, Flask, jsonify, abort
from flask_cors import CORS
import json

import engines.functions_timeseries as ft
import engines.BBDD as db
import os
from celery import Celery



# import engines functions_timeseries
from engines.helpers import merge_two_dicts,trendline
from engines.var import anomaly_VAR, univariate_anomaly_VAR,univariate_forecast_VAR
from engines.holtwinter import anomaly_holt,forecast_holt
from engines.auto_arima import anomaly_AutoArima
from engines.lstm import anomaly_LSTM, anomaly_uni_LSTM
from engines.fbprophet import anomaly_fbprophet
from engines.gluonts import anomaly_gluonts
from engines.changepointdetection import find_changepoints


from datetime import datetime



from struct import *


app = Flask(__name__)
CORS(app)


app.config.from_pyfile(os.path.join(".", "config/app.cfg"), silent=False)

db.init_database()

DB_NAME= app.config.get("DB_NAME")
PORT = app.config.get("PORT")

# Celery configuration
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'

# Initialize Celery
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
#broker=main.config['CELERY_BROKER_URL'])

celery.conf.update(app.config)


@app.route('/univariate', methods=['POST'])
def univariate_engine():
    db.init_database()

    if not request.json:
        abort(400)


    timedata = request.get_json()
    print (timedata)
    lista=timedata['data']

    num_fut = int(timedata.get('num_future', 5))
    desv_mae = int(timedata.get('desv_metric', 2))
    name = timedata.get('name', 'NA')
    train = timedata.get('train', True)
    restart = timedata.get('restart', False)

    print ("train?"+ str(train))
    print ("restart?" + str(restart))
    print ("Received TS")


    if(name != 'NA'):
        filename= './lst/'+name+'.lst'
        try:
            # with open(filename, 'r') as filehandle:
            #     previousList = json.load(filehandle)
            previousList=db.get_ts(name).split(',')
            previousList = list(map(int, previousList))
        except Exception:
            previousList=[]
        print ("previous list" )

        if  not restart :
            print ("Lista append")
            lista = previousList + lista
        # with open(filename, 'w') as filehandle:
        #     json.dump(lista,filehandle)
        str_lista= ",".join(str(v) for v in lista)
        db.set_ts(name,str_lista)

    #desv_mse = 0
    print ("la lista al final es "+ str(type(lista)))
    print (lista)

    salida = ft.model_univariate(lista,num_fut,desv_mae,train,name)

    return jsonify(salida), 201


@app.route('/back_univariate', methods=['POST'])
def back_univariate_engine():
    db.init_database()

    if not request.json:
        abort(400)

    timedata = request.get_json()
    print (timedata)
    lista=timedata['data']

    num_fut = int(timedata.get('num_future', 5))
    desv_mae = int(timedata.get('desv_metric', 2))
    name = timedata.get('name', 'NA')
    train = timedata.get('train', True)
    restart = timedata.get('restart', False)

    print ("train?"+ str(train))
    print ("restart?" + str(restart))
    print ("Received TS")


    if(name != 'NA'):
        filename= './lst/'+name+'.lst'
        try:
            # with open(filename, 'r') as filehandle:
            #     previousList = json.load(filehandle)
            previousList=db.get_ts(name).split(',')
            previousList = list(map(int, previousList))
        except Exception:
            previousList=[]
        print ("previous list" )

        if  not restart :
            print ("Lista append")
            lista = previousList + lista
        # with open(filename, 'w') as filehandle:
        #     json.dump(lista,filehandle)
        str_lista= ",".join(str(v) for v in lista)
        db.set_ts(name,str_lista)

    #desv_mse = 0
    print ("la lista al final es "+ str(type(lista)))
    print (lista)
    print (name )

    print ("invoco el backend")
    salida = back_model_univariate.s(lista_datos=lista,num_fut=num_fut,desv_mse=desv_mae,train=train,name=name).apply_async()

    print (salida.id)

    #task = long_task.apply_async()
    valor = {'task_id': salida.id}
    return jsonify(valor), 200
    #return jsonify(salida), 201

@app.route('/back_univariate_status/<task_id>')
def univariate_taskstatus(task_id):
    task = back_model_univariate.AsyncResult(task_id)
    print ("llega aqui")
    print (task)
    print("----"+task.state+"----")

    if task.state == 'PENDING':
        response = {
            'state': 'Pending',
            'current': 0,
            'total': 1,
            'status': 'Pending...',
            'result': 'Pending'
        }
    if task.state == 'PROGRESS':
        response = {
            'state': task.state,
            'current': task.info.get('current', 0),
            'total': task.info.get('total', 1),
            'status': task.info.get('status', ''),
            'result': task.info.get('result', ''),
            'response': task.info
        }
    if task.state == 'SUCCESS':
        response = {
            'state': task.state,
            'current': 6,
            'total': 6,
            'result': task.info.get('result', ''),
            'status': task.info.get('status', 'Sucessfully'),
            'task_dump': str(task)
        }
        # if 'result' in task.info:
        #     print ("el result aparece en el SUCCESS")
        #     response['result'] = task.info['result']
        # else:
        #     print ("el result NO aparece en el SUCCESS")


    elif task.state == 'FAILURE':
        response = {
            'state': task.state,
            'current': task.info.get('current', 0),
            'total': task.info.get('total', 1),
            'status': task.info.get('status', ''),
            'result': task.info.get('result', ''),
            'response': task.info
        }
    print (task.state)
    print(task.info)
    return jsonify(response)




############################backen functions


@celery.task(bind=True)
def back_model_univariate(self, lista_datos,num_fut,desv_mse,train,name):
    engines_output={}
    debug = {}

    temp_info = {}

    starttime = datetime.now()

    self.update_state(state='PROGRESS',
                      meta={'running': 'LSTM',
                            'status': '',
                            'total': 6,
                            'finish': 0 })
    if not train:

        (model_name,model,params)=db.get_best_model('winner_'+name)
        # print ("recupero el motor " )
        winner= model_name
        if winner == 'LSTM':
            try:
                engines_output['LSTM'] = anomaly_uni_LSTM(lista_datos,num_fut,desv_mse,train,name)
                debug['LSTM'] = engines_output['LSTM']['debug']
            except Exception as e:
                print(e)
                print ('ERROR: exception executing LSTM univariate')
        elif winner == 'VAR':
            engines_output['VAR'] = univariate_forecast_VAR(lista_datos,num_fut,name)
            debug['VAR'] = engines_output['VAR']['debug']
        elif winner == 'Holtwinters':
           engines_output['Holtwinters'] = forecast_holt(lista_datos,num_fut,desv_mse,name)
           debug['Holtwinters'] = engines_output['Holtwinters']['debug']
        else:
            print ("Error")

    else:



        try:
            engines_output['gluonts'] = anomaly_gluonts(lista_datos,num_fut,desv_mse,train,name)
            debug['gluonts'] = engines_output['gluonts']['debug']
            temp_info['gluonts']=engines_output['gluonts']
            self.update_state(state='PROGRESS',
                      meta={'running': 'gluonts',
                            'status': temp_info,
                            'total': 6,
                            'finish': 1})
        except Exception as e:

            print ('ERROR: gluonts univariate: ' + str(e) )


        try:
            engines_output['fbprophet'] = anomaly_fbprophet(lista_datos,num_fut,desv_mse,train,name)
            debug['fbprophet'] = engines_output['fbprophet']['debug']
            temp_info['fbprophet']=engines_output['fbprophet']
            self.update_state(state='PROGRESS',
                      meta={'running': 'fbprophet',
                            'status': temp_info,
                            'total': 6,
                            'finish': 2})
        except Exception as e:

            print ('ERROR: fbprophet univariate: ' + str(e) )


        try:

            engines_output['arima'] = anomaly_AutoArima(lista_datos,num_fut,desv_mse,train,name)
            debug['arima'] = engines_output['arima']['debug']
            temp_info['arima']=engines_output['arima']
            self.update_state(state='PROGRESS',
                      meta={'running': 'VAR',
                            'status': temp_info,
                            'total': 6,
                            'finish': 3})
        except  Exception as e:
            print(e)
            print ('ERROR: exception executing Autoarima')

        try:
            if (train):
                engines_output['VAR'] = univariate_anomaly_VAR(lista_datos,num_fut,name)
                debug['VAR'] = engines_output['VAR']['debug']
            else:
                engines_output['VAR'] = univariate_forecast_VAR(lista_datos,num_fut,name)
                debug['VAR'] = engines_output['VAR']['debug']
            temp_info['VAR'] = engines_output['VAR']
            self.update_state(state='PROGRESS',
                      meta={'running': 'Holtwinters',
                            'status': temp_info,
                            'total': 6,
                            'finish': 4})

        except  Exception as e:
            print(e)
            print ('ERROR: exception executing VAR')

        try:
            if (train ):
                    if (len(lista_datos) > 2000):
                        #new_length=
                        lista_datos_holt=lista_datos[len(lista_datos)-2000:]
                    else:
                        lista_datos_holt = lista_datos
                    engines_output['Holtwinters'] = anomaly_holt(lista_datos_holt,num_fut,desv_mse,name)
                    debug['Holtwinters'] = engines_output['Holtwinters']['debug']
            else:
                   print ("entra en forecast")
                   if (len(lista_datos) > 2000):
                       #new_length=
                       lista_datos_holt=lista_datos[len(lista_datos)-2000:]
                   else:
                       lista_datos_holt = lista_datos
                   engines_output['Holtwinters'] = forecast_holt(lista_datos,num_fut,desv_mse,name)
                   debug['Holtwinters'] = engines_output['Holtwinters']['debug']

            temp_info['Holtwinters'] = engines_output['Holtwinters']
            self.update_state(state='PROGRESS',
                      meta={'running': 'Holtwinters',
                            'status': temp_info,
                            'total': 6,
                            'finish': 5})

        except  Exception as e:
               print(e)
               print ('ERROR: exception executing Holtwinters')

        try:
            engines_output['LSTM'] = anomaly_uni_LSTM(lista_datos,num_fut,desv_mse,train,name)
            debug['LSTM'] = engines_output['LSTM']['debug']
            temp_info['LSTM']=engines_output['LSTM']
            self.update_state(state='PROGRESS',
                      meta={'running': 'anomaly_AutoArima',
                            'status': temp_info,
                            'total': 6,
                            'finish': 6})
        except Exception as e:
            print(e)
            print ('ERROR: exception executing LSTM univariate')


        best_mae=999999999
        winner='VAR'
        print ('The size is: ')
        print (len(engines_output))
        for key, value in engines_output.items():
            print (key + "   " + str(value['mae']))

            if value['mae'] < best_mae:
                best_mae=value['mae']
                winner=key
            print(winner)

        db.new_model('winner_'+name, winner, pack('N', 365),'',0)


        print (winner)

    print ("el ganador es " + str(winner))
    print (engines_output[winner])
    temp= {}
    temp['debug']=debug
    temp['trend']= trendline(lista_datos)

#    return merge_two_dicts(engines_output[winner] , temp)
    salida = merge_two_dicts(engines_output[winner], temp_info)
    finishtime = datetime.now()
    diff_time = finishtime - starttime
    salida['time']= diff_time.total_seconds()
    salida['changepoint'] = find_changepoints(lista_datos)
    salida['winner'] = winner
    salida['trend']= trendline(lista_datos)
    salida_temp= {}
    salida_temp['status'] = salida
    salida_temp['current'] = 100
    salida_temp['total']=5
    salida_temp['finish'] =5
    salida_temp['result'] ='Task completed'


    # insert json output to mongodb
    import pymongo
    from pymongo import MongoClient
    import os
    import pandas as pd
    import numpy as np

    timecop_backend = os.getenv('mongodb_backend' )
    if timecop_backend != None:
        client = MongoClient(timecop_backend)
        # database
        mongo_db = client["ts"]
        timecop_db= mongo_db["timecop"]
        # data_dict = resultado.to_dict("records")
        lista_puntos = np.arange(0, len(lista_datos),1)
        df = pd.DataFrame(list(zip(lista_puntos, lista_datos)), columns = ['step','value'])
        timecop_db.insert_one({"name":name,"data":salida_temp, "ts": df.to_dict(orient='record')})

    return  salida_temp





@app.route('/multivariate', methods=['POST'])
def multivariate_engine():
    if not request.json:
        abort(400)


    timedata = request.get_json()
    items = timedata['timeseries']
    name = timedata.get('name', 'NA')
    train = timedata.get('train', True)
    restart = timedata.get('restart', False)

    list_var=[]
    for item in items:
        data = item['data']
        if(name != 'NA'):
            sub_name = item['name']

            filename= './lst/'+name + '_' + sub_name +'.lst'
            try:
                with open(filename, 'r') as filehandle:
                    previousList = json.load(filehandle)
            except Exception:
                previousList=[]

            lista = previousList + data
            with open(filename, 'w') as filehandle:
                json.dump(lista,filehandle)


        list_var.append(data)



    lista = timedata['main']
    if(name != 'NA'):
        filename= './lst/'+name+'.lst'
        try:
            with open(filename, 'r') as filehandle:
                previousList = json.load(filehandle)
        except Exception:
            previousList=[]

        lista = previousList + lista
        with open(filename, 'w') as filehandle:
            json.dump(lista,filehandle)

    list_var.append(lista)

    num_fut = int(timedata.get('num_future', 5))
    desv_mae = int(timedata.get('desv_metric', 2))


    desv_mse = 0

    salida = ft.model_multivariate(list_var,num_fut,desv_mae)
    #print(salida)
    return jsonify(salida), 201



@app.route('/back_multivariate', methods=['POST'])

def  back_multivariate_engine():
    if not request.json:
        abort(400)


    timedata = request.get_json()
    items = timedata['timeseries']
    name = timedata.get('name', 'NA')
    train = timedata.get('train', True)

    list_var=[]
    for item in items:
        data = item['data']
        if(name != 'NA'):
            sub_name = item['name']

            filename= './lst/'+name + '_' + sub_name +'.lst'
            try:
                with open(filename, 'r') as filehandle:
                    previousList = json.load(filehandle)
            except Exception:
                previousList=[]

            lista = previousList + data
            with open(filename, 'w') as filehandle:
                json.dump(lista,filehandle)


        list_var.append(data)



    lista = timedata['main']
    if(name != 'NA'):
        filename= './lst/'+name+'.lst'
        try:
            with open(filename, 'r') as filehandle:
                previousList = json.load(filehandle)
        except Exception:
            previousList=[]

        lista = previousList + lista
        with open(filename, 'w') as filehandle:
            json.dump(lista,filehandle)

    list_var.append(lista)

    num_fut = int(timedata.get('num_future', 5))
    desv_mae = int(timedata.get('desv_metric', 2))


    desv_mse = 0

    #salida = ft.model_multivariate(list_var,num_fut,desv_mae)
    #print(salida)
    #return jsonify(salida), 201

    print ("invoco el backend")
    salida = back_model_multivariate.s(list_var=list_var,num_fut=num_fut,desv_mse=desv_mae,train=train,name=name).apply_async()

    print (salida.id)

        #task = long_task.apply_async()
    valor = {'task_id': salida.id}
    return jsonify(valor), 200
    #return jsonify(salida), 201




@app.route('/back_multivariate_status/<task_id>')
def multivariate_taskstatus(task_id):
    task = back_model_multivariate.AsyncResult(task_id)
    print ("llega aqui")
    print (task)


    if task.state == 'PENDING':
        response = {
            'state': 'Pending',
            'current': 0,
            'total': 1,
            'status': 'Pending...',
            'result': 'Pending'
        }
    if task.state == 'PROGRESS':
        response = {
            'state': task.state,
            'current': task.info.get('current', 0),
            'total': task.info.get('total', 1),
            'status': task.info.get('status', ''),
            'result': task.info.get('result', ''),
            'response': task.info
        }
    if task.state == 'SUCCESS':
        response = {
            'state': task.state,
            'current': 6,
            'total': 6,
            'result': task.info.get('result', ''),
            'status': task.info.get('status', 'Sucessfully'),
            'task_dump': str(task)
        }
        # if 'result' in task.info:
        #     print ("el result aparece en el SUCCESS")
        #     response['result'] = task.info['result']
        # else:
        #     print ("el result NO aparece en el SUCCESS")


    elif task.state == 'FAILURE':
        response = {
            'state': task.state,
            'current': task.info.get('current', 0),
            'total': task.info.get('total', 1),
            'status': task.info.get('status', ''),
            'result': task.info.get('result', ''),
            'response': task.info
        }
    print (task.state)
    print(task.info)
    return jsonify(response)




@celery.task(bind=True)
def back_model_multivariate(self, list_var,num_fut,desv_mse,train=True,name='Test'):

    engines_output={}
    debug = {}
    temp_info = {}

    self.update_state(state='PROGRESS',
        meta={'running': 'LSTM',
            'status': temp_info,
            'total': 2,
            'finish': 2})

    try:
        engines_output['LSTM'] = anomaly_LSTM(list_var,num_fut,desv_mse)
        debug['LSTM'] = engines_output['LSTM']['debug']
        temp_info['LSTM']=engines_output['LSTM']
        self.update_state(state='PROGRESS',
            meta={'running': 'VAR',
                'status': temp_info,
                'total': 2,
                'finish': 1})
        
        print (engines_output['LSTM'])
    except   Exception as e:
        print(e)
        print ('ERROR: exception executing LSTM')

    try:
        engines_output['VAR'] = anomaly_VAR(list_var,num_fut)
        debug['VAR'] = engines_output['VAR']['debug']
        temp_info['VAR']=engines_output['VAR']
        self.update_state(state='PROGRESS',
            meta={'running': 'VAR',
                'status': temp_info,
                'total': 2,
                'finish': 2})
        print (engines_output['VAR'])
    except   Exception as e:
        print(Exception)
        print("type error: " + str(e))
        print ('ERROR: exception executing VAR')

    best_mae=999999999
    winner='LSTM'
    print ('The size is ')
    print (len(engines_output))
    print (debug)
    for key, value in engines_output.items():
        print (key)
        print(str(value['mae']))
        if value['mae'] < best_mae:
            print (key + " " + str(value['mae']) + " best:" + str(best_mae) )
            best_mae=value['mae']
            winner=key

    print ("el ganador es " + winner)
    temp= {}
    temp['debug']=debug
    #return merge_two_dicts(engines_output[winner] , temp)
    salida = merge_two_dicts(engines_output[winner], temp_info)
    salida['winner'] = winner
    salida['trend']= trendline(list_var[0])
    salida_temp= {}
    salida_temp['status'] = salida
    salida_temp['current'] = 100
    salida_temp['total']=4
    salida_temp['finish'] =4
    salida_temp['result'] ='Task completed'

    return  salida_temp




@app.route('/monitoring')
def monitoring():
    model_name = request.args.get('model_name', default = '%', type = str)
    data_models= db.get_all_models(model_name)
    return jsonify(data_models.to_dict(orient='record')),201


@app.route('/monitoring_winners')
def monitoring_winners():
    model_name = request.args.get('model_name', default = '%', type = str)
    data_models= db.get_winners(model_name)
    return jsonify(data_models.to_dict(orient='record')),201


@app.route('/result_list', methods=['POST','GET'])
def result_list():
    from bson import json_util
    timedata = request.get_json()
    collection_ts = timedata.get('collection', 'NA')
    database = timedata.get('database', 'NA')
    url = timedata.get('url', 'NA')
    ###"mongodb://username:pwd@ds261570.mlab.com:61570/ts?retryWrites=false"

    import pandas as pd
    import pymongo
    from pymongo import MongoClient
    # Making a Connection with MongoClient
    client = MongoClient(url)
    # database
    db = client[database]
    # collection
    collection_data= db[collection_ts]
    import time
    import json
    from bson import json_util, ObjectId

    #Dump loaded BSON to valid JSON string and reload it as dict
    page_sanitized = json.loads(json_util.dumps(collection_data.find({},{'name':1})))
    return jsonify(page_sanitized), 201

@app.route('/result_document', methods=['POST','GET'])
def result_document():
        from bson import json_util
        timedata = request.get_json()
        database = timedata.get('database', 'NA')
        url = timedata.get('url', 'NA')
        input_name = timedata.get('name','NA')
        collection_ts = timedata.get('collection_ts','ts')
        collection_timecop = timedata.get('collection_timecop','timecop')
        ###"mongodb://username:pwd@ds261570.mlab.com:61570/ts?retryWrites=false"

        import pymongo
        from pymongo import MongoClient
        # Making a Connection with MongoClient
        client = MongoClient(url)
        # database
        db = client[database]
        # collection
        data_collection_ts= db[collection_ts]
        ts_data = data_collection_ts.find_one({"name": input_name})
        data_collection_timecop= db[collection_timecop]
        timecop_data = data_collection_timecop.find_one({"name": input_name})
        timecop_data['ts']=ts_data['data']
        import time
        import json
        from bson import json_util, ObjectId

        #Dump loaded BSON to valid JSON string and reload it as dict
        page_sanitized = json.loads(json_util.dumps(timecop_data))
        return jsonify(page_sanitized), 201


@app.route('/')
def index():
    return "Timecop ready to play"

if __name__ == '__main__':
    db.init_database()
    app.run(host = '0.0.0.0',port=PORT)