/**
 * Fichier  : assets/js/pages/ldap-settings.js
 * Auteur   : KREMER Régis
 * Version  : 2.6.113
 * Date     : 2026-07-21
 * Description : Interface administrateur LDAP/Active Directory, diagnostics et mapping des droits.
 *   v2.6.113 (2026-07-22) — Mapping du rôle Magasinier.
 */
const LdapSettingsPage = {
    config: null,
    diagnostics: null,
    warehouses: [],
    PERMISSIONS: [
        ['products.read','Produits'],['stock.read','Stock'],['movements.read','Lire mouvements'],['movements.create','Créer mouvements'],['movements.delete','Supprimer mouvements'],
        ['inventory.read','Lire inventaires'],['inventory.write','Gérer inventaires'],['analytics.read','Pilotage'],['price.read','Prix'],['valuation.read','Valorisation'],
        ['kits.read','Voir kits'],['kits.manage','Gérer kits'],['lots.read','Voir lots'],['lots.manage','Gérer lots'],['reservations.read','Voir réservations'],['reservations.manage','Gérer réservations'],
        ['product.versions.read','Historique produits'],['plans.read','Voir plans'],['plans.manage','Modifier plans'],
        ['warehouses.manage','Gérer magasins'],['locations.manage','Gérer emplacements'],['users.manage','Utilisateurs'],['settings.write','Paramètres'],['admin.access','Zone admin']
    ],
    PRESETS: {
        active_directory: {
            userFilter:'(&(objectCategory=person)(objectClass=user)(sAMAccountName={login}))',loginAttribute:'sAMAccountName',stableIdAttribute:'objectGUID',
            displayNameAttribute:'displayName',firstNameAttribute:'givenName',lastNameAttribute:'sn',emailAttribute:'mail',memberOfAttribute:'memberOf',
            disabledAttribute:'userAccountControl',groupFilter:'(&(objectClass=group)(member={dn}))'
        },
        openldap: {
            userFilter:'(&(objectClass=inetOrgPerson)(uid={login}))',loginAttribute:'uid',stableIdAttribute:'entryUUID',displayNameAttribute:'cn',
            firstNameAttribute:'givenName',lastNameAttribute:'sn',emailAttribute:'mail',memberOfAttribute:'memberOf',disabledAttribute:'pwdAccountLockedTime',
            groupFilter:'(&(objectClass=groupOfNames)(member={dn}))'
        },
        freeipa: {
            userFilter:'(&(objectClass=person)(uid={login}))',loginAttribute:'uid',stableIdAttribute:'ipaUniqueID',displayNameAttribute:'cn',
            firstNameAttribute:'givenName',lastNameAttribute:'sn',emailAttribute:'mail',memberOfAttribute:'memberOf',disabledAttribute:'nsAccountLock',
            groupFilter:'(&(objectClass=groupOfNames)(member={dn}))'
        },
        generic: {
            userFilter:'(&(objectClass=person)(uid={login}))',loginAttribute:'uid',stableIdAttribute:'entryUUID',displayNameAttribute:'cn',
            firstNameAttribute:'givenName',lastNameAttribute:'sn',emailAttribute:'mail',memberOfAttribute:'memberOf',disabledAttribute:'',
            groupFilter:'(&(objectClass=groupOfNames)(member={dn}))'
        }
    },
    el(id){ return document.getElementById(id); },
    esc(value){ return String(value ?? '').replace(/[&<>'"]/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c])); },
    async init(){
        if(!this.el('ldapSettingsSection')) return;
        this.bindEvents();
        try {
            const [payload, warehouses] = await Promise.all([Api.ldapConfig(), Api.warehouses().catch(()=>[])]);
            this.config = payload.config || {};
            this.diagnostics = payload.diagnostics || {};
            this.warehouses = Array.isArray(warehouses) ? warehouses : [];
            this.renderConfig(this.config);
            this.renderDiagnostics(this.diagnostics, this.config);
        } catch(error){
            this.setResult(error.message || 'Impossible de charger la configuration LDAP.', 'error');
            this.el('ldapGlobalStatus').textContent='Indisponible';
            this.el('ldapGlobalStatus').className='ldap-status-badge is-error';
        }
    },
    bindEvents(){
        const on=(id,event,fn)=>this.el(id)?.addEventListener(event,fn);
        on('ldapDirectoryType','change',()=>this.applyPreset(this.el('ldapDirectoryType').value));
        ['ldapMode','ldapEnabled'].forEach(id=>on(id,'change',()=>this.syncPriorityUI()));
        ['ldapEncryption1','ldapEncryption2'].forEach((id,index)=>on(id,'change',()=>{
            const value=this.el(id).value;
            const port=this.el(index===0?'ldapPort1':'ldapPort2');
            if(port && (!port.value || ['389','636'].includes(port.value))) port.value=value==='ldaps'?'636':'389';
        }));
        on('ldapAddMapping','click',()=>this.addMapping({}));
        on('ldapSaveConfig','click',()=>this.saveConfig());
        on('ldapSaveSecret','click',()=>this.saveSecret());
        on('ldapDeleteSecret','click',()=>this.deleteSecret());
        on('ldapSaveCertificate','click',()=>this.saveCertificate());
        on('ldapDeleteCertificate','click',()=>this.deleteCertificate());
        on('ldapTestConnection','click',()=>this.runTest('Connexion serveur',cfg=>Api.ldapTestConnection(cfg)));
        on('ldapTestBind','click',()=>this.runTest('Compte technique',cfg=>Api.ldapTestBind(cfg)));
        on('ldapTestSearch','click',()=>this.runUserTest('Recherche utilisateur',(u,cfg)=>Api.ldapTestSearch(u,cfg)));
        on('ldapSimulate','click',()=>this.runUserTest('Simulation des droits',(u,cfg)=>Api.ldapSimulate(u,cfg)));
        on('ldapTestAuthentication','click',()=>this.testAuthentication());
        on('ldapSyncKnownUsers','click',()=>this.syncUsers());
        on('ldapLoadAudit','click',()=>this.loadAudit());
    },
    renderConfig(cfg){
        const put=(id,value)=>{const el=this.el(id);if(!el)return;if(el.type==='checkbox')el.checked=!!value;else el.value=value??'';};
        put('ldapName',cfg.name);put('ldapDirectoryType',cfg.directoryType);put('ldapMode',cfg.mode);put('ldapEnabled',cfg.enabled);put('ldapLocalFallback',cfg.localFallbackAdminEnabled);
        (cfg.servers||[]).slice(0,2).forEach((server,i)=>{const n=i+1;put('ldapHost'+n,server.host);put('ldapPort'+n,server.port);put('ldapEncryption'+n,server.encryption);put('ldapTimeout'+n,server.timeout);});
        put('ldapVerifyCertificate',cfg.verifyCertificate);put('ldapReferrals',cfg.referrals);put('ldapBaseDn',cfg.baseDn);put('ldapBindDn',cfg.bindDn);put('ldapUserBaseDn',cfg.userBaseDn);put('ldapUserFilter',cfg.userFilter);put('ldapAnonymousBind',cfg.allowAnonymousBind);
        ['loginAttribute','stableIdAttribute','displayNameAttribute','firstNameAttribute','lastNameAttribute','emailAttribute','memberOfAttribute','disabledAttribute','groupBaseDn','groupFilter'].forEach(key=>put('ldap'+key[0].toUpperCase()+key.slice(1),cfg[key]));
        put('ldapDisabledValues',(cfg.disabledValues||[]).join(', '));
        put('ldapNestedGroups',cfg.nestedGroups);put('ldapDenyUnmapped',cfg.denyUnmappedUsers);put('ldapSyncOnLogin',cfg.syncOnLogin);put('ldapDisableMissing',cfg.disableMissingUsers);
        this.el('ldapSecretState').textContent=cfg.secretConfigured?'Configuré — valeur non affichée':'Non configuré';
        this.el('ldapCertificateState').textContent=cfg.caCertificateConfigured?'Configuré — contenu non affiché':'Non configuré';
        this.el('ldapGroupMappings').innerHTML='';
        (cfg.groupMappings||[]).forEach(mapping=>this.addMapping(mapping));
        if(!(cfg.groupMappings||[]).length) this.addMapping({group:'MageStock-Utilisateurs',role:'user',allowedWarehouses:[],permissions:{'products.read':true,'stock.read':true,'movements.read':true,'movements.create':true}});
        this.syncPriorityUI();
    },
    renderDiagnostics(diag,cfg){
        const items=[
            ['Extension PHP LDAP',diag.extensionLoaded,'Installée','Absente'],['OpenSSL',diag.opensslLoaded,'Disponible','Absent'],
            ['Stockage des secrets',diag.secretDirectoryWritable,'Protégé et accessible','Non inscriptible'],['Secret technique',diag.secretConfigured,'Configuré','Manquant'],
            ['Certificat CA',diag.caCertificateConfigured,'Configuré','Système / absent'],['Clé maître',!!diag.masterKeySource,diag.masterKeySource==='environment'?'Variable serveur':'Fichier local protégé','Indisponible'],['Admin local de secours',diag.emergencyLocalAdminAvailable,'Disponible','Introuvable'],
            ['Dernier test complet',!!diag.lastSuccessfulTestAt,diag.lastSuccessfulTestAt?Locale.formatDateTime(diag.lastSuccessfulTestAt):'Jamais','Jamais'],
            ['Mode actif',true,(cfg.enabled?cfg.mode:'local')]
        ];
        this.el('ldapDiagnosticsGrid').innerHTML=items.map(([label,ok,yes,no])=>`<div class="ldap-diagnostic ${ok?'is-ok':'is-error'}"><span>${this.esc(label)}</span><strong>${this.esc(ok?yes:no)}</strong></div>`).join('');
        const status=this.el('ldapGlobalStatus');
        if(!diag.extensionLoaded){status.textContent='Extension LDAP absente';status.className='ldap-status-badge is-error';}
        else if(cfg.enabled){status.textContent=cfg.mode==='ldap_priority'?'LDAP prioritaire':'LDAP hybride';status.className='ldap-status-badge is-ok';}
        else {status.textContent='Désactivé';status.className='ldap-status-badge is-warning';}
    },
    applyPreset(type){
        const preset=this.PRESETS[type]||this.PRESETS.generic;
        Object.entries(preset).forEach(([key,value])=>{const id='ldap'+key[0].toUpperCase()+key.slice(1);const el=this.el(id);if(el)el.value=value;});
    },
    syncPriorityUI(){
        const priority=this.el('ldapMode')?.value==='ldap_priority' && this.el('ldapEnabled')?.checked;
        this.el('ldapPriorityConfirm').hidden=!priority;
        if(priority) this.el('ldapLocalFallback').checked=true;
        this.el('ldapLocalFallback').disabled=priority;
    },
    selectedWarehouses(select){ return Array.from(select?.selectedOptions||[]).map(o=>Number(o.value)).filter(Boolean); },
    collectMappings(){
        return Array.from(document.querySelectorAll('.ldap-mapping-row')).map(row=>{
            const permissions={};row.querySelectorAll('[data-ldap-permission]').forEach(cb=>{if(cb.checked)permissions[cb.dataset.ldapPermission]=true;});
            return {group:row.querySelector('[data-map-group]').value.trim(),role:row.querySelector('[data-map-role]').value,allowedWarehouses:this.selectedWarehouses(row.querySelector('[data-map-warehouses]')),permissions};
        }).filter(m=>m.group);
    },
    addMapping(mapping={}){
        const row=document.createElement('div');row.className='ldap-mapping-row';
        const selected=(mapping.allowedWarehouses||[]).map(String);
        const whOptions=this.warehouses.map(w=>`<option value="${this.esc(w.id)}" ${selected.includes(String(w.id))?'selected':''}>${this.esc(w.name||w.label||('Magasin '+w.id))}</option>`).join('');
        const perms=mapping.permissions||{};
        row.innerHTML=`<label>Groupe LDAP<input class="form-input ldap-mono" data-map-group value="${this.esc(mapping.group||'')}" placeholder="CN=MageStock-Agents,OU=Groupes,..."></label>
            <label>Rôle<select class="form-select" data-map-role><option value="user" ${(mapping.role||'user')==='user'?'selected':''}>Utilisateur</option><option value="magasinier" ${mapping.role==='magasinier'?'selected':''}>Magasinier</option><option value="admin" ${mapping.role==='admin'?'selected':''}>Administrateur</option></select></label>
            <label>Magasins autorisés<select class="form-select ldap-warehouse-select" data-map-warehouses multiple>${whOptions}</select></label>
            <button class="btn btn-ghost text-danger ldap-remove-mapping" type="button">Retirer</button>
            <div class="ldap-mapping-permissions">${this.PERMISSIONS.map(([key,label])=>`<label class="ldap-permission-chip"><input type="checkbox" data-ldap-permission="${key}" ${perms[key]?'checked':''}>${this.esc(label)}</label>`).join('')}</div>`;
        row.querySelector('.ldap-remove-mapping').onclick=()=>row.remove();
        this.el('ldapGroupMappings').appendChild(row);
    },
    collectConfig(forceDisabled=false){
        const value=id=>this.el(id)?.value?.trim?.()??'';
        const checked=id=>!!this.el(id)?.checked;
        return {
            name:value('ldapName'),directoryType:value('ldapDirectoryType'),mode:value('ldapMode'),enabled:forceDisabled?false:checked('ldapEnabled'),localFallbackAdminEnabled:checked('ldapLocalFallback'),
            servers:[1,2].map(n=>({host:value('ldapHost'+n),port:Number(value('ldapPort'+n)||636),encryption:value('ldapEncryption'+n),timeout:Number(value('ldapTimeout'+n)||5)})),
            verifyCertificate:checked('ldapVerifyCertificate'),referrals:checked('ldapReferrals'),baseDn:value('ldapBaseDn'),bindDn:value('ldapBindDn'),allowAnonymousBind:checked('ldapAnonymousBind'),
            userBaseDn:value('ldapUserBaseDn'),userFilter:value('ldapUserFilter'),loginAttribute:value('ldapLoginAttribute'),stableIdAttribute:value('ldapStableIdAttribute'),displayNameAttribute:value('ldapDisplayNameAttribute'),
            firstNameAttribute:value('ldapFirstNameAttribute'),lastNameAttribute:value('ldapLastNameAttribute'),emailAttribute:value('ldapEmailAttribute'),memberOfAttribute:value('ldapMemberOfAttribute'),disabledAttribute:value('ldapDisabledAttribute'),
            disabledValues:value('ldapDisabledValues').split(',').map(v=>v.trim()).filter(Boolean),groupBaseDn:value('ldapGroupBaseDn'),groupFilter:value('ldapGroupFilter'),nestedGroups:checked('ldapNestedGroups'),denyUnmappedUsers:checked('ldapDenyUnmapped'),syncOnLogin:checked('ldapSyncOnLogin'),disableMissingUsers:checked('ldapDisableMissing'),
            defaultRole:'user',defaultAllowedWarehouses:[],defaultPermissions:{},groupMappings:this.collectMappings()
        };
    },
    draftConfig(){ return this.collectConfig(true); },
    async saveConfig(){
        try{
            const config=this.collectConfig(false);
            const confirmation=this.el('ldapPriorityPhrase')?.value?.trim()||'';
            const payload=await Api.ldapSaveConfig(config,confirmation);
            this.config=payload.config;this.diagnostics=payload.diagnostics;this.renderConfig(this.config);this.renderDiagnostics(this.diagnostics,this.config);
            toast('Configuration LDAP enregistrée','success');this.setResult({message:'Configuration enregistrée.',mode:payload.config.mode,enabled:payload.config.enabled},'success');
        }catch(error){this.handleError(error);}
    },
    async saveSecret(){
        const input=this.el('ldapBindSecret');const secret=input.value;
        if(!secret){toast('Saisissez le mot de passe du compte technique','warning');return;}
        try{await Api.ldapSetSecret(secret);input.value='';this.el('ldapSecretState').textContent='Configuré — valeur non affichée';toast('Secret LDAP chiffré et enregistré','success');await this.refreshDiagnostics();}
        catch(error){this.handleError(error);}finally{input.value='';}
    },
    async deleteSecret(){ if(!confirm('Supprimer le secret du compte technique LDAP ?'))return;try{await Api.ldapDeleteSecret();this.el('ldapSecretState').textContent='Non configuré';await this.refreshDiagnostics();toast('Secret supprimé','success');}catch(error){this.handleError(error);} },
    async saveCertificate(){const pem=this.el('ldapCertificatePem').value.trim();if(!pem){toast('Collez le certificat PEM','warning');return;}try{await Api.ldapSetCertificate(pem);this.el('ldapCertificatePem').value='';this.el('ldapCertificateState').textContent='Configuré — contenu non affiché';await this.refreshDiagnostics();toast('Certificat CA enregistré','success');}catch(error){this.handleError(error);}},
    async deleteCertificate(){if(!confirm('Supprimer le certificat CA privé ?'))return;try{await Api.ldapDeleteCertificate();this.el('ldapCertificateState').textContent='Non configuré';await this.refreshDiagnostics();toast('Certificat supprimé','success');}catch(error){this.handleError(error);}},
    async refreshDiagnostics(){const diag=await Api.ldapDiagnostics();this.diagnostics=diag;this.renderDiagnostics(diag,this.config||{});},
    async runTest(label,fn){
        const button=document.activeElement; if(button?.classList?.contains('btn'))button.disabled=true;
        this.setResult(`${label} en cours…`);
        try{const result=await fn(this.draftConfig());this.setResult({test:label,status:'réussi',...result},'success');toast(`${label} réussi`,'success');await this.refreshDiagnostics();return result;}
        catch(error){this.handleError(error,label);throw error;}finally{if(button?.classList?.contains('btn'))button.disabled=false;}
    },
    async runUserTest(label,fn){const username=this.el('ldapTestUsername').value.trim();if(!username){toast('Saisissez un utilisateur de test','warning');return;}try{return await this.runTest(label,cfg=>fn(username,cfg));}catch(_){return null;}},
    async testAuthentication(){
        const username=this.el('ldapTestUsername').value.trim(),password=this.el('ldapTestPassword').value;
        if(!username||!password){toast('Identifiant et mot de passe de test obligatoires','warning');return;}
        try{const result=await this.runTest('Authentification complète',cfg=>Api.ldapTestAuthentication(username,password,cfg));if(result?.config){this.config=result.config;await this.refreshDiagnostics();}}
        finally{this.el('ldapTestPassword').value='';}
    },
    async syncUsers(){if(!confirm('Synchroniser maintenant tous les comptes LDAP déjà connus ?'))return;try{const result=await this.runTest('Synchronisation des comptes',()=>Api.ldapSyncKnownUsers());toast(`${result.updated||0} compte(s) synchronisé(s)`,'success');}catch(_){ }},
    async loadAudit(){try{const entries=await Api.ldapAudit();this.setResult({journal:entries},'');}catch(error){this.handleError(error,'Journal LDAP');}},
    setResult(value,type=''){
        const out=this.el('ldapTestResult');if(!out)return;out.className='ldap-test-result'+(type?' is-'+type:'');
        out.textContent=typeof value==='string'?value:JSON.stringify(value,null,2);
    },
    handleError(error,label='LDAP'){
        const message=error?.message||'Erreur LDAP';const payload=error?.payload||{};
        this.setResult({test:label,status:'échec',message,code:payload.code||'unknown'},'error');toast(message,'error');
    }
};
