<commit_before>'use strict';

import * as _ from 'lodash';
import * as conf from './conf';
import * as ldap from './ldap';
const filters = ldap.filters;

const maxLoginLength = 10;

const remove_accents = _.deburr;

export const onePerson = (filter) => (
    ldap.searchOne(conf.ldap.base_people, filter, conf.ldap.people.types, conf.ldap.people.attrs, {})
);

export const vuser = (user: CurrentUser) => {
    if (!user) return Promise.resolve(undefined);
    return onePerson(filters.eq("eduPersonPrincipalName", user.id)).then(vuser => {
        if (!vuser) console.error("unknown user", user)
        return vuser;
    })
};

export const structures = (token: string, sizeLimit: number) => {
    let words_filter = filters.fuzzy(['description', 'ou'], token);
    let many = [filters.eq("supannCodeEntite", token), 
                filters.and([ words_filter, "(supannCodeEntite=*)"])];
    return ldap.searchMany(conf.ldap.base_structures, many, 'key', conf.ldap.structures.types, conf.ldap.structures.attrs, {sizeLimit}).then(ldap.remove_dns);
};

export const etablissements = (token: string, sizeLimit: number) => {
    let filter;
    if (token.match(/\{.*/)) {
        filter = filters.eq("up1TableKey", token);
    } else if (token.match(/[0-9\s-]{5,14}/)) {
        filter = filters.eq("up1TableKey", conf.ldap.etablissements.attrs.siret.convert.toLdap(token) + "*");
    } else {
        const words_filter = filters.fuzzy(['description', 'cn', 'displayName'], token);
        filter = words_filter;
    }
    return ldap.search(conf.ldap.base_etablissements, filter, conf.ldap.etablissements.types, conf.ldap.etablissements.attrs, {sizeLimit})
};

function suggested_mail(sn: string, givenName: string) {
    let s = remove_accents(sn);
    if (givenName) {
        givenName = remove_accents(givenName);
        // prénom présent: on exclue les points et on le préfixe au sn
        s = givenName.replace(/\./g, '') + "." + s.replace(/\./g, '');
    }
    s = s.replace(/\([^()]*\)/g, ''); // suppr. texte entre prenthèses
    s = s.replace(/["',;:]/g, '');    // suppr. apostrophes et quotes et ponctuation
    s = s.replace(/[^a-z0-9.]+/gi, '-'); // remplace tous autres car. indésirables par "-"
    s = s.replace(/^-+|-+$/g, '');    // purge tirets+points début/fin
    s = s.replace(/-*\.-*/g, '.');    // purge tirets autour du "."
    return s;
}

function homonymes_filter(sns: string[], givenNames: string[], birthDay: Date, supannMailPerso?: string): ldap.filter {

    function cn_filter() {
        return filters.alike_no_accents('cn', sns[0] + '*' + (givenNames[0] || ""));
    }

    function sn_givenName_filter() {
        let l = [];
        l.push(filters.alike_many('sn', sns));

        if (givenNames && givenNames.length) {
            l.push(filters.alike_many('givenName', givenNames));
        }
        return filters.and(l);
    }

    function mail_filter() {
        return filters.startsWith("mail", suggested_mail(sns[0], givenNames[0]) + '@');
    }

    const [ birthDay_filter ] = subv_to_eq_filters({ birthDay });

    let l = [ filters.and([ cn_filter(), birthDay_filter ]),
              filters.and([ sn_givenName_filter(), birthDay_filter ]),
              filters.and([ mail_filter(), birthDay_filter ]) ];
    if (supannMailPerso) {
        l.push(filters.eq('supannMailPerso', supannMailPerso));
    }
    //console.log("homonymes_filter", l);
    return filters.or(l);
}

export type Homonyme = typeof conf.ldap.people.types & { score: number }

const _homonymes_accountStatus_score = {
    "active": 3,
    "": 2,
    "disabled": 1,
    "deleted": 0
}

function homonymes_scoring(l: typeof conf.ldap.people.types[], preferStudent: boolean): Homonyme[] {
    let l_ = _.map(l, e => {
      let scores = [
        ... (preferStudent ? [
            e.supannEtuId ? 1 : 0, // Apogée user
            (e.mailHost || '').match(/^malix/) ? 1 : 0,
        ] : []),
            _homonymes_accountStatus_score[e.accountStatus || ''] || 0,
            e.up1KrbPrincipal ? 1 : 0, // has password
            e.mailHost ? 1 : 0, // has email address
            e.supannEmpId ? 1 : 0, // SIHAM user. Why prefer it over ???
            e.global_eduPersonPrimaryAffiliation === 'student' ? 1 : 0, // still prefer student ???
      ];
      let score = scores.reduce((score, subscore) => score * 10 + subscore, 0);
      return <Homonyme> _.merge({ score }, e);
    });
    return _.sortBy(l_, 'score').reverse();
}

const homonymes_ = (sns: string[], givenNames: string[], birthDay: Date, supannMailPerso: string, preferStudent: boolean) : Promise<Homonyme[]> => {
    let filter = homonymes_filter(sns, givenNames, birthDay, supannMailPerso);
    if (conf.ldap.people.homonymes_restriction) {
        filter = filters.and([filter, conf.ldap.people.homonymes_restriction]);
        //console.log("homonymes filter", filter);
    }
    //console.log("homonymes", sns, givenNames, birthDay);
    const sizeLimit = 99; // big enough to handle many results, eg for "<NAME>"
    return ldap.search(conf.ldap.base_people, filter, conf.ldap.people.types, conf.ldap.people.attrs, { sizeLimit }).then(l => homonymes_scoring(l, preferStudent));
};

const _merge_at = (v: v, attrs) => <string[]> _.merge(_.at(<{}> v, attrs));

export const homonymes = (v: v) : Promise<Homonyme[]> => {
    let sns = _merge_at(v, conf.ldap.people.sns);
    let givenNames = _merge_at(v, conf.ldap.people.givenNames);
    if (sns[0] === undefined) return Promise.resolve([]);
    console.log("sns", sns);
    const preferStudent = conf.ldap.people.homonymes_preferStudent(v.profilename);
    return homonymes_(sns, givenNames, v.birthDay, v.supannMailPerso, preferStudent);    
};

export const subv_to_eq_filters = (subv: Partial<v>) => {
    // limitation: do not handle multi-valued attrs
    const v_ldap = ldap.convertToLdap(conf.ldap.people.attrs, conf.ldap.people.attrs, subv as any, {});
    return _.map(v_ldap, (val, attr) => filters.eq(attr, val as string));
}

export function searchPeople_matching_subvs<T extends {}>(subvs: Partial<v>[], token: string, attrTypes: T, options: ldap.Options): Promise<T[]> {
    const ors = subvs.map(subv => filters.and(subv_to_eq_filters(subv)));
    const filter = filters.and([
        filters.fuzzy(['displayName', 'cn'], token),
        filters.or(ors),<commit_msg>no homonymes if no birthDay<commit_after>'use strict';

import * as _ from 'lodash';
import * as conf from './conf';
import * as ldap from './ldap';
const filters = ldap.filters;

const maxLoginLength = 10;

const remove_accents = _.deburr;

export const onePerson = (filter) => (
    ldap.searchOne(conf.ldap.base_people, filter, conf.ldap.people.types, conf.ldap.people.attrs, {})
);

export const vuser = (user: CurrentUser) => {
    if (!user) return Promise.resolve(undefined);
    return onePerson(filters.eq("eduPersonPrincipalName", user.id)).then(vuser => {
        if (!vuser) console.error("unknown user", user)
        return vuser;
    })
};

export const structures = (token: string, sizeLimit: number) => {
    let words_filter = filters.fuzzy(['description', 'ou'], token);
    let many = [filters.eq("supannCodeEntite", token), 
                filters.and([ words_filter, "(supannCodeEntite=*)"])];
    return ldap.searchMany(conf.ldap.base_structures, many, 'key', conf.ldap.structures.types, conf.ldap.structures.attrs, {sizeLimit}).then(ldap.remove_dns);
};

export const etablissements = (token: string, sizeLimit: number) => {
    let filter;
    if (token.match(/\{.*/)) {
        filter = filters.eq("up1TableKey", token);
    } else if (token.match(/[0-9\s-]{5,14}/)) {
        filter = filters.eq("up1TableKey", conf.ldap.etablissements.attrs.siret.convert.toLdap(token) + "*");
    } else {
        const words_filter = filters.fuzzy(['description', 'cn', 'displayName'], token);
        filter = words_filter;
    }
    return ldap.search(conf.ldap.base_etablissements, filter, conf.ldap.etablissements.types, conf.ldap.etablissements.attrs, {sizeLimit})
};

function suggested_mail(sn: string, givenName: string) {
    let s = remove_accents(sn);
    if (givenName) {
        givenName = remove_accents(givenName);
        // prénom présent: on exclue les points et on le préfixe au sn
        s = givenName.replace(/\./g, '') + "." + s.replace(/\./g, '');
    }
    s = s.replace(/\([^()]*\)/g, ''); // suppr. texte entre prenthèses
    s = s.replace(/["',;:]/g, '');    // suppr. apostrophes et quotes et ponctuation
    s = s.replace(/[^a-z0-9.]+/gi, '-'); // remplace tous autres car. indésirables par "-"
    s = s.replace(/^-+|-+$/g, '');    // purge tirets+points début/fin
    s = s.replace(/-*\.-*/g, '.');    // purge tirets autour du "."
    return s;
}

function homonymes_filter(sns: string[], givenNames: string[], birthDay: Date, supannMailPerso?: string): ldap.filter {

    function cn_filter() {
        return filters.alike_no_accents('cn', sns[0] + '*' + (givenNames[0] || ""));
    }

    function sn_givenName_filter() {
        let l = [];
        l.push(filters.alike_many('sn', sns));

        if (givenNames && givenNames.length) {
            l.push(filters.alike_many('givenName', givenNames));
        }
        return filters.and(l);
    }

    function mail_filter() {
        return filters.startsWith("mail", suggested_mail(sns[0], givenNames[0]) + '@');
    }

    const [ birthDay_filter ] = subv_to_eq_filters({ birthDay });

    let l = [ filters.and([ cn_filter(), birthDay_filter ]),
              filters.and([ sn_givenName_filter(), birthDay_filter ]),
              filters.and([ mail_filter(), birthDay_filter ]) ];
    if (supannMailPerso) {
        l.push(filters.eq('supannMailPerso', supannMailPerso));
    }
    //console.log("homonymes_filter", l);
    return filters.or(l);
}

export type Homonyme = typeof conf.ldap.people.types & { score: number }

const _homonymes_accountStatus_score = {
    "active": 3,
    "": 2,
    "disabled": 1,
    "deleted": 0
}

function homonymes_scoring(l: typeof conf.ldap.people.types[], preferStudent: boolean): Homonyme[] {
    let l_ = _.map(l, e => {
      let scores = [
        ... (preferStudent ? [
            e.supannEtuId ? 1 : 0, // Apogée user
            (e.mailHost || '').match(/^malix/) ? 1 : 0,
        ] : []),
            _homonymes_accountStatus_score[e.accountStatus || ''] || 0,
            e.up1KrbPrincipal ? 1 : 0, // has password
            e.mailHost ? 1 : 0, // has email address
            e.supannEmpId ? 1 : 0, // SIHAM user. Why prefer it over ???
            e.global_eduPersonPrimaryAffiliation === 'student' ? 1 : 0, // still prefer student ???
      ];
      let score = scores.reduce((score, subscore) => score * 10 + subscore, 0);
      return <Homonyme> _.merge({ score }, e);
    });
    return _.sortBy(l_, 'score').reverse();
}

const homonymes_ = (sns: string[], givenNames: string[], birthDay: Date, supannMailPerso: string, preferStudent: boolean) : Promise<Homonyme[]> => {
    let filter = homonymes_filter(sns, givenNames, birthDay, supannMailPerso);
    if (conf.ldap.people.homonymes_restriction) {
        filter = filters.and([filter, conf.ldap.people.homonymes_restriction]);
        //console.log("homonymes filter", filter);
    }
    //console.log("homonymes", sns, givenNames, birthDay);
    const sizeLimit = 99; // big enough to handle many results, eg for "<NAME>"
    return ldap.search(conf.ldap.base_people, filter, conf.ldap.people.types, conf.ldap.people.attrs, { sizeLimit }).then(l => homonymes_scoring(l, preferStudent));
};

const _merge_at = (v: v, attrs) => <string[]> _.merge(_.at(<{}> v, attrs));

export const homonymes = (v: v) : Promise<Homonyme[]> => {
    let sns = _merge_at(v, conf.ldap.people.sns);
    let givenNames = _merge_at(v, conf.ldap.people.givenNames);
    if (sns[0] === undefined || !v.birthDay) return Promise.resolve([]);
    console.log("sns", sns);
    const preferStudent = conf.ldap.people.homonymes_preferStudent(v.profilename);
    return homonymes_(sns, givenNames, v.birthDay, v.supannMailPerso, preferStudent);    
};

export const subv_to_eq_filters = (subv: Partial<v>) => {
    // limitation: do not handle multi-valued attrs
    const v_ldap = ldap.convertToLdap(conf.ldap.people.attrs, conf.ldap.people.attrs, subv as any, {});
    return _.map(v_ldap, (val, attr) => filters.eq(attr, val as string));
}

export function searchPeople_matching_subvs<T extends {}>(subvs: Partial<v>[], token: string, attrTypes: T, options: ldap.Options): Promise<T[]> {
    const ors = subvs.map(subv => filters.and(subv_to_eq_filters(subv)));
    const filter = filters.and([
        filters.fuzzy(['displayName', 'cn'], token),
        filters.or(ors),
{'fraction_non_alphanumeric': 0.1687278456085073, 'fraction_numerical': 0.006144151240645924, 'mean_word_length': 3.229180546302465, 'pattern_counts': {'":': 8, '<': 23, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1739886', 'n_tokens_mistral': 4389, 'n_tokens_neox': 4285, 'n_words': 920}