/* BDF - Copyright (c) 2015-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.servlets;

import fr.exemole.bdfserver.api.BdfExtensionInitializer;
import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.BdfServerConstants;
import fr.exemole.bdfserver.api.interaction.InteractionConstants;
import fr.exemole.bdfserver.api.session.LoginException;
import fr.exemole.bdfserver.api.session.SessionObserver;
import fr.exemole.bdfserver.api.users.BdfUser;
import fr.exemole.bdfserver.api.users.BdfUserConstants;
import fr.exemole.bdfserver.conf.ConfConstants;
import fr.exemole.bdfserver.conf.WebappDirs;
import fr.exemole.bdfserver.email.SmtpManager;
import fr.exemole.bdfserver.impl.BdfServerImpl;
import fr.exemole.bdfserver.multi.MultiConf;
import fr.exemole.bdfserver.multi.api.Multi;
import fr.exemole.bdfserver.multi.api.MultiConstants;
import fr.exemole.bdfserver.multi.api.central.CentralSphere;
import fr.exemole.bdfserver.multi.api.central.CentralUser;
import fr.exemole.bdfserver.multi.subscribe.SubscribeManager;
import fr.exemole.bdfserver.multi.subscribe.SubscribeUtils;
import fr.exemole.bdfserver.tools.storage.Storages;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import net.fichotheque.Fichotheque;
import net.fichotheque.sphere.LoginKey;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.sphere.Sphere;
import net.mapeadores.util.buildinfo.BuildInfo;
import net.mapeadores.util.io.ResourceStorages;
import net.mapeadores.util.mimetype.MimeTypeResolver;
import net.mapeadores.util.mimetype.MimeTypeUtils;
import net.mapeadores.util.models.EmailCore;
import net.mapeadores.util.security.PasswordChecker;
import net.mapeadores.util.servlets.HttpServletRequestMap;
import net.mapeadores.util.servlets.ServletUtils;
import net.mapeadores.util.smtp.SmtpParameters;


/**
 *
 * @author Vincent Calame
 */
public class BDFWebapp {

    public final static String BDFWEBAPP_KEY = "fr.exemole.BDFWebapp";
    public final static String BDFSESSION_KEY = "fr.exemole.BDFSession";
    public final static String BDFBINDING_KEY = "fr.exemole.BDFBinding";
    private final WebappDirs webappDirs;
    private final boolean loginPasswordDisabled;
    private final SmtpManager smtpManager;
    private final Map<String, BDFSession> sessionsMap = new LinkedHashMap<String, BDFSession>();
    private final BDFBinding bdfBinding = new BDFBinding();
    private final int maxInactiveInterval = 18000;
    private final MultiImpl multiImpl;
    private final Unique unique;
    private final SubscribeManager subscribeManager;

    private BDFWebapp(String servletContextName, BDFInitParameters initParameters) {
        this.webappDirs = initParameters.webappDirs();
        this.loginPasswordDisabled = initParameters.loginPasswordDisabled();
        this.smtpManager = initParameters.smtpManager();
        if (initParameters.multiBdf()) {
            multiImpl = new MultiImpl(this, servletContextName, initParameters);
            unique = null;
        } else {
            multiImpl = null;
            unique = new Unique(webappDirs);
        }
        this.subscribeManager = initSubscribeManager(initParameters);
    }

    public boolean isMultiBdf() {
        return (multiImpl != null);
    }

    public Multi getMulti() {
        return multiImpl;
    }

    public Unique getUnique() {
        return unique;
    }

    public boolean isLoginPasswordDisabled() {
        return loginPasswordDisabled;
    }

    public WebappDirs getWebappDirs() {
        return webappDirs;
    }

    public SmtpManager getSmtpManager() {
        return smtpManager;
    }

    public SubscribeManager getSubscribeManager() {
        return subscribeManager;
    }

    public short checkMultiAdmin(HttpServletRequestMap requestMap) {
        BDFSession bdfSession = getCurrentBDFSession(requestMap);
        if ((bdfSession != null) && (bdfSession.isMultiAdmin())) {
            return MultiConf.CONNECTED_STATE;
        }
        short check = multiImpl.getMultiConf().checkAuthentification(requestMap, loginPasswordDisabled);
        if (check == MultiConf.CONNECTED_STATE) {
            if (bdfSession == null) {
                bdfSession = createBDFSession(requestMap);
            }
            bdfSession.setMultiAdmin(true);
        }
        return check;
    }


    public synchronized void invalidateBdfUser(HttpServletRequestMap requestMap, String fichothequeName) {
        BDFSession bdfSession = getCurrentBDFSession(requestMap);
        if (bdfSession == null) {
            return;
        }
        BdfUser bdfUser = bdfSession.getBdfUser(fichothequeName);
        if (bdfUser == null) {
            return;
        }
        LoginKey loginKey = bdfUser.getLoginKey();
        if ((multiImpl != null) && (multiImpl.isAuthentificationSharingAllowed(loginKey))) {
            bdfSession.logoutAll(loginKey);
        } else {
            bdfSession.logout(fichothequeName);
        }
        checkSession(bdfSession);
    }

    public synchronized void invalidateCentralUser(HttpServletRequestMap requestMap) {
        BDFSession bdfSession = getCurrentBDFSession(requestMap);
        if (bdfSession == null) {
            return;
        }
        CentralUser centralUser = bdfSession.getCentralUser();
        if (centralUser == null) {
            return;
        }
        bdfSession.logoutAll(centralUser.getLoginKey());
        checkSession(bdfSession);
    }

    public synchronized CentralUser getOrCreateCentralUser(HttpServletRequestMap requestMap) throws LoginException {
        BDFSession bdfSession = getCurrentBDFSession(requestMap);
        if (bdfSession != null) {
            CentralUser centralUser = bdfSession.getCentralUser();
            if (centralUser != null) {
                return centralUser;
            }
            if (!BDFUtils.hasLogin(requestMap)) {
                centralUser = getMatchingCentralUser(requestMap, bdfSession);
                if (centralUser != null) {
                    return centralUser;
                }
            }
        }
        return createCentralUser(requestMap, bdfSession);
    }

    public synchronized BdfUser getOrCreateBdfUser(HttpServletRequestMap requestMap, BdfServer bdfServer) throws LoginException {
        BDFSession bdfSession = getCurrentBDFSession(requestMap);
        if (bdfSession != null) {
            BdfUser bdfUser = bdfSession.getBdfUser(bdfServer.getName());
            if (bdfUser != null) {
                bdfUser.putParameter(BdfUserConstants.SESSION_LASTACCESSEDTIME, System.currentTimeMillis());
                return bdfUser;
            }
            if (!BDFUtils.hasLogin(requestMap)) {
                bdfUser = getMatchingBdfUser(bdfServer, requestMap, bdfSession);
                if (bdfUser != null) {
                    return bdfUser;
                }
            }
        }
        return createBdfUser(bdfServer, requestMap, bdfSession);
    }


    public BDFSession getMatchingSession(BdfUser bdfUser) {
        String fichothequeName = bdfUser.getBdfServerName();
        for (BDFSession bdfSession : sessionsMap.values()) {
            if (bdfSession.containsBdfUser(fichothequeName, bdfUser)) {
                return bdfSession;
            }
        }
        return null;
    }

    public SessionObserver getSessionObserverInstance(String fichothequeName) {
        return new InternalSessionObserver(fichothequeName);
    }

    public static BDFWebapp build(ServletContext servletContext) {
        String servletContextName = ServletUtils.getServletContextName(servletContext);
        BDFInitParameters initParameters = BDFInitParametersEngine.run(servletContext);
        return new BDFWebapp(servletContextName, initParameters);
    }

    private CentralUser getMatchingCentralUser(HttpServletRequestMap requestMap, BDFSession bdfSession) {
        if (multiImpl == null) {
            return null;
        }
        for (LoginKey loginKey : bdfSession.getLoginKeySet()) {
            if (multiImpl.isAuthentificationSharingAllowed(loginKey)) {
                CentralSphere centralSphere = multiImpl.getCentralSphere(loginKey.getSphereName());
                if (centralSphere != null) {
                    CentralUser centralUser = centralSphere.getCentralUser(loginKey.getLogin());
                    if (centralUser != null) {
                        return centralUser;
                    }
                }
            }
        }
        return null;
    }

    private BdfUser getMatchingBdfUser(BdfServer bdfServer, HttpServletRequestMap requestMap, BDFSession bdfSession) {
        if (multiImpl == null) {
            return null;
        }
        Fichotheque fichotheque = bdfServer.getFichotheque();
        for (LoginKey loginKey : bdfSession.getLoginKeySet()) {
            if (multiImpl.isAuthentificationSharingAllowed(loginKey)) {
                Sphere sphere = (Sphere) fichotheque.getSubset(loginKey.getSphereKey());
                if (sphere != null) {
                    Redacteur existingRedacteur = sphere.getRedacteurByLogin(loginKey.getLogin());
                    if ((existingRedacteur != null) && (!existingRedacteur.isInactive())) {
                        return createBdfUser(bdfServer, requestMap, bdfSession, existingRedacteur);
                    }
                }
            }
        }
        return null;
    }

    private BdfUser createBdfUser(BdfServer bdfServer, HttpServletRequestMap requestMap, BDFSession bdfSession) throws LoginException {
        Redacteur redacteur = BDFUtils.getRedacteur(bdfServer, requestMap);
        if (redacteur.isInactive()) {
            throw new LoginException(LoginException.INACTIVE_ERROR);
        }
        if (!loginPasswordDisabled) {
            String bdfPassword = requestMap.getParameter(InteractionConstants.BDF_PASSWORD_PARAMNAME);
            if (bdfPassword == null) {
                throw new LoginException(LoginException.UNDEFINED_ERROR);
            }
            if (!bdfServer.getPasswordManager().checkPassword(redacteur.getGlobalId(), bdfPassword)) {
                throw new LoginException(LoginException.PASSWORD_ERROR);
            }
        }
        if (bdfSession == null) {
            bdfSession = createBDFSession(requestMap);
        }
        return createBdfUser(bdfServer, requestMap, bdfSession, redacteur);
    }


    private BdfUser createBdfUser(BdfServer bdfServer, HttpServletRequestMap requestMap, BDFSession bdfSession, Redacteur redacteur) {
        BdfUser bdfUser = bdfServer.createBdfUser(redacteur);
        bdfSession.login(bdfServer.getName(), bdfUser);
        bdfServer.getSelectionManager().updateFicheSelection(bdfUser);
        long currentTime = System.currentTimeMillis();
        boolean withJavascript = true;
        if (requestMap.isTrue(InteractionConstants.BDF_NOSCRIPT_PARAMNAME)) {
            withJavascript = false;
        }
        bdfUser.putParameter(BdfUserConstants.SESSION_CREATIONTIME, currentTime);
        bdfUser.putParameter(BdfUserConstants.SESSION_LASTACCESSEDTIME, currentTime);
        bdfUser.putParameter(BdfUserConstants.HTML_WITHJAVASCRIPT, withJavascript);
        bdfUser.putParameter(BdfUserConstants.SESSION_ROOTURL, BDFUtils.getRootUrl(requestMap.getRequest(), bdfServer.getName()));
        return bdfUser;
    }

    private CentralUser createCentralUser(HttpServletRequestMap requestMap, BDFSession bdfSession) throws LoginException {
        CentralUser centralUser = BDFUtils.getCentralUser(requestMap, multiImpl);
        if (!loginPasswordDisabled) {
            String bdfPassword = requestMap.getParameter(InteractionConstants.BDF_PASSWORD_PARAMNAME);
            if (bdfPassword == null) {
                throw new LoginException(LoginException.UNDEFINED_ERROR);
            }
            String encryptedPassword = centralUser.getEncryptedPassword();
            if (encryptedPassword.isEmpty()) {
                throw new LoginException(LoginException.PASSWORD_ERROR);
            }
            if (!PasswordChecker.check(bdfPassword, encryptedPassword)) {
                throw new LoginException(LoginException.PASSWORD_ERROR);
            }
        }
        if (bdfSession == null) {
            bdfSession = createBDFSession(requestMap);
        }
        bdfSession.centralLogin(centralUser);
        return centralUser;
    }

    public BDFSession getCurrentBDFSession(HttpServletRequestMap requestMap) {
        HttpSession session = requestMap.getRequest().getSession(false);
        if (session != null) {
            return (BDFSession) session.getAttribute(BDFSESSION_KEY);
        }
        return null;
    }

    private BDFSession createBDFSession(HttpServletRequestMap requestMap) {
        HttpSession httpSession = requestMap.getRequest().getSession();
        httpSession.setMaxInactiveInterval(maxInactiveInterval);
        BDFSession bdfSession = new BDFSession(httpSession);
        httpSession.setAttribute(BDFSESSION_KEY, bdfSession);
        sessionsMap.put(httpSession.getId(), bdfSession);
        httpSession.setAttribute(BDFBINDING_KEY, bdfBinding);
        return bdfSession;
    }

    private void checkSession(BDFSession bdfSession) {
        if (bdfSession.isEmpty()) {
            HttpSession httpSession = bdfSession.getHttpSession();
            sessionsMap.remove(httpSession.getId());
            httpSession.removeAttribute(BDFSESSION_KEY);
            httpSession.removeAttribute(BDFBINDING_KEY);
            httpSession.invalidate();
        }
    }

    private static SubscribeManager initSubscribeManager(BDFInitParameters initParameters) {
        if (initParameters.multiBdf()) {
            if (initParameters.authentificationSharing().equals(MultiConstants.NONE_SHARING)) {
                if (initParameters.smtpManager().isInit()) {
                    if (initParameters.subscribeAllowed()) {
                        SmtpParameters smtpParameters = initParameters.smtpManager().getSmtpParameters();
                        EmailCore fromEmail = smtpParameters.getEmail(SubscribeManager.EMAIL_FROM);
                        if (fromEmail != null) {
                            return new SubscribeManagerImpl(smtpParameters);
                        }
                    }
                }
            }
        }
        return SubscribeUtils.INACTIVE_SUBSCRIBEMANAGER;
    }


    public class Unique {

        private final ResourceStorages webappResourceStorages;
        private final BuildInfo buildInfo;
        private final MimeTypeResolver mimeTypeResolver;
        private final List<BdfExtensionInitializer> extensionInitializerList;
        private BdfServer bdfServer = null;

        private Unique(WebappDirs webappDirs) {
            this.buildInfo = BDFInit.buildInfo();
            this.mimeTypeResolver = MimeTypeUtils.DEFAULT_RESOLVER;
            this.extensionInitializerList = BDFInit.extensionInitializerList(webappDirs);
            this.webappResourceStorages = BDFInit.initWebappResourceStorages(webappDirs, this.extensionInitializerList);
        }

        public BdfServer getBdfServer() {
            if (bdfServer != null) {
                return bdfServer;
            }
            return initBdfServer();
        }

        private synchronized BdfServer initBdfServer() {
            if (bdfServer != null) {
                return bdfServer;
            }
            String name = ConfConstants.UNIQUE_NAME;
            Map<String, Object> contextObjectMap = new HashMap<String, Object>();
            contextObjectMap.put(BdfServerConstants.SMTPMANAGER_CONTEXTOBJECT, smtpManager);
            contextObjectMap.put(BdfServerConstants.SESSIONOBSERVER_CONTEXTOBJECT, getSessionObserverInstance(name));
            contextObjectMap.put(BdfServerConstants.MIMETYPERESOLVER_CONTEXTOBJECT, mimeTypeResolver);
            Storages storages = BDFInit.storages(webappDirs.getBdfServerDirs(name), webappResourceStorages);
            bdfServer = new BdfServerImpl(name, storages, buildInfo, extensionInitializerList, contextObjectMap);
            return bdfServer;
        }

    }


    private class BDFBinding implements HttpSessionBindingListener {

        private BDFBinding() {

        }

        @Override
        public void valueBound(HttpSessionBindingEvent httpSessionEvent) {
        }

        @Override
        public void valueUnbound(HttpSessionBindingEvent httpSessionEvent) {
            HttpSession session = httpSessionEvent.getSession();
            sessionsMap.remove(session.getId());
        }

    }


    private class InternalSessionObserver implements SessionObserver {

        private final String fichothequeName;

        private InternalSessionObserver(String fichothequeName) {
            this.fichothequeName = fichothequeName;
        }

        @Override
        public BdfUser[] getCurrentBdfUserArray() {
            List<BdfUser> bdfUserList = new ArrayList<BdfUser>();
            for (BDFSession bdfSession : sessionsMap.values()) {
                BdfUser bdfUser = bdfSession.getBdfUser(fichothequeName);
                if (bdfUser != null) {
                    bdfUserList.add(bdfUser);
                }
            }
            return bdfUserList.toArray(new BdfUser[bdfUserList.size()]);
        }

    }

}
