/*
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * MyCoRe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MyCoRe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mycore.frontend.acl2.resources;

import java.io.InputStream;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.mycore.access.mcrimpl.MCRAccessRule;
import org.mycore.access.mcrimpl.MCRAccessStore;
import org.mycore.access.mcrimpl.MCRRuleMapping;
import org.mycore.access.mcrimpl.MCRRuleStore;
import org.mycore.common.MCRSession;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.common.content.MCRContent;
import org.mycore.frontend.MCRFrontendUtil;
import org.mycore.frontend.jersey.MCRJerseyUtil;
import org.mycore.frontend.jersey.filter.access.MCRRestrictedAccess;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

@Path("ACLE")
public class MCRAclEditorResource {

    private static final MCRRuleStore RULE_STORE = MCRRuleStore.getInstance();

    private static final MCRAccessStore ACCESS_STORE = MCRAccessStore.getInstance();

    private static final String JSON_ACCESSID = "accessID";

    private static final String JSON_ACCESSPOOL = "accessPool";

    private static final String JSON_RULE = "rule";

    private static final String JSON_SUCCESS = "success";

    @Context
    HttpServletRequest request;

    @Context
    HttpServletResponse response;

    @Context
    ServletContext context;

    @GET
    @Path("start")
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    @Produces(MediaType.TEXT_HTML)
    public InputStream start() throws Exception {
        return transform("/META-INF/resources/modules/acl-editor2/gui/xml/webpage.xml");
    }

    protected InputStream transform(String xmlFile) throws Exception {
        InputStream guiXML = getClass().getResourceAsStream(xmlFile);
        if (guiXML == null) {
            throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).build());
        }
        SAXBuilder saxBuilder = new SAXBuilder();
        Document webPage = saxBuilder.build(guiXML);
        XPathExpression<Object> xpath = XPathFactory.instance().compile(
            "/MyCoReWebPage/section/div[@id='mycore-acl-editor2']");
        Object node = xpath.evaluateFirst(webPage);
        MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
        String lang = mcrSession.getCurrentLanguage();
        if (node != null) {
            Element mainDiv = (Element) node;
            mainDiv.setAttribute("lang", lang);
            String bsPath = MCRConfiguration2.getString("MCR.bootstrap.path").orElse("");
            if (!Objects.equals(bsPath, "")) {
                bsPath = MCRFrontendUtil.getBaseURL() + bsPath;
                Element item = new Element("link").setAttribute("href", bsPath).setAttribute("rel", "stylesheet")
                    .setAttribute("type", "text/css");
                mainDiv.addContent(0, item);
            }
        }
        MCRContent content = MCRJerseyUtil.transform(webPage, request);
        return content.getInputStream();
    }

    @GET
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    public String list() {
        Collection<String> ruleIDs = RULE_STORE.retrieveAllIDs();
        JsonArray jsonARules = new JsonArray();
        JsonObject jsonObj = new JsonObject();
        for (String id : ruleIDs) {
            MCRAccessRule rule = RULE_STORE.getRule(id);
            JsonObject jsonO = new JsonObject();
            jsonO.addProperty("ruleID", id);
            jsonO.addProperty("desc", rule.getDescription());
            jsonO.addProperty("ruleSt", rule.getRuleString());
            jsonARules.add(jsonO);
        }
        jsonObj.add("rules", jsonARules);
        JsonArray jsonAAccess = new JsonArray();
        Collection<String> ids = ACCESS_STORE.getDistinctStringIDs();
        for (String id : ids) {
            Collection<String> pools = ACCESS_STORE.getPoolsForObject(id);
            for (String pool : pools) {
                JsonObject jsonO = new JsonObject();
                jsonO.addProperty(JSON_ACCESSID, id);
                jsonO.addProperty(JSON_ACCESSPOOL, pool);
                jsonO.addProperty(JSON_RULE, ACCESS_STORE.getRuleID(id, pool));
                jsonAAccess.add(jsonO);
            }
        }
        jsonObj.add("access", jsonAAccess);
        return jsonObj.toString();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    public Response add(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        String accessID = jsonObject.get(JSON_ACCESSID).getAsString();
        String accessPool = jsonObject.get(JSON_ACCESSPOOL).getAsString();
        String rule = jsonObject.get(JSON_RULE).getAsString();

        if (RULE_STORE.existsRule(rule) && !Objects.equals(accessID, "") && !Objects.equals(accessPool, "")) {
            MCRRuleMapping accessRule = createRuleMap(accessID, accessPool, rule);

            if (!ACCESS_STORE.existsRule(accessID, accessPool)) {
                ACCESS_STORE.createAccessDefinition(accessRule);
                return Response.ok().build();
            } else {
                return Response.status(Status.CONFLICT).build();
            }
        } else {
            return Response.status(Status.CONFLICT).build();
        }
    }

    @DELETE
    @Consumes(MediaType.APPLICATION_JSON)
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    public String remove(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        JsonArray jsonArray = jsonObject.getAsJsonArray("access");
        for (int i = 0; i < jsonArray.size(); i++) {
            JsonObject accessAsJsonObject = jsonArray.get(i).getAsJsonObject();
            String accessID = accessAsJsonObject.get(JSON_ACCESSID).getAsString();
            String accessPool = accessAsJsonObject.get(JSON_ACCESSPOOL).getAsString();

            if (ACCESS_STORE.existsRule(accessID, accessPool)) {
                MCRRuleMapping accessRule = ACCESS_STORE.getAccessDefinition(accessPool, accessID);

                if (!"".equals(accessRule.getObjId())) {
                    ACCESS_STORE.deleteAccessDefinition(accessRule);
                    accessAsJsonObject.addProperty(JSON_SUCCESS, "1");
                } else {
                    accessAsJsonObject.addProperty(JSON_SUCCESS, "0");
                }
            } else {
                accessAsJsonObject.addProperty(JSON_SUCCESS, "0");
            }
        }
        return jsonObject.toString();
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    public Response edit(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        String accessIDOld = jsonObject.get("accessIDOld").getAsString();
        String accessPoolOld = jsonObject.get("accessPoolOld").getAsString();
        String mode = jsonObject.get("mode").getAsString();
        String accessIDNew = jsonObject.get("accessIDNew").getAsString();
        String accessPoolNew = jsonObject.get("accessPoolNew").getAsString();
        String accessRuleNew = jsonObject.get("accessRuleNew").getAsString();

        if (!ACCESS_STORE.existsRule(accessIDNew, accessPoolNew) || JSON_RULE.equals(mode)) {
            if (ACCESS_STORE.existsRule(accessIDOld, accessPoolOld) && RULE_STORE.existsRule(accessRuleNew)
                && !Objects.equals(accessIDNew, "") && !Objects.equals(accessPoolNew, "")) {
                MCRRuleMapping accessRule = createRuleMap(accessIDNew, accessPoolNew, accessRuleNew);
                MCRRuleMapping oldAccessRule = ACCESS_STORE.getAccessDefinition(accessPoolOld, accessIDOld);

                if (oldAccessRule != null && !oldAccessRule.getObjId().equals("")) {
                    if (JSON_RULE.equals(mode)) {
                        ACCESS_STORE.updateAccessDefinition(accessRule);
                    } else {
                        ACCESS_STORE.deleteAccessDefinition(oldAccessRule);
                        ACCESS_STORE.createAccessDefinition(accessRule);
                    }
                } else {
                    ACCESS_STORE.createAccessDefinition(accessRule);
                }
                return Response.ok().build();
            } else {
                return Response.status(Status.CONFLICT).build();
            }
        } else {
            return Response.status(Status.CONFLICT).build();
        }
    }

    @POST
    @Path(JSON_RULE)
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    @Consumes(MediaType.APPLICATION_JSON)
    public String addRule(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        String ruleDesc = jsonObject.get("ruleDesc").getAsString();
        String ruleText = jsonObject.get("ruleText").getAsString();
        MCRAccessRule accessRule;

        try {
            accessRule = createAccessRule(ruleDesc, ruleText);
        } catch (Exception e) {
            return "";
        }
        RULE_STORE.createRule(accessRule);
        return accessRule.getId();
    }

    @DELETE
    @Path(JSON_RULE)
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response removeRule(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        String ruleID = jsonObject.get("ruleID").getAsString();

        if (!ACCESS_STORE.isRuleInUse(ruleID)) {
            RULE_STORE.deleteRule(ruleID);
            return Response.ok().build();
        } else {
            return Response.status(Status.CONFLICT).build();
        }
    }

    @PUT
    @Path(JSON_RULE)
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response editRule(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        String ruleID = jsonObject.get("ruleID").getAsString();
        String ruleDesc = jsonObject.get("ruleDesc").getAsString();
        String ruleText = jsonObject.get("ruleText").getAsString();
        String uid = MCRSessionMgr.getCurrentSession().getUserInformation().getUserID();

        if (RULE_STORE.existsRule(ruleID)) {
            try {
                MCRAccessRule accessRule = new MCRAccessRule(ruleID, uid, new Date(), ruleText, ruleDesc);
                RULE_STORE.updateRule(accessRule);
                return Response.ok().build();
            } catch (Exception e) {
                return Response.status(Status.CONFLICT).build();
            }
        } else {
            return Response.status(Status.CONFLICT).build();
        }
    }

    @PUT
    @Path("multi")
    @MCRRestrictedAccess(MCRAclEditorPermission.class)
    @Consumes(MediaType.APPLICATION_JSON)
    public String editMulti(String data) {
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        JsonArray jsonArray = jsonObject.getAsJsonArray("access");
        for (int i = 0; i < jsonArray.size(); i++) {
            JsonObject accessAsJsonObject = jsonArray.get(i).getAsJsonObject();
            String accessID = accessAsJsonObject.get(JSON_ACCESSID).getAsString();
            String accessPool = accessAsJsonObject.get(JSON_ACCESSPOOL).getAsString();
            String accessRule = accessAsJsonObject.get("accessRule").getAsString();

            if (ACCESS_STORE.existsRule(accessID, accessPool) && RULE_STORE.existsRule(accessRule)) {
                MCRRuleMapping newAccessRule = createRuleMap(accessID, accessPool, accessRule);
                MCRRuleMapping oldAccessRule = ACCESS_STORE.getAccessDefinition(accessPool, accessID);

                if (oldAccessRule != null && !oldAccessRule.getObjId().equals("")) {
                    ACCESS_STORE.updateAccessDefinition(newAccessRule);
                    accessAsJsonObject.addProperty(JSON_SUCCESS, "1");
                } else {
                    ACCESS_STORE.createAccessDefinition(newAccessRule);
                    accessAsJsonObject.addProperty(JSON_SUCCESS, "1");
                }
            } else {
                accessAsJsonObject.addProperty(JSON_SUCCESS, "0");
            }
        }
        return jsonObject.toString();
    }

    private MCRRuleMapping createRuleMap(String accessID, String accessPool, String rule) {
        String uid = MCRSessionMgr.getCurrentSession().getUserInformation().getUserID();
        MCRRuleMapping accessRule = new MCRRuleMapping();
        accessRule.setObjId(accessID);
        accessRule.setPool(accessPool);
        accessRule.setRuleId(rule);
        accessRule.setCreationdate(new Date());
        accessRule.setCreator(uid);
        return accessRule;
    }

    private MCRAccessRule createAccessRule(String ruleDesc, String ruleText) {
        int freeRuleID = RULE_STORE.getNextFreeRuleID("SYSTEMRULE");
        String ruleID = "0000000000" + freeRuleID;
        ruleID = ruleID.substring(ruleID.length() - "0000000000".length());
        String newRuleID = "SYSTEMRULE" + ruleID;
        String uid = MCRSessionMgr.getCurrentSession().getUserInformation().getUserID();

        return new MCRAccessRule(newRuleID, uid, new Date(), ruleText, ruleDesc);
    }
}
