# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@viennet.net
#
##############################################################################

"""Fonctions sur les utilisateurs
"""

# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy

from flask import url_for, g
from flask_login import current_user

import cracklib  # pylint: disable=import-error

from app import db

from app.auth.models import Permission
from app.auth.models import User

from app.scodoc import html_sco_header
from app.scodoc import sco_etud
from app.scodoc import sco_excel
from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log
from app.scodoc.scolog import logdb
import app.scodoc.sco_utils as scu

from app.scodoc.sco_exceptions import (
    AccessDenied,
    ScoException,
    ScoValueError,
    ScoInvalidDateError,
    ScoLockedFormError,
    ScoGenError,
)


def is_valid_password(cleartxt):
    """Check password.
    returns True if OK.
    """
    if (
        hasattr(scu.CONFIG, "MIN_PASSWORD_LENGTH")
        and scu.CONFIG.MIN_PASSWORD_LENGTH > 0
        and len(cleartxt) < scu.CONFIG.MIN_PASSWORD_LENGTH
    ):
        return False  # invalid: too short
    try:
        _ = cracklib.FascistCheck(cleartxt)
        return True
    except ValueError:
        return False


# ---------------

# ---------------


def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
    "gestion utilisateurs..."
    all_depts = int(all_depts)
    with_inactives = int(with_inactives)

    H = [html_sco_header.html_sem_header(REQUEST, "Gestion des utilisateurs")]

    if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
        H.append(
            '<p><a href="{}" class="stdlink">Ajouter un utilisateur</a>'.format(
                url_for("users.create_user_form", scodoc_dept=g.scodoc_dept)
            )
        )
        if current_user.is_administrator():
            H.append(
                '&nbsp;&nbsp; <a href="{}" class="stdlink">Importer des utilisateurs</a></p>'.format(
                    url_for("users.import_users_form", scodoc_dept=g.scodoc_dept)
                )
            )
        else:
            H.append(
                "&nbsp;&nbsp; Pour importer des utilisateurs en masse (via xlsx file) contactez votre administrateur scodoc."
            )
    if all_depts:
        checked = "checked"
    else:
        checked = ""
    if with_inactives:
        olds_checked = "checked"
    else:
        olds_checked = ""
    H.append(
        """<p><form name="f" action="%s" method="get">
    <input type="checkbox" name="all_depts" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
    <input type="checkbox" name="with_inactives" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
    </form></p>"""
        % (REQUEST.URL0, checked, olds_checked)
    )

    L = list_users(
        g.scodoc_dept,
        all_depts=all_depts,
        with_inactives=with_inactives,
        format=format,
        REQUEST=REQUEST,
        with_links=current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept),
    )
    if format != "html":
        return L
    H.append(L)

    F = html_sco_header.sco_footer()
    return "\n".join(H) + F


def list_users(
    dept,
    all_depts=False,  # tous les departements
    with_inactives=False,  # inclut les anciens utilisateurs (status "old")
    format="html",
    with_links=True,
    REQUEST=None,
):
    "List users, returns a table in the specified format"
    from app.scodoc.sco_permissions_check import can_handle_passwd

    if dept and not all_depts:
        users = get_user_list(dept=dept, with_inactives=with_inactives)
        comm = "dept. %s" % dept
    else:
        users = get_user_list(with_inactives=with_inactives)
        comm = "tous"
    if with_inactives:
        comm += ", avec anciens"
    comm = "(" + comm + ")"
    # -- Add some information and links:
    r = []
    for u in users:
        # Can current user modify this user ?
        can_modify = can_handle_passwd(u, allow_admindepts=True)

        d = u.to_dict()
        r.append(d)
        # Add links
        if with_links and can_modify:
            target = url_for(
                "users.user_info_page", scodoc_dept=dept, user_name=u.user_name
            )
            d["_user_name_target"] = target
            d["_nom_target"] = target
            d["_prenom_target"] = target

        # Hide passwd modification date (depending on visitor's permission)
        if not can_modify:
            d["date_modif_passwd"] = "(non visible)"

    title = "Utilisateurs définis dans ScoDoc"
    tab = GenTable(
        rows=r,
        columns_ids=(
            "user_name",
            "nom_fmt",
            "prenom_fmt",
            "email",
            "dept",
            "roles_string",
            "date_expiration",
            "date_modif_passwd",
            "passwd_temp",
            "status_txt",
        ),
        titles={
            "user_name": "Login",
            "nom_fmt": "Nom",
            "prenom_fmt": "Prénom",
            "email": "Mail",
            "dept": "Dept.",
            "roles_string": "Rôles",
            "date_expiration": "Expiration",
            "date_modif_passwd": "Modif. mot de passe",
            "passwd_temp": "Temp.",
            "status_txt": "Etat",
        },
        caption=title,
        page_title="title",
        html_title="""<h2>%d utilisateurs %s</h2>
        <p class="help">Cliquer sur un nom pour changer son mot de passe</p>"""
        % (len(r), comm),
        html_class="table_leftalign list_users",
        html_with_td_classes=True,
        html_sortable=True,
        base_url="%s?all=%s" % (REQUEST.URL0, all),
        pdf_link=False,  # table is too wide to fit in a paper page => disable pdf
        preferences=sco_preferences.SemPreferences(),
    )

    return tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)


def get_user_list(dept=None, with_inactives=False):
    """Returns list of users.
    If dept, select users from this dept,
    else return all users.
    """
    # was get_userlist
    q = User.query
    if dept is not None:
        q = q.filter_by(dept=dept)
    if not with_inactives:
        q = q.filter_by(active=True)
    return q.order_by(User.nom, User.user_name).all()


def _user_list(user_name):
    "return user as a dict"
    u = User.query.filter_by(user_name=user_name).first()
    if u:
        return u.to_dict()
    else:
        return None


def user_info(user_name_or_id=None, user=None):
    """Dict avec infos sur l'utilisateur (qui peut ne pas etre dans notre base).
    Si user_name est specifie (string ou id), interroge la BD. Sinon, user doit etre une instance
    de User.
    """
    if user_name_or_id is not None:
        if isinstance(user_name_or_id, int):
            u = User.query.filter_by(id=user_name_or_id).first()
        else:
            u = User.query.filter_by(user_name=user_name_or_id).first()
        if u:
            user_name = u.user_name
            info = u.to_dict()
        else:
            info = None
    else:
        info = user.to_dict()
        user_name = user.user_name

    if not info:
        # special case: user is not in our database
        return {
            "user_name": user_name,
            "nom": user_name,
            "prenom": "",
            "email": "",
            "dept": "",
            "nomprenom": user_name,
            "prenomnom": user_name,
            "prenom_fmt": "",
            "nom_fmt": user_name,
            "nomcomplet": user_name,
            "nomplogin": user_name,
            # "nomnoacc": scu.suppress_accents(user_name),
            "passwd_temp": 0,
            "status": "",
            "date_expiration": None,
        }
    else:
        # Ensure we never publish password hash
        if "password_hash" in info:
            del info["password_hash"]
        return info


def user_info_page(user_name=None):
    """Display page of info about given user.
    If user_name not specified, user current_user
    """
    from app.scodoc.sco_permissions_check import can_handle_passwd

    # peut on divulguer ces infos ?
    if not can_handle_passwd(current_user, allow_admindepts=True):
        raise AccessDenied("Vous n'avez pas la permission de voir cette page")

    dept = g.scodoc_dept
    if not user_name:
        user = current_user
    else:
        user = User.query.filter_by(user_name=user_name).first()
    if not user:
        raise ScoValueError("invalid user_name")
    H = [
        html_sco_header.sco_header(
            page_title="Utilisateur %s" % user.user_name,
        )
    ]
    F = html_sco_header.sco_footer()
    H.append("<h2>Utilisateur: %s" % user.user_name)
    info = user.to_dict()
    if info:
        H.append(" (%(status_txt)s)" % info)
    H.append("</h2>")
    if not info:
        H.append(
            "<p>L' utilisateur '%s' n'est pas défini dans ce module.</p>" % user_name
        )
        if user.has_permission(Permission.ScoEditAllNotes, dept):
            H.append("<p>(il peut modifier toutes les notes de %s)</p>" % dept)
        if user.has_permission(Permission.ScoEditAllEvals, dept):
            H.append("<p>(il peut modifier toutes les évaluations de %s)</p>" % dept)
        if user.has_permission(Permission.ScoImplement, dept):
            H.append("<p>(il peut creer des formations en %s)</p>" % dept)
    else:
        H.append(
            """<p>
        <b>Login :</b> %(user_name)s<br/>
        <b>Nom :</b> %(nom)s<br/>
        <b>Prénom :</b> %(prenom)s<br/>
        <b>Mail :</b> %(email)s<br/>
        <b>Roles :</b> %(roles_string)s<br/>
        <b>Dept :</b> %(dept)s<br/>
        <b>Dernière modif mot de passe:</b> %(date_modif_passwd)s<br/>
        <b>Date d'expiration:</b> %(date_expiration)s
        <p><ul>
            <li><a class="stdlink" href="form_change_password?user_name=%(user_name)s">changer le mot de passe</a></li>"""
            % info
        )
        if current_user.has_permission(Permission.ScoUsersAdmin, dept):
            H.append(
                """
            <li><a  class="stdlink" href="create_user_form?user_name=%(user_name)s&edit=1">modifier ou désactiver ce compte</a><br/>
            <em>(pour "supprimer" un utilisateur, le rendre inactif via le formulaire)</em>
            </li>

            """
                % info
            )

        H.append("</ul>")

        if current_user.user_name == user_name:
            H.append(
                '<p><b>Se déconnecter: <a class="stdlink" href="%s">logout</a></b></p>'
                % url_for("auth.logout")
            )
        # Liste des permissions
        H.append(
            '<div class="permissions"><p>Permissions de cet utilisateur dans le département %s:</p><ul>'
            % dept
        )
        for p in Permission.description:
            perm = getattr(Permission, p)
            if user.has_permission(perm, dept):
                b = "oui"
            else:
                b = "non"
            H.append("<li>%s : %s</li>" % (Permission.description[p], b))
        H.append("</ul></div>")

    if current_user.has_permission(Permission.ScoUsersAdmin, dept):
        H.append(
            '<p><a class="stdlink" href="%s">Liste de tous les utilisateurs</a></p>'
            % url_for("users.index_html", scodoc_dept=g.scodoc_dept)
        )
    return "\n".join(H) + F


def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]):
    """Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
    Cherche homonymes.
    returns (ok, msg)
        - ok : si vrai, peut continuer avec ces parametres
            (si ok est faux, l'utilisateur peut quand même forcer la creation)
        - msg: message warning a presenter l'utilisateur
    """
    if not user_name or not nom or not prenom:
        return False, "champ requis vide"
    if not email:
        return False, "vous devriez indiquer le mail de l'utilisateur créé !"
    # ce login existe ?
    user = _user_list(user_name)
    if edit and not user:  # safety net, le user_name ne devrait pas changer
        return False, "identifiant %s inexistant" % user_name
    if not edit and user:
        return False, "identifiant %s déjà utilisé" % user_name

    # Des noms/prénoms semblables existent ?
    nom = nom.lower().strip()
    prenom = prenom.lower().strip()
    similar_users = User.query.filter(
        User.nom.ilike(nom), User.prenom.ilike(prenom)
    ).all()
    if edit:
        minmatch = 1
    else:
        minmatch = 0
    if len(similar_users) > minmatch:
        return (
            False,
            "des utilisateurs proches existent: "
            + ", ".join(
                [
                    "%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
                    for x in similar_users
                ]
            ),
        )
    # Roles ?
    if not roles:
        return False, "aucun rôle sélectionné, êtes vous sûr ?"
    # ok
    return True, ""


def user_edit(user_name, vals):
    """Edit the user specified by user_name
    (ported from Zope to SQLAlchemy, hence strange !)
    """
    u = User.query.filter_by(user_name=user_name).first()
    if not u:
        raise ScoValueError("Invalid user_name")
    u.from_dict(vals)
    db.session.add(u)
    db.session.commit()
