from flask_login import current_user
from wtforms.validators import ValidationError
from app.models.users import Member
from app.utils.valid_name import valid_name
from app.utils.unicode_names import normalize
import app.utils.ldap as ldap
from config import V5Config


def name_valid(form, name):
    valid = valid_name(name.data)
    default = "Nom d'utilisateur invalide (erreur interne)"
    msg = {
        "too-short":
            "Le nom d'utilisateur doit faire au moins "
            f"{V5Config.USER_NAME_MINLEN} caractères.",
        "too-long":
            "Le nom d'utilisateur doit faire au plus "
            f"{V5Config.USER_NAME_MAXLEN} caractères.",
        "cant-normalize":
            "Ce nom d'utilisateur contient des caractères interdits. Les "
            "caractères autorisés sont les lettres, lettres accentuées, "
            'chiffres ainsi que "-" (tiret), "." (point), "~" (tilde) et '
            '"_" (underscore).',
        "no-letter":
            "Le nom d'utilisateur doit contenir au moins une lettre.",
        "forbidden":
            "Ce nom d'utilisateur est interdit."
    }
    if valid is not True:
        err = ' '.join(msg.get(code, default) for code in valid)
        raise ValidationError(err)


def name_available(form, name):
    # If the name is invalid, name_valid() will return a meaningful message
    try:
        norm = normalize(name.data)
    except ValueError:
        return

    member = Member.query.filter_by(norm=norm).first()
    if member is not None:
        raise ValidationError("Ce nom d'utilisateur est indisponible.")

    # Double check with LDAP if needed
    if V5Config.USE_LDAP:
        member = ldap.get_member(norm)
        if member is not None:
            raise ValidationError("Ce nom d'utilisateur est indisponible.")



def email(form, email):
    member = Member.query.filter_by(email=email.data).first()
    if member is not None:
        raise ValidationError('Adresse email déjà utilisée.')


def password(form, password):
    # To avoid errors in forms where password is optionnal
    if len(password.data) == 0:
        return

    errors = []
    if len(password.data) < V5Config.PASSWORD_MINLEN:
        errors.append('Le mot de passe doit faire au moins '
            f'{V5Config.PASSWORD_MINLEN} caractères.')

    checks = set()
    for c in password.data:
        if c in "abcdefghijklmnopqrstuvwxyz":
            checks.add('lower')
        elif c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
            checks.add('upper')
        elif c in "0123456789":
            checks.add('numeric')
        else:
            checks.add('other')

    missing = []
    if 'lower' not in checks:
        missing.append('une minuscule')
    if 'upper' not in checks:
        missing.append('une majuscule')
    if 'numeric' not in checks:
        missing.append('un chiffre')
    if 'other' not in checks:
        missing.append('un caractère spécial')

    if missing != []:
        errors.append('Le mot de passe doit aussi contenir ' + ', '.join(missing) + '.')

    if errors != []:
        raise ValidationError(' '.join(errors))


def avatar(form, avatar):
    pass


def old_password(form, field):
    if field.data:
        if not form.old_password.data:
            raise ValidationError('Votre ancien mot de passe est requis pour cette modification.')
        if not current_user.check_password(form.old_password.data):
            raise ValidationError('Mot de passe actuel erroné.')


def id_exists(object):
    """Check if an id exists in a table"""
    def _id_exists(form, id):
        try:
            id = int(id.data)
        except ValueError:
            raise ValidationError('L\'id n\'est pas un entier valide')
        r = object.query.filter_by(id=id)
        if not r:
            raise ValidationError('L\'id n\'existe pas dans la BDD')
    return _id_exists


def css(form, css):
    """Check if input is valid and sane CSS"""
    pass
