# -*- coding: utf-8 -*- #
# XXX FIXME : voir si on ne peut pas récupérer config_get_values depuis webapi.lib au lieu de vérifier la version ?
from zephir.utils.creolewrap import ZephirDict, loader3, loader4, get_load_errors
from zephir.eolerpclib import xmlrpclib, EoleProxy
from zephir.web import config
from flask import session, current_app, redirect, send_file, url_for, request, send_file
import base64, os, cjson, traceback
import ConfigParser
from collections import OrderedDict
import StringIO

# Redéfinitions pour gestion de l'authentification via Zéphir
from functools import wraps

# routes supplémentaires pour Zéphir
zephir_funcs = {'save_config_zephir':('/save', ['POST']),
                'download_config_zephir':('/download', ['GET']),
                'upload_config_zephir':('/upload', ['POST']),
                'init_serveur':('/serveur/<id_serveur>/<edit_mode>', None),
                'migrate_serveur':('/migrate/<id_serveur>/<id_variante>/<edit_mode>', None),
                'init_module':('/module/<id_module>', None),
                'init_variante':('/variante/<id_variante>', None)
               }

def get_active_client(reset=False):
    return None

def init_authentication(managed_app):
    managed_app.logger.warning('Session managed by Zephir web framework')

def get_creole_version(zephir_version):
    return config.CREOLE_VERSIONS[int(zephir_version)]

from zephir.web.zephirgenconfig1 import app
from eolegenconfig1 import webapi

def init_zephir(id_, zephir_data, session_data={}, load_error=False, force_load_owner=None):
    webapi.lib.init_session(id_)
    source = session_data['source']
    # version de la configuration éditée (dictionnaires chargés)
    eole_version = session_data.get('eole_version', None)
    creole_version = get_creole_version(eole_version)
    force_instanciate = 'non'
    no_auto_store = False
    if source == "serveur" and session_data.get('registered', 0) == 1:
        force_instanciate = 'oui'
    if source in  ('module', 'variante'):
        no_auto_store = True
    # version du fichier de configuration lu (ou version d'origine du serveur si migration)
    server_version = session_data.get('src_version', None)
    dico = ZephirDict(mode='', version=creole_version, force_instanciate=force_instanciate,
                      no_auto_store=no_auto_store, eole_version=eole_version, server_version=server_version)
    dico.init_from_zephir(zephir_data, load_error, force_load_owner=force_load_owner, warnings=True, eole_version=eole_version)
    # définition du 'owner' des valeurs éditées :
    # module / variante / serveur(->zephir)
    zephir_owners = {'serveur':'zephir'}
    dico.set_owner(zephir_owners.get(source, source))
    if source == 'serveur':
        # préremplissage de rne et libelle_etab si pas encore saisis
        try:
            if session_data.get('rne', '') and force_instanciate == 'non' and dico.get_var('numero_etab')[1] in ("", [], [""]):
                dico.set_value(session_data['rne'])
            if session_data.get('libelle_etab', '') and dico.get_var('libelle_etab')[1] in ("", [], [""]):
                dico.set_value(session_data['libelle_etab'])
        except:
            traceback.print_exc()
            app.logger.error('Erreur au pré-remplissage des informations établissement')
    dico.dico.read_write()
    if server_version and server_version != eole_version:
        # passage de la version d'origine si génération de configuration de migration
        server_release = config.DISTRIBS[server_version][1]
        dico.dico.impl_set_information('upgrade', server_release)
    # stockage de la configuration tiramisu et des données d'origine (sans infos d'upgrade)
    if type(zephir_data[-1]) == dict and 'load_errors' in zephir_data[-1]:
        del(zephir_data[-1])
    session_data.update({'config':dico.dico, 'zephir_data':zephir_data})
    webapi.lib.store_session_data(id_, session_data)

# surcharge des fonctions originales d'eolegenconfig
def get_zephir_id():
    # en mode zephir : on utilise le cookie de l'application web Zéphir
    return request.cookies['TWISTED_SECURE_SESSION']

def get_var_module(zephir_proxy, id_var):
    rc, mod_info = zephir_proxy.modules.get_variante(int(id_var))
    assert rc == 1, mod_info
    return int(mod_info[0]['module'])

def get_mod_version(zephir_proxy, id_mod):
    rc, data_mod = zephir_proxy.modules.get_module(int(id_mod))
    assert rc == 1, data_mod
    return data_mod[0]['version']

webapi.lib.get_id = get_zephir_id
webapi.lib.init_zephir = init_zephir


def release_zephir(id_):
    # récupère le numéro de release lié au module édité
    session_data = webapi.lib.session_tiramisu[id_]
    try:
        eole_version = session_data.get('eole_version', None)
        assert eole_version is not None
        assert session_data['eole_version'] in config.DISTRIBS
        return config.DISTRIBS[session_data['eole_version']][1]
    except AssertionError as err:
        # si pas d'infos posées par Zéphir, on utilise les données de la configuration
        # ne devrait pas arriver
        app.logger.error(err, exc_info=True)
        conf = webapi.lib.get_config(id_)
        return conf._getattr("creole.general.eole_release", force_permissive=True)

def version_zephir(id_):
    release = release_zephir(id_)
    return ".".join(release.split(".")[:2])

webapi.lib.get_version = version_zephir
webapi.lib.get_release = release_zephir

def get_proxy(request):
    # récupération des infos de la requête d'origine transmis par twisted WSGI
    session.pop('username', None)
    assert 'HTTP_AUTHORIZATION' in request.environ, 'Unauthorized access'
    auth_info = request.environ['HTTP_AUTHORIZATION'].split()[1]
    user, passwd = base64.decodebytes(auth_info).split(':')
    # XXX zephir_web est prévu pour fonctionner avec un backend local pour un
    # backend distant, passer l'adresse en paramètre et utiliser config.PORT_ZEPHIR
    port_zephir = str(int(config.PORT_ZEPHIR) + 1)
    proxy_addr = "http://{0}:{1}@localhost:{2}/".format(user, passwd, port_zephir)
    # on met à jour le nom de l'utilisateur dans la session flask d'eolegenconfig
    session['username'] = user
    return EoleProxy(proxy_addr)

def init_serveur(id_serveur, edit_mode):
    """Entry point for editing a server configuration through Zephir
    """
    try:
        zephir_proxy = get_proxy(request)
        rne = libelle_etab = ""
        try:
            # récupération rne/libellé établissement du serveur
            rc, data = zephir_proxy.serveurs.get_serveur(int(id_serveur))
            assert rc == 1
            rne = data[0]['rne']
            rc, data = zephir_proxy.etabs.get_etab(rne)
            assert rc == 1
            libelle_etab = data[0]['libelle']
        except:
            traceback.print_exc()
            app.logger.error(u"Erreur de récupération des infos établissement pour le serveur {0}".format(id_serveur))
        rc, status_data = zephir_proxy.serveurs.get_status(int(id_serveur))
        if edit_mode == "modif_config":
            # dans le cas d'un serveur (édition), on détermine si la procédure
            # d'enregistrement a déjà eu lieu (serveur instancié)
            registered = status_data.get('cle_ok', 0)
        else:
            registered = 0
        # récupération des dictionnaires/valeurs
        rc, data = zephir_proxy.serveurs.get_dico(int(id_serveur), edit_mode, False, True)
        assert rc == 1, data
        rc, module_version = zephir_proxy.serveurs.module_version(int(id_serveur))
        assert rc == 1
        session_data = {'source':'serveur', 'source_id':int(id_serveur), 'proxy':zephir_proxy, \
        'edit_mode':edit_mode, 'rne':rne, 'libelle_etab':libelle_etab, 'registered':registered, \
        'eole_version':module_version}
        init_zephir(get_zephir_id(), data, session_data)
    except xmlrpclib.ProtocolError:
        return webapi.make_error_response(u"Vous n'êtes pas autorisé à gérer la configuration de ce serveur", 403)
    except Exception as err:
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))
    return redirect(url_for('.root',zephir=1,title="Genconfig: serveur {0}".format(id_serveur)))

def migrate_serveur(id_serveur, id_variante, edit_mode='migration'):
    """Entry point for editing a server configuration through Zephir
    """
    try:
        zephir_proxy = get_proxy(request)
        # récupération de la configuration migrée avec les paramètres venant de la variante
        rc, data = zephir_proxy.serveurs.migrate_conf(int(id_serveur),edit_mode,int(id_variante))
        assert rc == 1, data
        id_module = get_var_module(zephir_proxy, id_variante)
        module_version = get_mod_version(zephir_proxy, id_module)
        # récupération de la version d'origine
        rc, src_version = zephir_proxy.serveurs.module_version(int(id_serveur))
        # récupération de la version "Zéphir" de la distribution de destination
        session_data = {'source':'serveur', 'source_id':int(id_serveur), 'proxy':zephir_proxy, \
                        'edit_mode':edit_mode, 'registered':0, 'eole_version':module_version, \
                        'src_version':src_version}
        init_zephir(get_zephir_id(), data, session_data)
    except xmlrpclib.ProtocolError:
        return webapi.make_error_response(u"Vous n'êtes pas autorisé à gérer la configuration de ce serveur", 403)
    except Exception as err:
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))
    return redirect(url_for('.root',zephir=1,title="Genconfig: serveur {0}".format(id_serveur)))

def init_module(id_module):
    """Entry point of the application for editing a module default values through Zephir
    """
    try:
        zephir_proxy = get_proxy(request)
        # récupération des dictionnaires/valeurs
        module_version = get_mod_version(zephir_proxy, id_module)
        rc, data = zephir_proxy.modules.get_dico(int(id_module))
        assert rc == 1, data
        session_data = {'source':'module', 'source_id':int(id_module),
                        'proxy':zephir_proxy, 'eole_version':module_version}
        init_zephir(get_zephir_id(), data, session_data)
    except xmlrpclib.ProtocolError:
        return webapi.make_error_response(u"Vous n'êtes pas autorisé à accéder à ce module", 403)
    except Exception as err:
        import traceback
        traceback.print_exc()
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))
    return redirect(url_for('.root',zephir=1,title="Genconfig: module {0}".format(id_module)))

def init_variante(id_variante):
    """Entry point of the application for editing a variante default values through Zephir
    """
    try:
        zephir_proxy = get_proxy(request)
        # récupération des dictionnaires/valeurs
        id_module = get_var_module(zephir_proxy, id_variante)
        module_version = get_mod_version(zephir_proxy, id_module)
        rc, data = zephir_proxy.modules.get_dico(id_module, int(id_variante))
        assert rc == 1, data
        session_data = {'source':'variante', 'source_id':int(id_variante),
                        'proxy':zephir_proxy, 'eole_version':module_version}
        init_zephir(get_zephir_id(), data, session_data)
    except xmlrpclib.ProtocolError:
        return webapi.make_error_response(u"Vous n'êtes pas autorisé à accéder à cette variante", 403)
    except Exception as err:
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))
    return redirect(url_for('.root',zephir=1,title="Genconfig: variante {0}".format(id_variante)))

# redéfinition de l'import de fichier de configuration. Permet de conserver
# la référence aux valeurs d'origine provenant de zéphir
def upload_config_zephir():
    """get values from client stored configuration file
    """
    try:
        eol_file = request.files['file']
        if not eol_file or not os.path.splitext(eol_file.filename)[1] == '.eol':
            return webapi.make_error_response(u"Extension de fichier non autorisée")
        else:
            # récupération des valeurs du fichier
            load_error = False
            try:
                f_values = cjson.decode(eol_file.stream.read().strip(), all_unicode=True)
            except:
                app.logger.error("Tentative d'import d'un fichier Creole2")
                # essai d'import à l'ancien format
                try:
                    old_conf = ConfigParser.ConfigParser(dict_type=dict)
                    eol_file.seek(0)
                    old_conf.read_file(eol_file.stream, 'zephir_ini.eol')
                    assert len(old_conf._sections) > 0
                    if isinstance(old_conf._sections, OrderedDict):
                        f_values = old_conf._sections
                    else:
                        f_values = dict(old_conf._sections)
                    load_error = True
                except Exception as err:
                    app.logger.error(err, exc_info=True)
                    return webapi.make_error_response(u"Fichier non compatible ({0})".format(err))
                # erreur, au chargement, fichier de type creole2 ?
            id_ = get_zephir_id()
            # mise à jour des valeurs récupérées de Zéphir et réinitialisation de la configuration
            orig_values = eval(webapi.lib.session_tiramisu[id_]['zephir_data'][-1])
            webapi.lib.session_tiramisu[id_]['zephir_data'][-1] = str(f_values)
            zephir_version = webapi.lib.session_tiramisu[id_]['eole_version']
            src_version = None
            src_release = None
            if load_error:
                # import 2.2/2.3 : src_version non disponible dans le fichier
                if 'src_version' in webapi.lib.session_tiramisu[id_]:
                    del(webapi.lib.session_tiramisu[id_]['src_version'])
            else:
                # on cherche si la version source est précisée dans le fichier.
                src_release = f_values.get('___version___', '2.4')
                for src_version, dist_infos in config.DISTRIBS.items():
                    if dist_infos[1] == src_release:
                        webapi.lib.session_tiramisu[id_]['src_version'] = src_version
                        break
            # on réinitialise la configuration en forçant les valeurs précédentes
            # comme valeurs d'origine (sinon genconfig ne détecte pas de modifications)
            webapi.lib.del_config(id_)
            if load_error or src_version != zephir_version:
                load_owner = 'zephir_upgrade'
            else:
                load_owner = 'zephir_import'
            conf = webapi.lib.get_config(id_, load_error=load_error, force_load_creole_owner=load_owner)
            conf.impl_set_information('orig_values', orig_values)
            # vérification des valeurs importées
            load_errors = get_load_errors(conf)
            for load_error in load_errors.values():
                app.logger.error(unicode(load_error, 'utf-8'))
            webapi.lib.session_tiramisu[id_]['zephir_data'][-1] = str(f_values)
            version = webapi.lib.get_upgrade_version(id_)
            return webapi.make_json_response({ 'filename': eol_file.filename, 'errors': load_errors, 'version': version})
    except Exception as err:
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))

# redéfinition pour Zéphir, ajoute type type de ressource et identifiant
# au nom du fichier téléchargé (au lieu de config.eol)
# pas de validation des valeurs obligatoires en mode module/variante
def download_config_zephir():
    try:
        id_ = webapi.lib.get_id()
        session_data = webapi.lib.session_tiramisu[id_]
        zephir_version = session_data['eole_version']
        source = session_data['source']
        source_id = session_data['source_id']
        conf = session_data['config']
        if get_creole_version(zephir_version) == "creole3":
            config_get_values = loader3.config_get_values
        else:
            config_get_values = loader4.config_get_values
        if source == 'serveur':
            json_config = config_get_values(conf, 'creole')
        else:
            json_config = config_get_values(conf, 'creole', check_mandatory=False, ignore_autofreeze=True)
        if int(zephir_version) > 6:
            # spécifique Zéphir, on passe la release à config_get_values pour avoir
            # ___version___ dans le fichier (https://dev-eole.ac-dijon.fr/issues/10530)
            eole_release = webapi.lib.get_release(id_)
            json_config['___version___'] = eole_release
        json_config = cjson.encode(json_config)
        strIO = StringIO.StringIO()
        strIO.write(json_config)
        strIO.seek(0)
        return send_file(strIO,
                     attachment_filename="{0}-{1}.eol".format(source, source_id),
                     as_attachment=True)
    except Exception as err:
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))

def save_config_zephir():
    """sends configuration values back to zephir
    """
    try:
        id_ = get_zephir_id()
        conf = webapi.lib.get_config(id_)
        # envoi des valeurs à Zéphir
        source = webapi.lib.session_tiramisu[id_]['source']
        source_id = webapi.lib.session_tiramisu[id_]['source_id']
        zephir_proxy = webapi.lib.session_tiramisu[id_]['proxy']
        zephir_data = webapi.lib.session_tiramisu[id_]['zephir_data']
        zephir_version = webapi.lib.session_tiramisu[id_]['eole_version']
        if get_creole_version(zephir_version) == "creole3":
            config_get_values = loader3.config_get_values
        else:
            config_get_values = loader4.config_get_values
        if source == 'serveur':
            values = config_get_values(conf, 'creole')
            edit_mode = webapi.lib.session_tiramisu[id_]['edit_mode']
            rc, msg = zephir_proxy.serveurs.save_conf(source_id, [str(values)] , edit_mode)
        elif source == 'module':
            values = config_get_values(conf, 'creole',check_mandatory=False, ignore_autofreeze=True)
            rc, msg = zephir_proxy.modules.save_dico([str(values)] , source_id)
        elif source == 'variante':
            values = config_get_values(conf, 'creole',check_mandatory=False, ignore_autofreeze=True)
            # cas particulier, on ne sauvegarde pas les valeurs du module dans le fichier de variante
            varnames = values.keys()
            for varname in varnames:
                if values[varname].get('owner','') == 'module':
                    del(values[varname])
            id_module = get_var_module(zephir_proxy, source_id)
            pass_var = "XXX FIXME : mot de passe variante ..."
            rc, msg = zephir_proxy.modules.save_dico([str(values)], id_module, source_id, pass_var)
            if rc == 0 : return webapi.make_error_response(unicode(msg, 'utf-8'), 403)
        # return mode and debug to previous state
        # after reloading conf
        mode = webapi.lib.get_mode(id_)
        debug = webapi.lib.get_debug(id_)
        webapi.lib.set_mode(id_, mode)
        webapi.lib.set_debug(id_, debug)
        # mise à jour des valeurs modifiées dans zephir_data
        zephir_data[-1] = str(values)
        webapi.lib.store_session_data(id_, {'zephir_data':zephir_data})
        webapi.lib.del_config(id_)
        return webapi.make_json_response({})
    except xmlrpclib.ProtocolError:
        return webapi.make_error_response(u"Vous n'êtes pas autorisé à modifier ces données", 403)
    except Exception as err:
        app.logger.error(err, exc_info=True)
        return webapi.make_error_response(str(err))

del(webapi.api_funcs['save_config'])
del(webapi.api_funcs['download_config'])
del(webapi.api_funcs['upload_config'])

glob = globals()
for func_name, options in webapi.api_funcs.items():
    route, methods, authenticated = options
    api_func = getattr(webapi, func_name)
    if methods is not None:
        api_func.methods = methods
    app.add_url_rule(route, func_name, api_func)
    glob[func_name] = api_func

for func_name, options in zephir_funcs.items():
    route, methods = options
    api_func = glob[func_name]
    if methods is not None:
        api_func.methods = methods
    app.add_url_rule(route, func_name, api_func)

after_request = webapi.after_request
