/* BDF - Copyright (c) 2022-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.BdfServer;
import fr.exemole.bdfserver.api.instruction.BdfInstruction;
import fr.exemole.bdfserver.api.instruction.BdfInstructionConstants;
import fr.exemole.bdfserver.api.interaction.Domain;
import fr.exemole.bdfserver.api.interaction.Domains;
import fr.exemole.bdfserver.api.session.AuthenticationException;
import fr.exemole.bdfserver.api.users.BdfUser;
import fr.exemole.bdfserver.get.GetInstructionFactory;
import fr.exemole.bdfserver.multi.MultiInstructions;
import fr.exemole.bdfserver.multi.api.Multi;
import fr.exemole.bdfserver.multi.api.MultiConstants;
import fr.exemole.bdfserver.multi.api.central.CentralUser;
import fr.exemole.bdfserver.multi.subscribe.SubscribeParameters;
import fr.exemole.bdfserver.servlets.instructions.AppInstruction;
import fr.exemole.bdfserver.servlets.instructions.DomainInstruction;
import fr.exemole.bdfserver.servlets.instructions.RunInstruction;
import fr.exemole.bdfserver.servlets.instructions.SessionInstruction;
import fr.exemole.bdfserver.tools.apps.AppConf;
import fr.exemole.bdfserver.tools.apps.AppConfUtils;
import fr.exemole.bdfserver.tools.instruction.BdfInstructionUtils;
import java.io.IOException;
import java.time.Instant;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.fichotheque.SubsetKey;
import net.mapeadores.util.logging.CommandMessage;
import net.mapeadores.util.logging.ErrorMessageException;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.request.OutputInfo;
import net.mapeadores.util.request.RequestConstants;
import net.mapeadores.util.request.RequestMap;
import net.mapeadores.util.servlets.HttpAccessException;
import net.mapeadores.util.servlets.HttpServletRequestMap;
import net.mapeadores.util.servlets.ResponseHandler;
import net.mapeadores.util.servlets.exceptions.InternalErrorException;
import net.mapeadores.util.servlets.exceptions.NotFoundException;


/**
 *
 * @author Vincent Calame
 */
public final class BDFRoutes {


    private BDFRoutes() {

    }

    public static void resolveMulti(BDFWebapp bdfWebapp, String pathInfo, HttpServletRequestMap requestMap, HttpServletResponse response) throws IOException, ServletException {
        Multi multi = bdfWebapp.getMulti();
        int idx = pathInfo.indexOf('/');
        if (idx == -1) {
            resolveMultiRoute(bdfWebapp, checkAlias(pathInfo), requestMap, response);
        } else {
            String pathRoot = pathInfo.substring(0, idx);
            String relativePath = pathInfo.substring(idx + 1);
            switch (pathRoot) {
                case "multi-rsc":
                case "_rsc": {
                    ResponseHandler responseHandler = MultiInstructions.getResource(multi, relativePath);
                    if (responseHandler != null) {
                        responseHandler.handleResponse(response);
                    } else {
                        throw new NotFoundException();
                    }
                    break;
                }
                default: {
                    BdfServer bdfServer;
                    try {
                        bdfServer = multi.getBdfServer(pathRoot);
                    } catch (ErrorMessageException eme) {
                        CommandMessage errorMessage = eme.getErrorMessage();
                        switch (errorMessage.getMessageKey()) {
                            case MultiConstants.UNKNOWN_FICHOTHEQUE_ERROR:
                                throw new NotFoundException(errorMessage);
                            case MultiConstants.INACTIVE_FICHOTHEQUE_ERROR:
                                throw new HttpAccessException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, errorMessage);
                            default:
                                throw new InternalErrorException(errorMessage);
                        }
                    }
                    if (relativePath.isEmpty()) {
                        response.sendRedirect(Domains.SESSION);
                    } else {
                        resolveBdfServer(bdfWebapp, bdfServer, relativePath, requestMap, response);
                    }
                }
            }
        }
    }

    public static void resolveBdfServer(BDFWebapp bdfWebapp, BdfServer bdfServer, String pathInfo, HttpServletRequestMap requestMap, HttpServletResponse response) throws IOException, ServletException {
        HttpServletRequest request = requestMap.getRequest();
        BdfInstruction bdfInstruction = getBdfServerInstruction(bdfServer, requestMap, pathInfo);
        if (bdfInstruction == null) {
            throw new NotFoundException();
        }
        BdfUser bdfUser = null;
        short needBdfUser = bdfInstruction.getBdfUserNeed();
        try {
            if (needBdfUser == BdfInstructionConstants.EXIT_BDFUSER) {
                bdfWebapp.invalidateBdfUser(requestMap, bdfServer.getName());
                throw new AuthenticationException(AuthenticationException.EXIT_ERROR);
            } else if (needBdfUser != BdfInstructionConstants.NONE_BDFUSER) {
                bdfUser = bdfWebapp.getOrCreateBdfUser(requestMap, bdfServer);
            }
        } catch (AuthenticationException le) {
            if (needBdfUser != BdfInstructionConstants.IF_ANY_BDFUSER) {
                ResponseHandler responseHandler = AuthenticationResponseHandlerFactory.getResponseHandler(bdfServer, requestMap, pathInfo, bdfInstruction, le);
                responseHandler.handleResponse(response);
                return;
            }
        }
        try {
            ResponseHandler responseHandler = bdfInstruction.runInstruction(bdfUser);
            if (responseHandler == null) {
                throw new NotFoundException();
            }
            Instant lastModified = responseHandler.getLastModified();
            if (lastModified.equals(ResponseHandler.LASTMODIFIED_NOCACHE)) {
                response.setHeader("Cache-Control", "no-store");
            } else if (lastModified.getEpochSecond() > 0) {
                long lastModifiedMillis = lastModified.toEpochMilli();
                response.setDateHeader("Last-Modified", lastModifiedMillis);
                try {
                    long since = request.getDateHeader("If-Modified-Since");
                    if (since >= lastModifiedMillis) {
                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                        return;
                    }
                } catch (IllegalArgumentException iae) {
                }
            }
            responseHandler.handleResponse(response);
        } catch (HttpAccessException hae) {
            if (hae.isUnresolvedMessage()) {
                hae.resolveMessage(BdfInstructionUtils.getMessageLocalisation(requestMap, bdfServer, bdfUser));
            }
            throw hae;
        }
    }

    private static BdfInstruction getBdfServerInstruction(BdfServer bdfServer, RequestMap requestMap, String pathInfo) {
        int idx = pathInfo.indexOf('/');
        if (idx == -1) {
            Domain domain = BDFUtils.parsePathInfo(pathInfo);
            switch (domain.getFirstPart()) {
                case Domains.SESSION:
                    return new SessionInstruction(bdfServer, requestMap);
                case Domains.RUN:
                    return new RunInstruction(bdfServer, requestMap);
                case Domains.APP:
                    return AppInstruction.build(bdfServer, requestMap, domain.getSecondPart());
                case Domains.ADMINISTRATION:
                case Domains.ALBUM:
                case Domains.CONFIGURATION:
                case Domains.CORPUS:
                case Domains.MAILING:
                case Domains.EXPORTATION:
                case Domains.EDITION:
                case Domains.SELECTION:
                case Domains.SPHERE:
                case Domains.THESAURUS:
                case Domains.MAIN:
                case Domains.MISC:
                case Domains.ADDENDA:
                case Domains.IMPORTATION:
                case Domains.PIOCHE:
                case Domains.EXTENSION:
                    return new DomainInstruction(bdfServer, requestMap, domain);
                default:
                    return null;
            }
        } else {
            String first = pathInfo.substring(0, idx);
            String next = pathInfo.substring(idx + 1);
            return GetInstructionFactory.getBdfInstruction(bdfServer, requestMap, first, next);
        }
    }

    private static void resolveMultiRoute(BDFWebapp bdfWebapp, String route, HttpServletRequestMap requestMap, HttpServletResponse response) throws IOException, ServletException {
        Multi multi = bdfWebapp.getMulti();
        int idx2 = route.indexOf('-');
        if (idx2 == -1) {
            if (multi.containsFichotheque(route)) {
                response.sendRedirect(route + "/" + Domains.SESSION);
            } else {
                throw new NotFoundException();
            }
        } else {
            String firstPart = route.substring(0, idx2);
            String secondPart = route.substring(idx2 + 1);
            ResponseHandler responseHandler;
            switch (firstPart) {
                case "multi": {
                    responseHandler = resolveMultiAction(bdfWebapp, requestMap, secondPart);
                    break;
                }
                case "app": {
                    AppConf appConf = AppConfUtils.getAppConf(multi.getWebappsResourceStorages(), secondPart);
                    if ((appConf != null) && (appConf.getBoolean(AppConf.MULTI_ACTIVE))) {
                        responseHandler = resolveMultiApp(bdfWebapp, appConf, requestMap);
                    } else {
                        throw new NotFoundException();
                    }
                    break;
                }
                case "index": {
                    SubsetKey sphereKey = BDFUtils.getValidCentralSphere(multi, secondPart);
                    if (sphereKey != null) {
                        responseHandler = resolveSphereIndex(bdfWebapp, sphereKey, requestMap);
                    } else {
                        throw new NotFoundException();
                    }
                    break;
                }
                default:
                    throw new NotFoundException();
            }
            responseHandler.handleResponse(response);
        }
    }


    private static ResponseHandler resolveMultiAction(BDFWebapp bdfWebapp, HttpServletRequestMap requestMap, String action) throws IOException, ServletException {
        OutputInfo outputInfo = null;
        switch (action) {
            case "run":
                break;
            case "subscribe":
            case "admin":
            case "sphere":
                outputInfo = OutputInfo.buildFromRequest(requestMap, action);
                break;
            default:
                throw new NotFoundException();
        }
        Multi multi = bdfWebapp.getMulti();
        if ((action.equals("admin")) && (outputInfo.getType() == OutputInfo.PAGE_TYPE)) {
            multi.getMultiConf().update();
            if (requestMap.isTrue(MultiConstants.RELOAD_PARAMNAME)) {
                multi.getWebappsJsAnalyser().clearCache();
            }
        }
        ResponseHandler errorResponseHandler = MultiInstructions.checkInitError(multi, outputInfo, requestMap);
        if (errorResponseHandler != null) {
            return errorResponseHandler;
        }
        switch (action) {
            case "run":
                return MultiInstructions.doRun(multi, requestMap);
            case "subscribe":
                String multiRootUrl = BDFUtils.getRootUrl(requestMap.getRequest(), "");
                SubscribeParameters subscribeParameters = new SubscribeParameters(bdfWebapp.getSubscribeManager(), multi, requestMap, multiRootUrl);
                return MultiInstructions.doSubscribe(subscribeParameters, outputInfo);
            default:
                short authentificationCheck = bdfWebapp.checkMultiAdmin(requestMap);
                return MultiInstructions.doAuthentificationCheckedAction(multi, action, authentificationCheck, requestMap, outputInfo);

        }
    }

    private static ResponseHandler resolveMultiApp(BDFWebapp bdfWebapp, AppConf appConf, HttpServletRequestMap requestMap) throws IOException, ServletException {
        Multi multi = bdfWebapp.getMulti();
        boolean noAuthenfication = appConf.getBoolean(AppConf.MULTI_NOAUTHENTIFICATION);
        if (noAuthenfication) {
            return MultiInstructions.getNoAuthentificationApp(multi, appConf);
        }
        Set<String> nameSet = multi.getCentralSphereNameSet();
        if (nameSet.isEmpty()) {
            throw new NotFoundException();
        }
        String[] sphereNameArray = nameSet.toArray(new String[nameSet.size()]);
        String defaultSphere = appConf.getString(AppConf.LOGIN_DEFAULTSPHERE, sphereNameArray[0]);
        try {
            CentralUser centralUser = bdfWebapp.getOrCreateCentralUser(requestMap);
            return MultiInstructions.getApp(multi, appConf, centralUser);
        } catch (AuthenticationException e) {
            OutputInfo outputInfo = OutputInfo.buildFromRequest(requestMap);
            return MultiInstructions.getCentralLogin(multi, defaultSphere, outputInfo, requestMap, LogUtils.error(e.getErrorMessageKey()));
        }
    }

    private static ResponseHandler resolveSphereIndex(BDFWebapp bdfWebapp, SubsetKey sphereKey, HttpServletRequestMap requestMap) throws IOException, ServletException {
        OutputInfo outputInfo = OutputInfo.buildFromRequest(requestMap, sphereKey.getSubsetName());
        Multi multi = bdfWebapp.getMulti();
        ResponseHandler errorResponseHandler = MultiInstructions.checkInitError(multi, outputInfo, requestMap);
        if (errorResponseHandler != null) {
            return errorResponseHandler;
        }
        String cmd = requestMap.getParameter(RequestConstants.COMMAND_PARAMETER);
        if ((cmd != null) && (cmd.equals("exit"))) {
            bdfWebapp.invalidateCentralUser(requestMap);
            return MultiInstructions.getCentralLogin(multi, sphereKey.getSubsetName(), outputInfo, requestMap, null);
        } else {
            try {
                CentralUser centralUser = bdfWebapp.getOrCreateCentralUser(requestMap);
                return MultiInstructions.getSphereIndex(multi, centralUser, outputInfo, requestMap);
            } catch (AuthenticationException e) {
                return MultiInstructions.getCentralLogin(multi, sphereKey.getSubsetName(), outputInfo, requestMap, LogUtils.error(e.getErrorMessageKey()));
            }
        }
    }

    private static String checkAlias(String route) {
        switch (route) {
            case "_admin":
            case "admin-multi":
                return "multi-admin";
            default:
                return route;
        }
    }

}
