/*
 * 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.document.validation.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.Chart;
import org.kuali.kfs.coa.businessobject.ProjectCode;
import org.kuali.kfs.coa.businessobject.SubAccount;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemKeyConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants.TemProfileProperties;
import org.kuali.kfs.module.tem.businessobject.TemProfile;
import org.kuali.kfs.module.tem.businessobject.TemProfileAccount;
import org.kuali.kfs.module.tem.businessobject.TemProfileArranger;
import org.kuali.kfs.module.tem.service.TemProfileService;
import org.kuali.kfs.module.tem.service.TravelService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class TemProfileRule extends MaintenanceDocumentRuleBase {
    protected static final String TRAVEL_ARRANGERS_SECTION_ID = "TemProfileArrangers";
    protected volatile static TemProfileService temProfileService;
    protected volatile static DataDictionaryService dataDictionaryService;

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#dataDictionaryValidate(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean dataDictionaryValidate(MaintenanceDocument document) {
        BusinessObjectService businessObjectService = getBusinessObjectService();
        TemProfile profile = (TemProfile) document.getNewMaintainableObject().getBusinessObject();
        TravelService travelService = SpringContext.getBean(TravelService.class);

        boolean success = true;
        List<String> paths = GlobalVariables.getMessageMap().getErrorPath();
        Map<String, Object> fieldValues = new HashMap<String, Object>();
        paths.add("document");
        paths.add("newMaintainableObject");


        if (!StringUtils.isEmpty(profile.getPhoneNumber())) {
            String errorMessage = travelService.validatePhoneNumber(profile.getCountryCode(), profile.getPhoneNumber(), TemKeyConstants.ERROR_PHONE_NUMBER);
            if (!StringUtils.isBlank(errorMessage)) {
                GlobalVariables.getMessageMap().putError(TemProfileProperties.PHONE_NUMBER, errorMessage, new String[]{"Phone Number"});
                success = false;
            }
        }

        if (!StringUtils.isEmpty(profile.getDefaultChartCode())) {
            fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, profile.getDefaultChartCode());
            List<Chart> chartList = (List<Chart>) businessObjectService.findMatching(Chart.class, fieldValues);
            if (chartList.size() == 0) {
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.DEFAULT_CHART_CODE,
                    TemKeyConstants.ERROR_TEM_PROFILE_CHART_NOT_EXIST, profile.getDefaultChartCode());
                success = false;
            }
        } else {
            profile.setChart(null);
        }

        if (success && !StringUtils.isEmpty(profile.getDefaultAccount())) {
            fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, profile.getDefaultAccount());
            List<Account> accountList = (List<Account>) businessObjectService.findMatching(Account.class, fieldValues);
            if (accountList.size() == 0) {
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.DEFAULT_ACCOUNT_NUMBER,
                    TemKeyConstants.ERROR_TEM_PROFILE_ACCOUNT_NOT_EXIST, profile.getDefaultAccount());
                success = false;
            }
        } else {
            profile.setAccount(null);
        }

        if (success && !StringUtils.isEmpty(profile.getDefaultSubAccount())) {
            fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, profile.getDefaultSubAccount());
            List<SubAccount> subAccountList = (List<SubAccount>) businessObjectService.findMatching(SubAccount.class, fieldValues);
            if (subAccountList.size() == 0) {
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.DEFAULT_SUB_ACCOUNT_NUMBER,
                    TemKeyConstants.ERROR_TEM_PROFILE_SUB_ACCOUNT_NOT_EXIST, profile.getDefaultSubAccount());
                success = false;
            }
        } else {
            profile.setSubAccount(null);
        }

        if (!StringUtils.isEmpty(profile.getDefaultProjectCode())) {
            fieldValues.clear();
            fieldValues.put(KFSConstants.GENERIC_CODE_PROPERTY_NAME, profile.getDefaultProjectCode());
            List<ProjectCode> subAccountList = (List<ProjectCode>) businessObjectService.findMatching(ProjectCode.class, fieldValues);
            if (subAccountList.size() == 0) {
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.DEFAULT_PROJECT_CODE,
                    TemKeyConstants.ERROR_TEM_PROFILE_PROJECT_CODE_NOT_EXIST, profile.getDefaultProjectCode());
                success = false;
            }
        } else {
            profile.setProject(null);
        }

        for (int i = 0; i < profile.getAccounts().size(); i++) {
            paths.add(TemPropertyConstants.TemProfileProperties.ACCOUNTS + "[" + i + "]");
            TemProfileAccount account = profile.getAccounts().get(i);
            paths.remove(paths.size() - 1);
        }

        //Arranger section validation
        if (profile.getArrangers() != null) {
            int arrangerPrimaryCount = 0;
            Set<String> arrangerId = new HashSet<String>();
            for (TemProfileArranger arranger : profile.getArrangers()) {
                if (arranger.getPrimary()) {
                    arrangerPrimaryCount++;
                }

                arrangerId.add(arranger.getPrincipalName());

                paths.add(TemPropertyConstants.TemProfileProperties.ARRANGERS);

                paths.remove(paths.size() - 1);
            }

            //check for multiple primary arrangers; only 1 primary allowed.
            if (arrangerPrimaryCount > 1) {
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.ARRANGERS,
                    TemKeyConstants.ERROR_TEM_PROFILE_ARRANGER_PRIMARY);
                success = false;
            }

            //check for duplicate arrangers
            if (profile.getArrangers().size() != arrangerId.size()) {
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.ARRANGERS,
                    TemKeyConstants.ERROR_TEM_PROFILE_ARRANGER_DUPLICATE);
                success = false;
            }

        }

        paths.clear();
        success = success && super.dataDictionaryValidate(document);
        if (success) {
            Person user = GlobalVariables.getUserSession().getPerson();
            profile.setUpdatedBy(user.getPrincipalName());
            Date current = new Date();
            profile.setLastUpdate(new java.sql.Date(current.getTime()));
        }
        return success;

    }

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject)
     */
    @Override
    public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) {
        TemProfile profile = (TemProfile) document.getNewMaintainableObject().getBusinessObject();

        //set other arranger as primary false if the new arranger is the primary
        if (line instanceof TemProfileArranger) {
            TemProfileArranger arranger = (TemProfileArranger) line;

            //check that the arranger's org is under the initiator's
            if (TemConstants.NONEMP_TRAVELER_TYP_CD.equals(profile.getTravelerTypeCode())) {
                TravelService travelService = SpringContext.getBean(TravelService.class);
                String initiatorId = document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
                final Person initiator = SpringContext.getBean(PersonService.class).getPerson(initiatorId);

            }

            if (arranger.getPrimary()) {
                for (TemProfileArranger tempArranger : ((TemProfile) document.getNewMaintainableObject().getBusinessObject()).getArrangers()) {
                    tempArranger.setPrimary(false);
                }

                if (TemConstants.NONEMP_TRAVELER_TYP_CD.equals(profile.getTravelerTypeCode())) {
                    String profileChart = profile.getHomeDeptChartOfAccountsCode();
                    String profileOrg = profile.getHomeDeptOrgCode();

                    final Person arrangerPerson = SpringContext.getBean(PersonService.class).getPerson(arranger.getPrincipalId());
                    String primaryDeptCode[] = arrangerPerson.getPrimaryDepartmentCode().split("-");
                    if (primaryDeptCode != null && primaryDeptCode.length == 2) {
                        profile.setHomeDeptChartOfAccountsCode(primaryDeptCode[0]);
                        profile.setHomeDeptOrgCode(primaryDeptCode[1]);
                    }
                }

            }
        }
        // check for CreditCardAgency accounts
        else if (line instanceof TemProfileAccount) {
            TemProfileAccount account = (TemProfileAccount) line;
            return checkNewTemProfileAccount(profile, account);
        }

        return super.processCustomAddCollectionLineBusinessRules(document, collectionName, line);
    }

    /**
     * Performs rules against a new TEM Profile account
     *
     * @param account
     * @return
     */
    protected boolean checkNewTemProfileAccount(TemProfile temProfile, TemProfileAccount account) {
        boolean success = true;
        //minimum length
        if (!StringUtils.isBlank(account.getAccountNumber())) {
            final Integer minLength = getDataDictionaryService().getAttributeMaxLength(TemProfile.class, TemPropertyConstants.TemProfileProperties.ACCOUNT_NUMBER);
            if (minLength != null && account.getAccountNumber().length() < minLength.intValue()) {
                final String label = getDataDictionaryService().getAttributeLabel(TemProfile.class, TemPropertyConstants.TemProfileProperties.ACCOUNT_NUMBER);

                String errorMessage[] = null;
                errorMessage = new String[]{label, minLength.toString()};
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_MIN_LENGTH, errorMessage);
                success = false;
            }

            //do not duplicate an existing account in the system
            if (doesProfileAccountMatchExisting(temProfile, account)) {
                if (account.getCreditCardOrAgencyCode() != null) {
                    account.refreshReferenceObject(TemPropertyConstants.CREDIT_CARD_AGENCY);
                }
                GlobalVariables.getMessageMap().putError(TemPropertyConstants.TemProfileProperties.ACCOUNT_NUMBER, TemKeyConstants.ERROR_TEM_PROFILE_ACCOUNT_ID_DUPLICATE, new String[]{account.getCreditCardAgency().getCreditCardOrAgencyName(), account.getAccountNumber()});
                success = false;
            }
        }

        return success;
    }

    /**
     * Checks if the given newAccount matches - by account number and credit card agency - an already existing profile account on the profile
     *
     * @param profile    the profile with existing profile accounts
     * @param newAccount the account to see if it exists among the existing accounts
     * @return true if the newAccount is already on the profile; false otherwise
     */
    protected boolean doesProfileAccountMatchExisting(TemProfile profile, TemProfileAccount newAccount) {
        if (profile.getAccounts() == null || profile.getAccounts().isEmpty()) {
            return getTemProfileService().doesProfileAccountExist(newAccount, profile);
        }
        for (TemProfileAccount existingAccount : profile.getAccounts()) {
            if (StringUtils.equals(existingAccount.getAccountNumber(), newAccount.getAccountNumber()) && StringUtils.equals(existingAccount.getCreditCardOrAgencyCode(), newAccount.getCreditCardOrAgencyCode())) {
                return true;
            }
        }
        return getTemProfileService().doesProfileAccountExist(newAccount, profile);
    }

    /**
     * Checks that, if the profile represents a non-employee, the profile has an active arranger
     *
     * @param profile the profile to check
     * @return true if the profile passed the validation, false otherwise
     */
    protected boolean checkActiveArrangersForNonEmployees(TemProfile profile) {
        boolean success = true;
        if (profile != null && getTemProfileService().isProfileNonEmploye(profile)) {
            // we've got a non-employee.  let's see if they have at least one active arranger
            if (!getTemProfileService().hasActiveArrangers(profile)) {
                GlobalVariables.getMessageMap().putErrorForSectionId(TRAVEL_ARRANGERS_SECTION_ID, TemKeyConstants.ERROR_TEM_PROFILE_NONEMPLOYEE_MUST_HAVE_ACTIVE_ARRANGER);
                success = false;
            }
        }
        return success;
    }

    /**
     * Checks that, if the profile represents a non-employee, the profile has an email address
     *
     * @param profile the profile to check
     * @return true if the profile passed the validation, false otherwise
     */
    protected boolean checkEmailAddressForNonEmployees(TemProfile profile) {
        boolean success = true;
        if (profile != null && getTemProfileService().isProfileNonEmploye(profile)) {
            // we've got a non-employee.  let's see if they have an email address
            if (StringUtils.isBlank(profile.getEmailAddress())) {
                GlobalVariables.getMessageMap().putError("document.newMaintainableObject." + TemPropertyConstants.TemProfileProperties.EMAIL_ADDRESS, TemKeyConstants.ERROR_TEM_PROFILE_NONEMPLOYEE_MUST_HAVE_EMAIL);
                success = false;
            }
        }
        return success;
    }

    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
        boolean success = true;
        TemProfile profile = (TemProfile) document.getNewMaintainableObject().getBusinessObject();
        if (ObjectUtils.isNotNull(profile.getHomeDeptOrg())) {
            if (!profile.getHomeDeptOrg().isActive()) {
                putFieldError(TemProfileProperties.HOME_DEPARTMENT, TemKeyConstants.ERROR_TEM_PROFILE_ORGANIZATION_INACTIVE, profile.getHomeDeptChartOfAccountsCode() + "-" + profile.getHomeDeptOrgCode());
                success = false;
            }
        }
        success &= checkActiveArrangersForNonEmployees(profile);
        success &= checkEmailAddressForNonEmployees(profile);
        return success;
    }

    protected BusinessObjectService getBusinessObjectService() {
        return SpringContext.getBean(BusinessObjectService.class);
    }

    protected TemProfileService getTemProfileService() {
        if (temProfileService == null) {
            temProfileService = SpringContext.getBean(TemProfileService.class);
        }
        return temProfileService;
    }

    @Override
    protected DataDictionaryService getDataDictionaryService() {
        if (dataDictionaryService == null) {
            dataDictionaryService = SpringContext.getBean(DataDictionaryService.class);
        }
        return dataDictionaryService;
    }

}
