package cz.muni.ics.oidc.web.controllers;

import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oidc.PerunConstants;
import cz.muni.ics.oidc.RedirectUtils;
import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.Group;
import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.models.Vo;
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.web.WebHtmlClasses;
import cz.muni.ics.openid.connect.view.HttpCodeView;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * Ctonroller for the unauthorized page.
 *
 * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
 */
@Controller
@Slf4j
public class PerunUnauthorizedController {

    public static final String UNAUTHORIZED_MAPPING = "/unauthorized";
    public static final String UNAUTHORIZED_SPECIFIC_MAPPING = "/unauthorizedSpec";
    public static final String UNAUTHORIZED_IS_ELIGIBLE_MAPPING = "/unauthorizedIsEligible";
    public static final String UNAUTHORIZED_ENSURE_VO_MAPPING = "/unauthorizedEnsureVo";
    public static final String UNAUTHORIZED_ENSURE_VO_REDIRECT_NOTIFY_MAPPING = "/unauthorizedEnsureVoRedirectNotify";
    public static final String UNAUTHORIZED_AUTHORIZATION_MAPPING = "/unauthorizedAuthorization";
    public static final String UNAUTHORIZED_NOT_IN_ENV_VOS_GROUPS_MAPPING = "/unauthorizedEnvVosGroups";
    public static final String UNAUTHORIZED_NOT_LOGGED_IN_MAPPING = "/unauthorizedNotLoggedIn";

    public static final String UNAUTHORIZED_REGISTER_CHOOSE_VO_GROUP_MAPPING = "/unauthorizedRegisterChooseVoGroup";
    public static final String UNAUTHORIZED_REGISTER_NOTIFY_ACTION_REQUIRED_MAPPING = "/unauthorizedRegisterNotifyActionRequired";

    // SESSION ATTRIBUTES FOR PASSING DATA

    public static final String SESS_ATTR_REGISTRATION_UNITS = "registrationUnits";
    public static final String SESS_ATTR_CLIENT = "client";

    public static final String SESS_ATTR_TARGET = "target";

    public static final String SESS_ATTR_USER = "logged_user";
    public static final String SESS_ATTR_FACILITY = "client_facility";

    public static final String SESS_ATTR_HEADER_TRANSLATION = "header_translation";
    public static final String SESS_ATTR_TEXT_TRANSLATION = "text_translation";
    public static final String SESS_ATTR_BUTTON_TRANSLATION = "button_translation";
    public static final String SESS_ATTR_CONTACT_TRANSLATION = "contact_translation";
    public static final String SESS_ATTR_HEADER = "header";
    public static final String SESS_ATTR_MESSAGE = "message";

    // MODEL ATTRIBUTES
    private static final String MODEL_REG_URLS = "registrationUrls";
    private static final String MODEL_ATTR_CLIENT = "client";
    private static final String MODEL_ATTR_GROUPS_FOR_REGISTRATION = "groupsForRegistration";
    private static final String MODEL_ATTR_PERUN_REGISTRAR_URL = "registrarUrl";
    private static final String MODEL_ATTR_TRANSL_HEADER = "outHeader";
    private static final String MODEL_ATTR_TRANSL_MESSAGE = "outMessage";
    private static final String MODEL_ATTR_TANSL_SUBMIT_BTN = "outButton";
    private static final String MODEL_ATTR_TRANSL_CONTACT_P = "outContactP";
    private static final String MODEL_ATTR_TARGET = "target";
    private static final String MODEL_ATTR_AAI_CONTACT_ADDRESS = "contactMail";

    // TRANSLATION KEYS

    private static final String AUTHORIZATION_HDR = "403_authorization_hdr";
    private static final String AUTHORIZATION_MSG = "403_authorization_msg";

    private static final String NOT_LOGGED_IN_HDR = "403_not_logged_in_hdr";
    private static final String NOT_LOGGED_IN_MSG = "403_not_logged_in_msg";

    // VIEWS
    public static final String VIEW_UNAUTHORIZED_SPEC = "unauthorized_spec";

    private final PerunOidcConfig perunOidcConfig;
    private final WebHtmlClasses htmlClasses;
    private final PerunAdapter perunAdapter;
    private final FacilityAttrsConfig facilityAttrsConfig;

    public PerunUnauthorizedController(PerunOidcConfig perunOidcConfig,
                                       WebHtmlClasses htmlClasses,
                                       PerunAdapter perunAdapter,
                                       FacilityAttrsConfig facilityAttrsConfig)
    {
        this.perunOidcConfig = perunOidcConfig;
        this.htmlClasses = htmlClasses;
        this.perunAdapter = perunAdapter;
        this.facilityAttrsConfig = facilityAttrsConfig;
    }

    @GetMapping(value = UNAUTHORIZED_MAPPING)
    public String showUnauthorized(HttpServletRequest req, Map<String, Object> model) {
        HttpSession sess = req.getSession();

        ClientDetailsEntity client = (ClientDetailsEntity) sess.getAttribute(SESS_ATTR_CLIENT);

        if (client == null) {
            model.put(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
            return HttpCodeView.VIEWNAME;
        }

        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
        model.put(MODEL_ATTR_CLIENT, client);
        return "unauthorized";
    }

    @GetMapping(value = UNAUTHORIZED_SPECIFIC_MAPPING)
    public String showUnauthorizedSpec(HttpServletRequest req, Map<String, Object> model)
    {
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);

        HttpSession sess = req.getSession();
        String header = (String) sess.getAttribute(SESS_ATTR_HEADER);
        String message = (String) sess.getAttribute(SESS_ATTR_MESSAGE);

        model.put(MODEL_ATTR_TRANSL_HEADER, header);
        model.put(MODEL_ATTR_TRANSL_MESSAGE, message);
        model.put(MODEL_ATTR_AAI_CONTACT_ADDRESS, perunOidcConfig.getEmailContact());
        return VIEW_UNAUTHORIZED_SPEC;
    }

    @GetMapping(value = UNAUTHORIZED_IS_ELIGIBLE_MAPPING)
    public String showUnauthorizedIsEligible(HttpServletRequest req, HttpSession sess, Map<String, Object> model) {
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);

        String header = (String) sess.getAttribute(SESS_ATTR_HEADER_TRANSLATION);
        String message = (String) sess.getAttribute(SESS_ATTR_TEXT_TRANSLATION);
        String button = (String) sess.getAttribute(SESS_ATTR_BUTTON_TRANSLATION);
        String contactP = (String) sess.getAttribute(SESS_ATTR_CONTACT_TRANSLATION);
        String targetUrl = (String) sess.getAttribute(SESS_ATTR_TARGET);
        ClientDetailsEntity client = (ClientDetailsEntity) sess.getAttribute(SESS_ATTR_CLIENT);

        model.put(MODEL_ATTR_TRANSL_HEADER, header);
        model.put(MODEL_ATTR_TRANSL_MESSAGE, message);
        model.put(MODEL_ATTR_TANSL_SUBMIT_BTN, button);
        model.put(MODEL_ATTR_TRANSL_CONTACT_P, contactP);
        model.put(MODEL_ATTR_AAI_CONTACT_ADDRESS, perunOidcConfig.getEmailContact());
        model.put(MODEL_ATTR_CLIENT, client);
        model.put(MODEL_ATTR_TARGET, targetUrl);
        return "unauthorized_is_eligible";
    }

    @GetMapping(value = UNAUTHORIZED_ENSURE_VO_MAPPING)
    public String showUnauthorizedEnsureVo(HttpServletRequest req, HttpSession sess, Map<String, Object> model) {
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);

        model.put(MODEL_ATTR_CLIENT, sess.getAttribute(SESS_ATTR_CLIENT));
        model.put(MODEL_ATTR_AAI_CONTACT_ADDRESS, perunOidcConfig.getEmailContact());

        return VIEW_UNAUTHORIZED_SPEC;
    }

    @GetMapping(value = UNAUTHORIZED_ENSURE_VO_REDIRECT_NOTIFY_MAPPING)
    public String showUnauthorizedEnsureVoRedirectNotify(HttpServletRequest req,
                                                         HttpSession sess,
                                                         Map<String, Object> model)
    {
        String target = (String) sess.getAttribute(SESS_ATTR_TARGET);
        model.put(MODEL_ATTR_TARGET, target);
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
        return "unauthorized_ensure_vo_member_notify_redirect";
    }

    @GetMapping(value = UNAUTHORIZED_AUTHORIZATION_MAPPING)
    public String showUnauthorizedAuthorization(HttpServletRequest req, HttpSession sess, Map<String, Object> model) {
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
        ClientDetailsEntity client = (ClientDetailsEntity) sess.getAttribute(SESS_ATTR_CLIENT);

        model.put(MODEL_ATTR_CLIENT, client);
        model.put(MODEL_ATTR_TRANSL_HEADER, AUTHORIZATION_HDR);
        model.put(MODEL_ATTR_TRANSL_MESSAGE, AUTHORIZATION_MSG);
        model.put(MODEL_ATTR_AAI_CONTACT_ADDRESS, perunOidcConfig.getEmailContact());
        return VIEW_UNAUTHORIZED_SPEC;
    }

    @GetMapping(value = UNAUTHORIZED_NOT_IN_ENV_VOS_GROUPS_MAPPING)
    public String showUnauthorizedNotInEnvUnits(HttpServletRequest req, Map<String, Object> model) {
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);

        HttpSession sess = req.getSession();

        Map<Vo, Set<Group>> registrationUnits = (Map<Vo, Set<Group>>) sess.getAttribute(SESS_ATTR_REGISTRATION_UNITS);
        ClientDetailsEntity client = (ClientDetailsEntity) sess.getAttribute(SESS_ATTR_CLIENT);

        Map<String, String> urls = new HashMap<>();
        for (Map.Entry<Vo, Set<Group>> entry : registrationUnits.entrySet()) {
            Vo vo = entry.getKey();
            if (entry.getValue().isEmpty()) {
                String url = ControllerUtils.createUrl(
                        perunOidcConfig.getRegistrarUrl(), Map.of("vo", vo.getShortName())
                );
                urls.put(url, vo.getName());
            } else {
                for (Group group : entry.getValue()) {
                    String url = ControllerUtils.createUrl(
                            perunOidcConfig.getRegistrarUrl(),
                            Map.of("vo", vo.getShortName(), "group", group.getName())
                    );
                    urls.put(url, group.getName() + "(" + group.getDescription() + ")");
                }
            }
        }

        model.put(MODEL_REG_URLS, urls);
        model.put(MODEL_ATTR_CLIENT, client);
        return "unauthorized_not_in_env_units";
    }

    @GetMapping(value = UNAUTHORIZED_NOT_LOGGED_IN_MAPPING)
    public String showUnauthorizedNotLoggedIn(HttpServletRequest req, Map<String, Object> model) {
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);

        model.put(MODEL_ATTR_TRANSL_HEADER, NOT_LOGGED_IN_HDR);
        model.put(MODEL_ATTR_TRANSL_MESSAGE, NOT_LOGGED_IN_MSG);
        model.put(MODEL_ATTR_AAI_CONTACT_ADDRESS, perunOidcConfig.getEmailContact());
        return VIEW_UNAUTHORIZED_SPEC;
    }

    @GetMapping(value = UNAUTHORIZED_REGISTER_CHOOSE_VO_GROUP_MAPPING)
    public String unauthorizedRegisterChooseVoGroup(HttpServletRequest req,
                                                    HttpServletResponse res,
                                                    HttpSession sess,
                                                    Map<String, Object> model)
    {
        ClientDetailsEntity client = (ClientDetailsEntity) sess.getAttribute(SESS_ATTR_CLIENT);
        Facility facility = (Facility) sess.getAttribute(SESS_ATTR_FACILITY);
        PerunUser user = (PerunUser) sess.getAttribute(SESS_ATTR_USER);

        if (client == null) {
            log.error("confirmAccess: could not find client in session");
            return error(model);
        } else if (facility == null) {
            log.error("confirmAccess: could not find facility in session");
            return error(model);
        } else if (user == null) {
            log.error("confirmAccess: could not find user in session");
            return error(model);
        }

        Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues(facility,
                facilityAttrsConfig.getMembershipAttrNames());

        List<String> voShortNames = facilityAttributes.get(facilityAttrsConfig.getVoShortNamesAttr()).valueAsList();
        Map<Vo, List<Group>> groupsForRegistration = perunAdapter.getAdapterRpc()
                .getGroupsForRegistration(facility, user.getId(), voShortNames);

        if (groupsForRegistration.isEmpty()) {
            log.debug("Redirecting user '{}' to the internal page informing about unauthorized access", user);
            Map<String, Object> sessAttributes = new HashMap<>();
            sessAttributes.put(PerunUnauthorizedController.SESS_ATTR_CLIENT, client);
            RedirectUtils.redirectInternal(req, res, perunOidcConfig, PerunUnauthorizedController.UNAUTHORIZED_AUTHORIZATION_MAPPING, sessAttributes);
            return null;
        } else if (groupsForRegistration.keySet().size() == 1) {
            for (Map.Entry<Vo, List<Group>> entry: groupsForRegistration.entrySet()) {
                // no other way how to extract the first item (as it is the only)
                List<Group> groupList = groupsForRegistration.get(entry.getKey());
                if (groupList.size() == 1) {
                    Group group = groupList.get(0);
                    String redirectUrl = createRegistrarUrl(entry.getKey(), group.getName());
                    RedirectUtils.redirectExternal(res, redirectUrl);
                    return null;
                }
            }
        }

        model.put(MODEL_ATTR_CLIENT, client);
        model.put(MODEL_ATTR_GROUPS_FOR_REGISTRATION, groupsForRegistration);
        model.put(MODEL_ATTR_PERUN_REGISTRAR_URL, perunOidcConfig.getRegistrarUrl());
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig, "unauthorized_register_choose_vo_group");
        return "unauthorized_register_choose_vo_group";
    }

    @GetMapping(value = UNAUTHORIZED_REGISTER_NOTIFY_ACTION_REQUIRED_MAPPING)
    public String unauthorizedRegisterNotifyActionRequired(HttpServletRequest req,
                                                           HttpSession sess,
                                                           Map<String, Object> model)
    {
        ClientDetailsEntity client = (ClientDetailsEntity) sess.getAttribute(SESS_ATTR_CLIENT);
        if (client == null) {
            log.error("confirmAccess: could not find client in session");
            return error(model);
        }

        model.put(MODEL_ATTR_CLIENT, client);
        ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig, "unauthorized_register_notify_action_required");
        return "unauthorized_register_notify_action_required";
    }

    private String createRegistrarUrl(Vo vo, String groupName) {
        return createRegistrarUrl(vo.getShortName(), groupName);
    }

    private String createRegistrarUrl(String voShortName, String groupName) {
        Map<String, String> params = new HashMap<>();
        params.put(PerunConstants.REGISTRAR_PARAM_VO, voShortName);
        if (groupName != null && !groupName.isEmpty() && !groupName.equalsIgnoreCase(PerunConstants.GROUP_NAME_MEMBERS)) {
            params.put(PerunConstants.REGISTRAR_PARAM_GROUP, groupName);
        }

        return ControllerUtils.createUrl(perunOidcConfig.getRegistrarUrl(), params);
    }

    private String error(Map<String, Object> model) {
        model.put(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
        return HttpCodeView.VIEWNAME;
    }

}
