/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.tem.service.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemConstants.NotificationPreference;
import org.kuali.kfs.module.tem.TemParameterConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.businessobject.TemProfile;
import org.kuali.kfs.module.tem.businessobject.TravelerDetail;
import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument;
import org.kuali.kfs.module.tem.document.TravelDocument;
import org.kuali.kfs.module.tem.service.TemProfileService;
import org.kuali.kfs.module.tem.service.TravelDocumentNotificationService;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.KfsNotificationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.document.DocumentStatus;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * implement the notification to the traveler based on the email preferences stored in the traveler profile
 */
public class TravelDocumentNotificationServiceImpl implements TravelDocumentNotificationService {
    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(TravelDocumentNotificationServiceImpl.class);

    private String notificationTemplate;
    private ParameterService parameterService;
    private KfsNotificationService kfsNotificationService;
    private BusinessObjectService businessObjectService;
    private DateTimeService dateTimeService;

    private List<String> noNotificationRouteStatusList;

    /**
     * @see org.kuali.kfs.module.tem.service.TravelDocumentNotificationService#sendNotificationOnChange(org.kuali.kfs.module.tem.document.TravelDocument,
     * org.kuali.rice.kew.dto.DocumentRouteStatusChange)
     */
    @Override
    public void sendNotificationOnChange(TravelDocument travelDocument, DocumentRouteStatusChange statusChangeDTO) {
        String documentTypeCode = travelDocument.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
        NotificationPreference preference = null;
        if (this.isNotificationEnabled()) {
            TemProfile travelProfile = this.getTravelProfile(travelDocument);
            String newRouteStatus = statusChangeDTO.getNewRouteStatus();

            if (travelProfile == null) {
                LOG.error("travelProfile is null.");
                return;
            }

            if (travelDocument instanceof TravelAuthorizationDocument) {
                if (!verifyDocumentTypeCodes(documentTypeCode, this.getEligibleTravelAuthorizationDocumentTypeCodes())) {
                    return;
                }

                preference = getEmailNotificationPreference(preference, newRouteStatus, travelProfile.getNotifyTAFinal(), travelProfile.getNotifyTAStatusChange(), documentTypeCode);
            } else {
                // TR/ENT/RELO
                if (!verifyDocumentTypeCodes(documentTypeCode, this.getEligibleTravelExpenseDocumentTypeCodes())) {
                    return;
                }

                preference = getEmailNotificationPreference(preference, newRouteStatus, travelProfile.getNotifyTERFinal(), travelProfile.getNotifyTERStatusChange(), documentTypeCode);
            }

            this.sendNotificationByPreference(travelDocument, statusChangeDTO, preference);
        }
    }

    private boolean verifyDocumentTypeCodes(String documentTypeCode, Collection<String> eligibleDocumentTypeCodes) {
        if (ObjectUtils.isNull(eligibleDocumentTypeCodes) || !eligibleDocumentTypeCodes.contains(documentTypeCode)) {
            return false;
        }

        return true;
    }

    private NotificationPreference getEmailNotificationPreference(NotificationPreference preference, String newRouteStatus, boolean notifyOnFinal, boolean notifyOnStatusChange, String documentTypeCode) {
        if (notifyOnFinal && (DocumentStatus.FINAL.getCode().equals(newRouteStatus) ||
            DocumentStatus.PROCESSED.getCode().equals(newRouteStatus))) {
            preference = TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT.equals(documentTypeCode) ? NotificationPreference.TA_ON_FINAL : NotificationPreference.TER_ON_FINAL;

        } else if (notifyOnStatusChange && !this.getNoNotificationRouteStatusList().contains(newRouteStatus)) {
            preference = TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT.equals(documentTypeCode) ? NotificationPreference.TA_ON_CHANGE : NotificationPreference.TER_ON_CHANGE;
        }

        return preference;
    }

    /**
     * get the traveler profile associated with the given travel document
     */
    protected TemProfile getTravelProfile(TravelDocument travelDocument) {
        Integer travelProfileId = travelDocument.getProfileId();
        if (travelProfileId != null) {
            return this.getBusinessObjectService().findBySinglePrimaryKey(TemProfile.class, travelProfileId);
        }
        return null;
    }

    /**
     * send notification based on the given document and preference
     */
    protected void sendNotificationByPreference(TravelDocument travelDocument, DocumentRouteStatusChange statusChangeDTO, NotificationPreference preference) {
        if (ObjectUtils.isNull(preference)) {
            return;
        }

        MailMessage mailMessage = this.buildDocumentStatusChangeMailMessage(travelDocument, statusChangeDTO, preference);

        if (mailMessage != null) {
            this.getKfsNotificationService().sendNotificationByMail(mailMessage);
        } else {
            LOG.error("mailMessage is null.");
        }
    }

    /**
     * build mail message object from the given travel document
     */
    @SuppressWarnings("null")
    protected MailMessage buildDocumentStatusChangeMailMessage(TravelDocument travelDocument, DocumentRouteStatusChange statusChangeDTO, NotificationPreference preference) {
        MailMessage mailMessage = new MailMessage();

        String senderEmailAddress = this.getNotificationSender();
        mailMessage.setFromAddress(senderEmailAddress);

        TravelerDetail traveler = travelDocument.getTraveler();
        String travelerEmailAddress = null;
        if (!ObjectUtils.isNull(traveler) && !ObjectUtils.isNull(travelDocument.getProfileId())) {
            TemProfile profile = SpringContext.getBean(TemProfileService.class).findTemProfileById(travelDocument.getProfileId());
            travelerEmailAddress = profile.getEmailAddress();
        } else {
            travelerEmailAddress = traveler.getEmailAddress();
        }

        if (senderEmailAddress != null && travelerEmailAddress != null) {
            mailMessage.addToAddress(travelerEmailAddress);

            String notificationSubject = this.getNotificationSubject(preference);
            mailMessage.setSubject(notificationSubject);

            String notificationBody = this.buildNotificationBody(travelDocument, statusChangeDTO, preference);
            mailMessage.setMessage(notificationBody);

            return mailMessage;
        }

        return null;
    }

    /**
     * collect all the information and build the notification body
     */
    protected String buildNotificationBody(TravelDocument travelDocument, DocumentRouteStatusChange statusChange, NotificationPreference preference) {
        Map<String, Object> notificationInformationHolder = new HashMap<String, Object>();
        notificationInformationHolder.put(TemConstants.NOTIFICATION_PREFERENCE, preference.getLabel());
        notificationInformationHolder.put(KFSPropertyConstants.DOCUMENT, travelDocument);
        notificationInformationHolder.put(TemConstants.STATUS_CHANGE_DTO, statusChange);
        String newStatusLabel = KewApiConstants.DOCUMENT_STATUSES.get(statusChange.getNewRouteStatus());
        notificationInformationHolder.put(TemPropertyConstants.NEW_ROUTE_STATUS, newStatusLabel);

        String oldStatusLabel = KewApiConstants.DOCUMENT_STATUSES.get(statusChange.getOldRouteStatus());
        notificationInformationHolder.put(TemPropertyConstants.OLD_ROUTE_STATUS, oldStatusLabel);

        String campusTravelEmailAddress = this.getCampusTravelEmailAddress();
        notificationInformationHolder.put(TemConstants.CAMPUS_TRAVEL_EMAIL_ADDRESS, campusTravelEmailAddress);

        notificationInformationHolder.put(DateTimeService.class.getSimpleName(), dateTimeService);

        return this.getKfsNotificationService().generateNotificationContent(this.getNotificationTemplate(), notificationInformationHolder);
    }

    /**
     * get the eligible travel expense document type codes for notification from an application parameter
     */
    protected Collection<String> getEligibleTravelExpenseDocumentTypeCodes() {
        return this.getParameterService().getParameterValuesAsString(TemParameterConstants.TEM_DOCUMENT.class, TemConstants.TravelParameters.SEND_NOTIFICATION_DOCUMENT_TYPES);
    }

    /**
     * get the eligible TA document type codes for notification from an application parameter
     */
    protected Collection<String> getEligibleTravelAuthorizationDocumentTypeCodes() {
        return this.getParameterService().getParameterValuesAsString(TravelAuthorizationDocument.class, TemConstants.TravelParameters.SEND_NOTIFICATION_DOCUMENT_TYPES);
    }

    /**
     * get the email notification sender from an application parameter
     */
    protected String getNotificationSender() {
        return this.getParameterService().getParameterValueAsString(TemParameterConstants.TEM_ALL.class, TemConstants.TravelParameters.FROM_EMAIL_ADDRESS_PARAM_NAME);
    }

    /**
     * get the notification subject from an application parameter
     */
    protected String getNotificationSubject(NotificationPreference preference) {
        final String preferenceCode = preference.getParameterEventCode();

        final String subjectParameterName = StringUtils.isBlank(preferenceCode) ? TemConstants.TravelParameters.CHANGE_NOTIFICATION_SUBJECT : preferenceCode + "_NOTIFICATION_SUBJECT";
        final Class<?> subjectParameterComponent = preference.getParameterComponentClass() == null ? TemParameterConstants.TEM_DOCUMENT.class : preference.getParameterComponentClass();

        return this.getParameterService().getParameterValueAsString(subjectParameterComponent, subjectParameterName);
    }

    /**
     * get the Campus Travel Email Address from an application parameter
     */
    protected String getCampusTravelEmailAddress() {
        return this.getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, TemConstants.TravelParameters.TRAVEL_EMAIL_ADDRESS);
    }

    /**
     * determine whether the notification is enable or not
     */
    protected boolean isNotificationEnabled() {
        return this.getParameterService().getParameterValueAsBoolean(TemParameterConstants.TEM_DOCUMENT.class, TemConstants.TravelParameters.SEND_NOTIFICATION_ON_WORKFLOW_STATUS_CHANGE_IND);
    }

    /**
     * Gets the noNotificationRouteStatusList attribute.
     *
     * @return Returns the noNotificationRouteStatusList.
     */
    protected List<String> getNoNotificationRouteStatusList() {
        if (ObjectUtils.isNull(noNotificationRouteStatusList)) {
            noNotificationRouteStatusList = new ArrayList<String>();

            noNotificationRouteStatusList.add(DocumentStatus.PROCESSED.getCode());
            noNotificationRouteStatusList.add(DocumentStatus.INITIATED.getCode());
            noNotificationRouteStatusList.add(DocumentStatus.SAVED.getCode());
        }

        return noNotificationRouteStatusList;
    }

    /**
     * Gets the parameterService attribute.
     *
     * @return Returns the parameterService.
     */
    public ParameterService getParameterService() {
        return parameterService;
    }

    /**
     * Sets the parameterService attribute value.
     *
     * @param parameterService The parameterService to set.
     */
    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    /**
     * Gets the kfsNotificationService attribute.
     *
     * @return Returns the kfsNotificationService.
     */
    public KfsNotificationService getKfsNotificationService() {
        return kfsNotificationService;
    }

    /**
     * Sets the kfsNotificationService attribute value.
     *
     * @param kfsNotificationService The kfsNotificationService to set.
     */
    public void setKfsNotificationService(KfsNotificationService kfsNotificationService) {
        this.kfsNotificationService = kfsNotificationService;
    }

    /**
     * Gets the notificationTemplate attribute.
     *
     * @return Returns the notificationTemplate.
     */
    public String getNotificationTemplate() {
        return notificationTemplate;
    }

    /**
     * Sets the notificationTemplate attribute value.
     *
     * @param notificationTemplate The notificationTemplate to set.
     */
    public void setNotificationTemplate(String notificationTemplate) {
        this.notificationTemplate = notificationTemplate;
    }

    /**
     * Gets the businessObjectService attribute.
     *
     * @return Returns the businessObjectService.
     */
    public BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    /**
     * Sets the businessObjectService attribute value.
     *
     * @param businessObjectService The businessObjectService to set.
     */
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    /**
     * Gets the dateTimeService attribute.
     *
     * @return Returns the dateTimeService.
     */
    public DateTimeService getDateTimeService() {
        return dateTimeService;
    }

    /**
     * Sets the dateTimeService attribute value.
     *
     * @param dateTimeService The dateTimeService to set.
     */
    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }
}
