/*
 * 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.coa.identity;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountDelegate;
import org.kuali.kfs.coa.service.AccountDelegateService;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.integration.cg.ContractsAndGrantsModuleService;
import org.kuali.kfs.kns.kim.role.DerivedRoleTypeServiceBase;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.MailService;
import org.kuali.kfs.krad.util.ObjectUtils;
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.kfs.sys.document.FinancialSystemMaintainable;
import org.kuali.kfs.sys.identity.KfsKimAttributes;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.membership.MemberType;
import org.kuali.rice.core.web.format.CurrencyFormatter;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.role.RoleMembership;
import org.kuali.rice.kim.api.services.IdentityManagementService;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AccountDerivedRoleTypeServiceImpl extends DerivedRoleTypeServiceBase {

    protected AccountService accountService;
    protected ContractsAndGrantsModuleService contractsAndGrantsModuleService;
    protected AccountDelegateService accountDelegateService;
    protected ConfigurationService configurationService;
    protected IdentityManagementService identityManagementService;
    protected MailService mailService;
    protected ParameterService parameterService;

    protected final static String DERIVED_ROLE_MEMBER_INACTIVATION_NOTIFICATION_EMAIL_ADDRESSES_PARAMETER_NAME = "DERIVED_ROLE_MEMBER_INACTIVATION_NOTIFICATION_EMAIL_ADDRESSES";

    /**
     * Attributes: Chart Code Account Number Requirements: - KFS-COA Account Supervisor: CA_ACCOUNT_T.ACCT_SPVSR_UNVL_ID - KFS-SYS
     * Fiscal Officer: CA_ACCOUNT_T.ACCT_FSC_OFC_UID - KFS-SYS Fiscal Officer Primary Delegate: CA_ACCT_DELEGATE_T.ACCT_DLGT_UNVL_ID
     * where ACCT_DLGT_PRMRT_CD = Y - KFS-SYS Fiscal Officer Secondary Delegate: CA_ACCT_DELEGATE_T.ACCT_DLGT_UNVL_ID where
     * ACCT_DLGT_PRMRT_CD = N - KFS-SYS Award Project Director: use the getProjectDirectorForAccount(String chartOfAccountsCode,
     * String accountNumber) method on the contracts and grants module service
     *
     * @see org.kuali.kfs.kns.kim.role.RoleTypeServiceBase#getPrincipalIdsFromApplicationRole(java.lang.String,
     * java.lang.String, org.kuali.kfs.kim.bo.types.dto.AttributeSet)
     */
    @Override
    public List<RoleMembership> getRoleMembersFromDerivedRole(String namespaceCode, String roleName, Map<String, String> qualification) {
        // validate received attributes
        validateRequiredAttributesAgainstReceived(qualification);
        List<RoleMembership> members = new ArrayList<RoleMembership>();
        if (qualification != null && !qualification.isEmpty()) {
            String chartOfAccountsCode = qualification.get(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE);
            String accountNumber = qualification.get(KfsKimAttributes.ACCOUNT_NUMBER);

            String financialSystemDocumentTypeCodeCode = qualification.get(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME);
            String totalDollarAmount = qualification.get(KfsKimAttributes.FINANCIAL_DOCUMENT_TOTAL_AMOUNT);

            String fiscalOfficerPrincipalID = qualification.get(KFSPropertyConstants.ACCOUNT_FISCAL_OFFICER_SYSTEM_IDENTIFIER);
            String accountSupervisorPrincipalID = qualification.get(KFSPropertyConstants.ACCOUNTS_SUPERVISORY_SYSTEMS_IDENTIFIER);
            String principalId = qualification.get(KimConstants.AttributeConstants.PRINCIPAL_ID);

            // Default to 0 total amount
            if (StringUtils.isEmpty(totalDollarAmount)) {
                totalDollarAmount = getDefaultTotalAmount();
            }

            Map<String, String> roleQualifier = new HashMap<String, String>();
            roleQualifier.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
            roleQualifier.put(KfsKimAttributes.ACCOUNT_NUMBER, accountNumber);

            if (chartOfAccountsCode == null && accountNumber == null && StringUtils.isNotBlank(principalId)) {
                RoleMembership roleMembershipInfo = getRoleMembershipWhenAccountInfoUnavailable(roleName, principalId, roleQualifier);
                if (ObjectUtils.isNotNull(roleMembershipInfo)) {
                    members.add(roleMembershipInfo);
                }
            }

            if (KFSConstants.SysKimApiConstants.ACCOUNT_SUPERVISOR_KIM_ROLE_NAME.equals(roleName)) {
                Account account = getAccount(chartOfAccountsCode, accountNumber);
                if (account != null) {
                    members.add(RoleMembership.Builder.create(null, null, account.getAccountsSupervisorySystemsIdentifier(), MemberType.PRINCIPAL, roleQualifier).build());
                }
                // only add the additional approver if they are different
                if (StringUtils.isNotBlank(accountSupervisorPrincipalID) && (account == null || !StringUtils.equals(accountSupervisorPrincipalID, account.getAccountsSupervisorySystemsIdentifier()))) {
                    members.add(RoleMembership.Builder.create(null, null, accountSupervisorPrincipalID, MemberType.PRINCIPAL, roleQualifier).build());
                }
            } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_KIM_ROLE_NAME.equals(roleName)) {
                Account account = getAccount(chartOfAccountsCode, accountNumber);
                if (account != null) {
                    members.add(RoleMembership.Builder.create(null, null, account.getAccountFiscalOfficerSystemIdentifier(), MemberType.PRINCIPAL, roleQualifier).build());
                }
                // only add the additional approver if they are different
                if (StringUtils.isNotBlank(fiscalOfficerPrincipalID) && (account == null || !StringUtils.equals(fiscalOfficerPrincipalID, account.getAccountFiscalOfficerSystemIdentifier()))) {
                    members.add(RoleMembership.Builder.create(null, null, fiscalOfficerPrincipalID, MemberType.PRINCIPAL, roleQualifier).build());
                }
            } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_PRIMARY_DELEGATE_KIM_ROLE_NAME.equals(roleName)) {
                AccountDelegate delegate = getPrimaryDelegate(chartOfAccountsCode, accountNumber, financialSystemDocumentTypeCodeCode, totalDollarAmount);
                if (delegate != null) {
                    roleQualifier.put(KfsKimAttributes.FINANCIAL_SYSTEM_DOCUMENT_TYPE_CODE, delegate.getFinancialDocumentTypeCode());
                    roleQualifier.put(KfsKimAttributes.FROM_AMOUNT, (delegate.getFinDocApprovalFromThisAmt() == null) ? "0" : delegate.getFinDocApprovalFromThisAmt().toString());
                    roleQualifier.put(KfsKimAttributes.TO_AMOUNT, (delegate.getFinDocApprovalToThisAmount() == null) ? "NOLIMIT" : delegate.getFinDocApprovalToThisAmount().toString());
                    members.add(RoleMembership.Builder.create(null, null, delegate.getAccountDelegateSystemId(), MemberType.PRINCIPAL, roleQualifier).build());
                }
            } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_SECONDARY_DELEGATE_KIM_ROLE_NAME.equals(roleName)) {
                List<AccountDelegate> delegates = getSecondaryDelegates(chartOfAccountsCode, accountNumber, financialSystemDocumentTypeCodeCode, totalDollarAmount);
                for (AccountDelegate delegate : delegates) {
                    roleQualifier.put(KfsKimAttributes.FINANCIAL_SYSTEM_DOCUMENT_TYPE_CODE, delegate.getFinancialDocumentTypeCode());
                    roleQualifier.put(KfsKimAttributes.FROM_AMOUNT, (delegate.getFinDocApprovalFromThisAmt() == null) ? "0" : delegate.getFinDocApprovalFromThisAmt().toString());
                    roleQualifier.put(KfsKimAttributes.TO_AMOUNT, (delegate.getFinDocApprovalToThisAmount() == null) ? "NOLIMIT" : delegate.getFinDocApprovalToThisAmount().toString());
                    members.add(RoleMembership.Builder.create(null, null, delegate.getAccountDelegateSystemId(), MemberType.PRINCIPAL, roleQualifier).build());
                }
            } else if (KFSConstants.SysKimApiConstants.AWARD_SECONDARY_DIRECTOR_KIM_ROLE_NAME.equals(roleName)) {
                Person person = getProjectDirectorForAccount(chartOfAccountsCode, accountNumber);
                if (person != null) {
                    members.add(RoleMembership.Builder.create(null, null, person.getPrincipalId(), MemberType.PRINCIPAL, roleQualifier).build());
                }
            }
        }
        return members;
    }

    // build memebership information based on the given role name for the given principal when account information is unavailable
    protected RoleMembership getRoleMembershipWhenAccountInfoUnavailable(String roleName, String principalId, Map<String, String> roleQualifier) {
        BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
        Map<String, Object> fieldValues = new HashMap<String, Object>();

        RoleMembership roleMembership = null;

        if ((KFSConstants.SysKimApiConstants.FISCAL_OFFICER_KIM_ROLE_NAME.equals(roleName))) {
            fieldValues.put(KFSPropertyConstants.ACCOUNT_FISCAL_OFFICER_SYSTEM_IDENTIFIER, principalId);

            if (businessObjectService.countMatching(Account.class, fieldValues) > 0) {
                roleMembership = RoleMembership.Builder.create(null, null, principalId, MemberType.PRINCIPAL, roleQualifier).build();
            }
        } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_PRIMARY_DELEGATE_KIM_ROLE_NAME.equals(roleName)) {
            fieldValues.put(KFSPropertyConstants.ACCOUNT_DELEGATE_SYSTEM_ID, principalId);
            fieldValues.put(KFSPropertyConstants.ACCOUNTS_DELEGATE_PRMRT_INDICATOR, Boolean.TRUE);

            if (businessObjectService.countMatching(AccountDelegate.class, fieldValues) > 0) {
                roleMembership = RoleMembership.Builder.create(null, null, principalId, MemberType.PRINCIPAL, roleQualifier).build();
            }
        } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_SECONDARY_DELEGATE_KIM_ROLE_NAME.equals(roleName)) {
            fieldValues.put(KFSPropertyConstants.ACCOUNT_DELEGATE_SYSTEM_ID, principalId);
            fieldValues.put(KFSPropertyConstants.ACCOUNTS_DELEGATE_PRMRT_INDICATOR, Boolean.FALSE);

            if (businessObjectService.countMatching(AccountDelegate.class, fieldValues) > 0) {
                roleMembership = RoleMembership.Builder.create(null, null, principalId, MemberType.PRINCIPAL, roleQualifier).build();
            }
        }

        return roleMembership;
    }

    protected Account getAccount(String chartOfAccountsCode, String accountNumber) {
        return getAccountService().getByPrimaryId(chartOfAccountsCode, accountNumber);
    }

    protected AccountDelegate getPrimaryDelegate(String chartOfAccountsCode, String accountNumber, String fisDocumentTypeCode, String totalDollarAmount) {
        return getAccountService().getPrimaryDelegationByExample(getDelegateExample(chartOfAccountsCode, accountNumber, fisDocumentTypeCode), totalDollarAmount);
    }

    protected List<AccountDelegate> getSecondaryDelegates(String chartOfAccountsCode, String accountNumber, String fisDocumentTypeCode, String totalDollarAmount) {
        return getAccountService().getSecondaryDelegationsByExample(getDelegateExample(chartOfAccountsCode, accountNumber, fisDocumentTypeCode), totalDollarAmount);
    }

    protected Person getProjectDirectorForAccount(String chartOfAccountsCode, String accountNumber) {
        return getContractsAndGrantsModuleService().getProjectDirectorForAccount(chartOfAccountsCode, accountNumber);
    }

    // Default to 0 total amount
    protected String getDefaultTotalAmount() {
        return "0";
    }

    protected AccountDelegate getDelegateExample(String chartOfAccountsCode, String accountNumber, String fisDocumentTypeCode) {
        AccountDelegate delegateExample = new AccountDelegate();
        delegateExample.setChartOfAccountsCode(chartOfAccountsCode);
        delegateExample.setAccountNumber(accountNumber);
        delegateExample.setFinancialDocumentTypeCode(fisDocumentTypeCode);
        return delegateExample;
    }

    /**
     * Gets the accountService attribute.
     *
     * @return Returns the accountService.
     */
    protected AccountService getAccountService() {
        if (accountService == null) {
            accountService = SpringContext.getBean(AccountService.class);
        }
        return accountService;
    }

    /**
     * Sets the accountService attribute value.
     *
     * @param accountService The accountService to set.
     */
    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * @return an implementation of the AccountDelegateService
     */
    protected AccountDelegateService getAccountDelegateService() {
        if (accountDelegateService == null) {
            accountDelegateService = SpringContext.getBean(AccountDelegateService.class);
        }
        return accountDelegateService;
    }

    /**
     * Sets the accountDelegateService attribute value.
     *
     * @param accountDelegateService The accountDelegateService to set.
     */
    public void setAccountDelegateService(AccountDelegateService accountDelegateService) {
        this.accountDelegateService = accountDelegateService;
    }

    /**
     * @return an implementation of the ConfigurationService
     */
    protected ConfigurationService getConfigurationService() {
        if (configurationService == null) {
            configurationService = SpringContext.getBean(ConfigurationService.class);
        }
        return configurationService;
    }

    /**
     * @return an implementation of the IdentityManagementService
     */
    protected IdentityManagementService getIdentityManagementService() {
        if (identityManagementService == null) {
            identityManagementService = SpringContext.getBean(IdentityManagementService.class);
        }
        return identityManagementService;
    }

    /**
     * @return an implementation of the MailService
     */
    protected MailService getMailService() {
        if (mailService == null) {
            mailService = SpringContext.getBean(MailService.class);
        }
        return mailService;
    }

    /**
     * @return an implementation of the ParameterService
     */
    protected ParameterService getParameterService() {
        if (parameterService == null) {
            parameterService = SpringContext.getBean(ParameterService.class);
        }
        return parameterService;
    }

    protected ContractsAndGrantsModuleService getContractsAndGrantsModuleService() {
        if (contractsAndGrantsModuleService == null) {
            contractsAndGrantsModuleService = SpringContext.getBean(ContractsAndGrantsModuleService.class);
        }
        return contractsAndGrantsModuleService;
    }

    /**
     * @see org.kuali.rice.kns.kim.role.RoleTypeServiceBase#principalInactivated(java.lang.String, java.lang.String, java.lang.String)
     */
    //   @Override
    public void principalInactivated(String principalId, String namespaceCode, String roleName) {
        //    super.principalInactivated(principalId, namespaceCode, roleName);

        if (KFSConstants.SysKimApiConstants.ACCOUNT_SUPERVISOR_KIM_ROLE_NAME.equals(roleName)) {
            if (getAccountService().isPrincipalInAnyWayShapeOrFormAccountSupervisor(principalId)) {
                handleAccountSupervisorInactivation(principalId, namespaceCode, roleName);
            }
        } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_KIM_ROLE_NAME.equals(roleName)) {
            if (getAccountService().isPrincipalInAnyWayShapeOrFormFiscalOfficer(principalId)) {
                handleFiscalOfficerInactivation(principalId, namespaceCode, roleName);
            }
        } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_PRIMARY_DELEGATE_KIM_ROLE_NAME.equals(roleName)) {
            if (getAccountDelegateService().isPrincipalInAnyWayShapeOrFormPrimaryAccountDelegate(principalId)) {
                handlePrimaryAccountDelegateInactivation(principalId, namespaceCode, roleName);
            }
        } else if (KFSConstants.SysKimApiConstants.FISCAL_OFFICER_SECONDARY_DELEGATE_KIM_ROLE_NAME.equals(roleName)) {
            if (getAccountDelegateService().isPrincipalInAnyWayShapeOrFormSecondaryAccountDelegate(principalId)) {
                handleSecondaryAccountDelegateInactivation(principalId, namespaceCode, roleName);
            }
        }
    }

    /**
     * Notifies the specified list that the given principal is being inactivated, listing any active or expired accounts the principal is fiscal officer of
     *
     * @param principalId   the principal id of the inactivating person
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     */
    protected void handleFiscalOfficerInactivation(String principalId, String roleNamespace, String roleName) {
        // build the message
        final Principal principal = getIdentityManagementService().getPrincipal(principalId);

        Iterator<Account> activeAccounts = getAccountService().getActiveAccountsForFiscalOfficer(principalId);
        final String joinedActiveAccounts = joinAccounts(activeAccounts);
        Iterator<Account> inactiveAccounts = getAccountService().getExpiredAccountsForFiscalOfficer(principalId);
        final String joinedExpiredAccounts = joinAccounts(inactiveAccounts);

        final String message = getMessage(KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_FISCAL_OFFICER_NOTIFICATION, roleNamespace, roleName, principal.getPrincipalName(), joinedActiveAccounts, joinedExpiredAccounts);

        // get listserv to mail to
        String toAddress = getDeactivationInterestAddress();
        try {

            // get mail message
            MailMessage mailMessage = buildMailMessage(toAddress, message, roleNamespace, roleName, principalId);
            // send it
            getMailService().sendMessage(mailMessage);
        } catch (Exception iae) {
            throw new RuntimeException("Could not mail principal inactivation notification to " + toAddress);
        }

    }

    /**
     * Notifies the specified list that the given principal is being inactivated, listing any active or expired accounts the principal is account supervisor of
     *
     * @param principalId   the principal id of the inactivating person
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     */
    protected void handleAccountSupervisorInactivation(String principalId, String roleNamespace, String roleName) {
        // build the message
        final Principal principal = getIdentityManagementService().getPrincipal(principalId);

        Iterator<Account> activeAccounts = getAccountService().getActiveAccountsForAccountSupervisor(principalId);
        final String joinedActiveAccounts = joinAccounts(activeAccounts);
        Iterator<Account> inactiveAccounts = getAccountService().getExpiredAccountsForAccountSupervisor(principalId);
        final String joinedExpiredAccounts = joinAccounts(inactiveAccounts);

        final String message = getMessage(KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_ACCOUNT_SUPERVISOR_NOTIFICATION, roleNamespace, roleName, principal.getPrincipalName(), joinedActiveAccounts, joinedExpiredAccounts);

        // get listserv to mail to
        String toAddress = getDeactivationInterestAddress();
        try {

            // get mail message
            MailMessage mailMessage = buildMailMessage(toAddress, message, roleNamespace, roleName, principalId);
            // send it
            getMailService().sendMessage(mailMessage);
        } catch (Exception iae) {
            throw new RuntimeException("Could not mail principal inactivation notification to " + toAddress);
        }
    }

    /**
     * Does the logic for the inactivation of a secondary delegate
     *
     * @param principalId   the principal id of the inactivating person
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     */
    protected void handlePrimaryAccountDelegateInactivation(String principalId, String roleNamespace, String roleName) {
        handleAccountDelegateInactivation(principalId, roleNamespace, roleName, true);
    }

    /**
     * Does the logic for the inactivation of a secondary delegate
     *
     * @param principalId   the principal id of the inactivating person
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     */
    protected void handleSecondaryAccountDelegateInactivation(String principalId, String roleNamespace, String roleName) {
        handleAccountDelegateInactivation(principalId, roleNamespace, roleName, false);
    }

    /**
     * Handles the inactivation of account delegates when their principal is inactivated
     *
     * @param principalId   the principal id of the inactivating person
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     * @param primary       whether primary delegates should be inactivated, rather than secondary
     */
    protected void handleAccountDelegateInactivation(String principalId, String roleNamespace, String roleName, boolean primary) {
        List<String> accountDisdelegations = new ArrayList<String>();
        List<String> blockedAccountDisdelegations = new ArrayList<String>();

        Iterator<AccountDelegate> accountDelegationsToInactivate = getAccountDelegateService().retrieveAllActiveDelegationsForPerson(principalId, primary);
        while (accountDelegationsToInactivate.hasNext()) {
            final AccountDelegate accountDelegate = accountDelegationsToInactivate.next();
            final String blockingDocumentId = lookupDocumentBlockingInactivation(accountDelegate);
            if (StringUtils.isBlank(blockingDocumentId)) {
                accountDisdelegations.add(getAccountDisdelegationInformationForAccountDelegate(accountDelegate));
                inactivateDelegation(accountDelegate);
            } else {
                blockedAccountDisdelegations.add(getBlockedAccountDisdelegationInformationForAccountDelegate(accountDelegate, blockingDocumentId));
            }
        }
        notifyListAboutAccountDelegateInactivations(principalId, roleNamespace, roleName, accountDisdelegations, blockedAccountDisdelegations, primary);
    }

    /**
     * Returns the document number of a maintenance document which would block the inactivation of the given account delegate
     *
     * @param delegate the account delegate to check
     * @return the document id of the maintenance document blocking the inactivation of the given account delegate, null otherwise
     */
    protected String lookupDocumentBlockingInactivation(AccountDelegate delegate) {
        FinancialSystemMaintainable maintainable = getAccountDelegateService().buildMaintainableForAccountDelegate(delegate);
        return maintainable.getLockingDocumentId();
    }

    /**
     * Inactivates an account delegate
     *
     * @param delegate the account delegate to inactivate
     */
    protected void inactivateDelegation(AccountDelegate delegate) {
        delegate.setActive(false); // this is deprecated?  lame!
        getBusinessObjectService().save(delegate);
    }

    /**
     * Converts information from the given AccountDelegate record into an informative String
     *
     * @param accountDelegate the account delegate to report on
     * @return the report on the given account delegate
     */
    protected String getAccountDisdelegationInformationForAccountDelegate(AccountDelegate accountDelegate) {
        final CurrencyFormatter formatter = new CurrencyFormatter();
        final String message = getMessage(KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_ACCOUNT_DELEGATE_INACTIVATED_INFORMATION, accountDelegate.getChartOfAccountsCode(), accountDelegate.getAccountNumber(), accountDelegate.getFinancialDocumentTypeCode(), (String) (accountDelegate.getFinDocApprovalFromThisAmt() != null ? formatter.format(accountDelegate.getFinDocApprovalFromThisAmt()) : ""), (String) (accountDelegate.getFinDocApprovalFromThisAmt() != null ? formatter.format(accountDelegate.getFinDocApprovalFromThisAmt()) : ""));
        return message;
    }

    /**
     * Converts information from the given AccountDelegate record into an informative String
     *
     * @param accountDelegate the account delegate to report on
     * @return the report on the given account delegate
     */
    protected String getBlockedAccountDisdelegationInformationForAccountDelegate(AccountDelegate accountDelegate, String blockingDocumentId) {
        final CurrencyFormatter formatter = new CurrencyFormatter();
        final String message = getMessage(KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_ACCOUNT_DELEGATE_BLOCKED_INACTIVATION_INFORMATION, accountDelegate.getChartOfAccountsCode(), accountDelegate.getAccountNumber(), accountDelegate.getFinancialDocumentTypeCode(), (String) (accountDelegate.getFinDocApprovalFromThisAmt() != null ? formatter.format(accountDelegate.getFinDocApprovalFromThisAmt()) : ""), (String) (accountDelegate.getFinDocApprovalFromThisAmt() != null ? formatter.format(accountDelegate.getFinDocApprovalFromThisAmt()) : ""), blockingDocumentId);
        return message;
    }

    /**
     * Fires an e-mail off the specified e-mail listserv about the account delegates inactivated by a
     *
     * @param principalId           the principal being inactivated
     * @param roleNamespace         the namespace of the role the inactivated principal is leaving
     * @param roleName              the name of the role the inactivated principal is leaving
     * @param accountsDisdelegated  the account delegate records which were inactivated
     * @param blockedDisdelegations the account delegate records which could not be inactivated because of locking maintenance documents
     * @param primary               whether the account delegations being reported on were primary delegations, as opposed to secondary delegations
     */
    protected void notifyListAboutAccountDelegateInactivations(String principalId, String roleNamespace, String roleName, List<String> accountsDisdelegated, List<String> blockedDisdelegations, boolean primary) {
        // build message
        final Principal principal = getIdentityManagementService().getPrincipal(principalId);

        final String message = getMessage((primary ? KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_FISCAL_OFFICER_PRIMARY_DELEGATE_NOTIFICATION : KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_FISCAL_OFFICER_SECONARY_DELEGATE_NOTIFICATION), roleNamespace, roleName, principal.getPrincipalName(), StringUtils.join(accountsDisdelegated, '\n'), StringUtils.join(blockedDisdelegations, '\n'));

        String toAddress = getDeactivationInterestAddress();
        try {
            // get mail message
            MailMessage mailMessage = buildMailMessage(toAddress, message, roleNamespace, roleName, principalId);
            // send it
            getMailService().sendMessage(mailMessage);
        } catch (Exception iae) {
            throw new RuntimeException("Could not mail principal inactivation notification to " + toAddress);
        }
    }

    /**
     * Retrieves message from configuration service and formats it directly
     *
     * @param messageConstant the property to find the message under
     * @param parameters      the parameters to interpolate into the message
     * @return a formatted String
     */
    protected String getMessage(String messageConstant, String... parameters) {
        final String messagePattern = getConfigurationService().getPropertyValueAsString(messageConstant);
        return MessageFormat.format(messagePattern, (Object[]) parameters);
    }

    /**
     * Joins an Iterator of accounts into a meaningful String
     *
     * @param accounts the accounts to join
     * @return a String of account information
     */
    protected String joinAccounts(Iterator<Account> accounts) {
        StringBuilder s = new StringBuilder();
        int count = 0;

        while (accounts.hasNext()) {
            final Account account = accounts.next();
            if (count > 0) {
                s.append('\n');
            }
            s.append(account.getChartOfAccountsCode() + "-" + account.getAccountNumber());
            count += 1;
        }
        return s.toString();
    }

    /**
     * Builds a MailMessage to send out a notification e-mail
     *
     * @param toAddress     the address to send the notification to
     * @param message       the body of the notification
     * @param principalId   the principal being inactivated
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     * @return an appropriately constructed MailMessage
     */
    protected MailMessage buildMailMessage(String toAddress, String message, String roleNamespace, String roleName, String principalId) {
        MailMessage mailMessage = new MailMessage();
        mailMessage.setFromAddress(getFromAddress());
        mailMessage.setSubject(getMailMessageSubject(roleNamespace, roleName, principalId));

        Set<String> toAddresses = new HashSet<String>();
        toAddresses.add(toAddress);
        mailMessage.setToAddresses(toAddresses);
        mailMessage.setMessage(message);
        return mailMessage;
    }

    /**
     * Builds a subject line for an e-mail
     *
     * @param principalId   the principal being inactivated
     * @param roleNamespace the namespace of the role the inactivated principal is leaving
     * @param roleName      the name of the role the inactivated principal is leaving
     * @return a suitable subject line for the notification e-mail
     */
    protected String getMailMessageSubject(String roleNamespace, String roleName, String principalId) {
        final Principal principal = getIdentityManagementService().getPrincipal(principalId);
        return getMessage(KFSKeyConstants.MESSAGE_ACCOUNT_DERIVED_ROLE_PRINCIPAL_INACTIVATED_NOTIFICATION_SUBJECT, roleNamespace, roleName, principal.getPrincipalName());
    }

    /**
     * @return the from address to send mail from; in the default version, the given toAddress (but since this is a method, implementers can override)
     */
    protected String getFromAddress() {
        return getDeactivationInterestAddress();
    }

    /**
     * @return the e-mail address of those interested in account role principal inactivations
     */
    protected String getDeactivationInterestAddress() {
        return getParameterService().getParameterValueAsString(Account.class, AccountDerivedRoleTypeServiceImpl.DERIVED_ROLE_MEMBER_INACTIVATION_NOTIFICATION_EMAIL_ADDRESSES_PARAMETER_NAME);
    }


}
