/**
 * Copyright 2005-2018 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.ken.web.spring;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.rice.ken.api.KenApiConstants;
import org.kuali.rice.ken.bo.NotificationBo;
import org.kuali.rice.ken.bo.NotificationMessageDelivery;
import org.kuali.rice.ken.bo.NotificationRecipientBo;
import org.kuali.rice.ken.bo.NotificationSenderBo;
import org.kuali.rice.ken.service.NotificationMessageDeliveryService;
import org.kuali.rice.ken.service.NotificationService;
import org.kuali.rice.ken.service.NotificationWorkflowDocumentService;
import org.kuali.rice.ken.util.NotificationConstants;
import org.kuali.rice.ken.util.Util;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.krad.UserSession;
import org.kuali.rice.krad.util.KRADUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;


/**
 * This class is the controller for the basic notification related actions - viewing, etc.
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
public class NotificationController extends MultiActionController {
    /** Logger for this class and subclasses */
    private static final Logger LOG = Logger.getLogger(NotificationController.class);
    
    protected NotificationService notificationService;
    protected NotificationWorkflowDocumentService notificationWorkflowDocService;
    protected NotificationMessageDeliveryService messageDeliveryService;
   
    /**
     * Set the NotificationService
     * @param notificationService
     */   
    public void setNotificationService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    /**
     * This method sets the NotificationWorkflowDocumentService
     * @param s
     */
    public void setNotificationWorkflowDocumentService(NotificationWorkflowDocumentService s) {
        this.notificationWorkflowDocService = s;
    }

    /**
     * Sets the messageDeliveryService attribute value.
     * @param messageDeliveryService The messageDeliveryService to set.
     */
    public void setMessageDeliveryService(NotificationMessageDeliveryService messageDeliveryService) {
        this.messageDeliveryService = messageDeliveryService;
    }

    /**
     * Handles the display of the main home page in the system.
     * @param request : a servlet request
     * @param response : a servlet response
     * @throws ServletException : an exception
     * @throws IOException : an exception
     * @return a ModelAndView object
     */   
    public ModelAndView displayHome(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String view = "HomePage";
        LOG.debug("remoteUser: "+request.getRemoteUser());
        Map<String, Object> model = new HashMap<String, Object>(); 
        return new ModelAndView(view, model);
    }
   
    /**
     * This method handles displaying the notifications that an individual sent.
     * @param request
     * @param response
     * @return ModelAndView
     * @throws ServletException
     * @throws IOException
     */
    public ModelAndView displayNotificationsSent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String view = "NotificationsSent";
        LOG.debug("remoteUser: "+request.getRemoteUser());
        Map<String, Object> model = new HashMap<String, Object>();
        model.put("userId", request.getRemoteUser());
        return new ModelAndView(view, model);
    }

    /**
     * This method handles displaying the search screen.
     * @param request
     * @param response
     * @return ModelAndView
     * @throws ServletException
     * @throws IOException
     */
    public ModelAndView displaySearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String view = "Search";
        LOG.debug("remoteUser: "+request.getRemoteUser());
        Map<String, Object> model = new HashMap<String, Object>(); 
        return new ModelAndView(view, model);
    }

    /**
     * This method displays the user lookup screen.
     * @param request
     * @param response
     * @return
     * @throws ServletException
     * @throws IOException
     */
    public ModelAndView displayLookupUsers(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String view = "LookupUsers";
        LOG.debug("remoteUser: "+request.getRemoteUser());
        Map<String, Object> model = new HashMap<String, Object>(); 
        return new ModelAndView(view, model);
    }

    /**
     * This method displays the workgroup lookup screen.
     * @param request
     * @param response
     * @return
     * @throws ServletException
     * @throws IOException
     */
    public ModelAndView displayLookupWorkgroups(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String view = "LookupWorkgroups";
        LOG.debug("remoteUser: "+request.getRemoteUser());
        Map<String, Object> model = new HashMap<String, Object>(); 
        return new ModelAndView(view, model);
    }


    /**
     * This method retrieves the NotificationMessageDelivery given an HttpServletRequest which
     * may contain EITHER a message delivery id or a workflow doc id.  Therefore, this is a
     * "special case" for handling the workflow deliverer.
     * @param request the incoming {@link HttpServletRequest}
     * @return the {@link NotificationMessageDelivery} or null if not found
     */
    protected NotificationMessageDelivery determineMessageFromRequest(HttpServletRequest request) {
        /**
         * We can get the NotificationMessageDelivery object given a workflow ID or a NotificationMessageDelivery
         * Id.  This method might be called either from a workflow action list or
         * as a link from a message deliverer endpoint such as an email message.
         */
        String messageDeliveryId = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.MSG_DELIVERY_ID);
        String delivererId = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.DELIVERER_ID);
        if (delivererId == null) {
            delivererId = request.getParameter(KewApiConstants.DOCUMENT_ID_PARAMETER);
        }

        NotificationMessageDelivery messageDelivery;
        if (messageDeliveryId != null) { // this means that the request came in not from the action list, but rather from a delivery end point
            LOG.debug("Looking up notification with messageDeliveryId: "+messageDeliveryId);
            try {
                messageDelivery = messageDeliveryService.getNotificationMessageDelivery(new Long(messageDeliveryId));
            } catch (Exception e) {
                throw new RuntimeException("Error getting message with id: " + messageDeliveryId, e);
            }
        } else if (delivererId != null) {  // this means that the request was triggered via the action list
            LOG.debug("Looking up notification with workflowId: "+delivererId);
            try {
                messageDelivery = messageDeliveryService.getNotificationMessageDeliveryByDelivererId(delivererId);
            } catch (Exception e) {
                LOG.error("Error getting message with from deliverer id: " + delivererId, e);
                throw new RuntimeException("Error getting message with deliverer id: " + delivererId, e);
            }
        } else {
            throw new RuntimeException("Neither message ('" + NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.MSG_DELIVERY_ID
                                       + "') nor deliverer id ('" + NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.DELIVERER_ID + "') were specified in the request");
        }
        
        return messageDelivery;
    }

    /**
     * @param req the {@link HttpServletRequest}
     * @return whether the incoming request was from the action list
     */
    protected boolean requestIsFromKEW(HttpServletRequest req) {
        return req.getParameter(KewApiConstants.DOCUMENT_ID_PARAMETER) != null;
    }

    /**
     * This controller handles displaying the appropriate notification details for a specific record.
     * @param request : a servlet request
     * @param response : a servlet response
     * @throws ServletException : an exception
     * @throws IOException : an exception
     * @return a ModelAndView object
     */   
    public ModelAndView displayNotificationDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String view = "NotificationDetail"; // default to full view

        UserSession userSession = KRADUtils.getUserSessionFromRequest(request);
        String principalId = "";
        if(userSession != null) {
            principalId = userSession.getPrincipalId();
            if(StringUtils.isBlank(principalId)) {
                String principalName = request.getRemoteUser();
                Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName);
                if(principal != null) {
                    principalId = principal.getPrincipalId();
                } else {
                    throw new RuntimeException("There is no principal for principalName " + principalName);
                }
            }
        }

        String command = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.COMMAND);
        String standaloneWindow = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW);

        NotificationMessageDelivery messageDelivery = determineMessageFromRequest(request);
        // now get the notification from the message delivery object
        NotificationBo notification = messageDelivery.getNotification();
        boolean actionable = false;

        if (requestIsFromKEW(request)) {
            // check to see if this was a standalone window by examining the command from KEW before setting it to INLINE to force an inline view
            if(command != null && 
                    (command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.NORMAL_VIEW) || 
                            command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.DOC_SEARCH_VIEW))) {
                standaloneWindow = "true";
            }

            // we want all messages from the action list in line
            command = NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE;
        }

        actionable = (principalId).equals(messageDelivery.getUserRecipientId()) && NotificationConstants.MESSAGE_DELIVERY_STATUS.DELIVERED.equals(messageDelivery.getMessageDeliveryStatus());

        String documentId = request.getParameter(KewApiConstants.DOCUMENT_ID_PARAMETER);
        if(StringUtils.isNotBlank(documentId)) {
            boolean authorized = KewApiServiceLocator.getWorkflowDocumentActionsService().isUserInRouteLog(documentId, principalId, false);
            LOG.debug("User in route log = " + authorized);
            if(!authorized) {
                Map<String, String> permissionDetails = new HashMap<String, String>();
                permissionDetails.put(KenApiConstants.KIMTypes.Channel.CHANNEL_ID, notification.getChannel().getId().toString());
                Map<String, String> qualification = new HashMap<String, String>();
                authorized = KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(principalId, KenApiConstants.Namespaces.CODE, KenApiConstants.Permissions.VIEW_NOTIFICATION, permissionDetails, qualification);
                LOG.debug("User has 'View Notification' permission = " + authorized);
                if(!authorized) {
                    return new ModelAndView("NotAuthorized");
                }
            }
        }
        
        List<NotificationSenderBo> senders = notification.getSenders();
        List<NotificationRecipientBo> recipients = notification.getRecipients();

        String contenthtml = Util.transformContent(notification);

        // check to see if the details need to be rendered in line (no stuff around them)
        if (command != null && command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE)) {
            view = "NotificationDetailInline";   
        } 

        Map<String, Object> model = new HashMap<String, Object>();
        model.put("notification", notification);
        model.put("senders", senders);
        model.put("recipients", recipients);
        model.put("contenthtml", contenthtml);
        model.put("messageDeliveryId", messageDelivery.getId());
        model.put("command", command);
        model.put("actionable", actionable);
        model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, standaloneWindow);
        return new ModelAndView(view, model);
    }

    /**
     * This method handles user dismissal of a message
     * @param request : a servlet request
     * @param response : a servlet response
     * @return a ModelAndView object
     */   
    public ModelAndView dismissMessage(HttpServletRequest request, HttpServletResponse response) {
        String command = request.getParameter("action");
        if (command == null) throw new RuntimeException("Dismissal command not specified");

        if (NotificationConstants.ACK_CAUSE.equals(command)) {
            return dismissMessage(command, "Notificaton acknowledged.  Please refresh your action list.", request, response);
        } else if (NotificationConstants.FYI_CAUSE.equals(command)) {
            return dismissMessage(command, "Action Taken.  Please refresh your action list.", request, response);
        } else {
            throw new RuntimeException("Unknown dismissal command: " + command);
        }
    }

    /**
     * This method takes an action on the message delivery - dismisses it with the action/cause that comes from the
     * UI layer
     * @param action the action or cause of the dismissal
     * @param message the message to display to the user
     * @param request the HttpServletRequest
     * @param response the HttpServletResponse
     * @return an appropriate ModelAndView
     */
    private ModelAndView dismissMessage(String action, String message, HttpServletRequest request, HttpServletResponse response) {
        String view = "NotificationDetail";

        String principalNm = request.getRemoteUser();
        String messageDeliveryId = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.MSG_DELIVERY_ID);
        String command = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.COMMAND);
        String standaloneWindow = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW);

        if (messageDeliveryId == null) {
            throw new RuntimeException("A null messageDeliveryId was provided.");
        }

        LOG.debug("messageDeliveryId: "+messageDeliveryId);
        LOG.debug("command: "+command);

        /**
         * We can get the notification object given a workflow ID or a notification
         * Id.  This method might be called either from a workflow action list or
         * as a link from a message deliverer endpoint such as an email message.  
         */        
        NotificationMessageDelivery delivery = messageDeliveryService.getNotificationMessageDelivery(Long.decode(messageDeliveryId));
        if (delivery == null) {
            throw new RuntimeException("Could not find message delivery with id " + messageDeliveryId);
        }
        NotificationBo notification = delivery.getNotification();

        /*
         * dismiss the message delivery
         */

        Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalNm);
        notificationService.dismissNotificationMessageDelivery(delivery.getId(), principal.getPrincipalId(), action);

        List<NotificationSenderBo> senders = notification.getSenders();
        List<NotificationRecipientBo> recipients = notification.getRecipients();

        String contenthtml = Util.transformContent(notification);       

        // first check to see if this is a standalone window, b/c if it is, we'll want to close
        if(standaloneWindow != null && standaloneWindow.equals("true")) {
            view = "NotificationActionTakenCloseWindow";
        } else { // otherwise check to see if the details need to be rendered in line (no stuff around them)
            if (command != null && command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE)) { 
                view = "NotificationDetailInline";   
            }
        }

        Map<String, Object> model = new HashMap<String, Object>();
        model.put("notification", notification);
        model.put("message", message);
        model.put("senders", senders);
        model.put("recipients", recipients);
        model.put("contenthtml", contenthtml);
        model.put("messageDeliveryId", messageDeliveryId);
        model.put("command", command);
        model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, standaloneWindow);
        return new ModelAndView(view, model);
    }
}
