/*
 * 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.sys.service.impl;

import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.fp.document.AdvanceDepositDocument;
import org.kuali.kfs.integration.ar.AccountsReceivableModuleService;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.ElectronicPaymentClaim;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy;
import org.kuali.kfs.sys.service.ElectronicPaymentClaimingService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.parameter.ParameterEvaluatorService;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.services.IdentityManagementService;

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

public class ElectronicPaymentClaimingServiceImpl implements ElectronicPaymentClaimingService {
    private org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ElectronicPaymentClaimingServiceImpl.class);
    private DocumentService documentService;
    private BusinessObjectService businessObjectService;
    private ParameterService parameterService;
    private DateTimeService dateTimeService;

    protected static final String ELECTRONIC_FUNDS_CLAIMANT_GROUP_PARAMETER = "ELECTRONIC_FUNDS_CLAIMANT_GROUP";
    protected final static String ELECTRONIC_PAYMENT_ADMINISTRATOR_GROUP_PARAM_NAME = "ELECTRONIC_FUNDS_ADMINISTRATOR_GROUP";
    protected static final String ELECTRONIC_FUNDS_CLAIM_SUMMARIES_PER_NOTE_PARAMETER = "ELECTRONIC_FUNDS_CLAIM_SUMMARIES_PER_NOTE";
    protected static final String CLAIMING_NOTE_PRELUDE = "Claiming CR Items: ";
    protected static final String DI_CLAIMING_DOC_HELPER_BEAN_NAME = "distributionOfIncomeAndExpenseElectronicPaymentClaimingDocumentHelper";
    protected static final String YEDI_CLAIMING_DOC_HELPER_BEAN_NAME = "yearEndDistributionOfIncomeAndExpenseElectronicPaymentClaimingDocumentHelper";
    protected static final String CLAIMING_DOC_HELPER_BEAN_NAME = "expenseElectronicPaymentClaimingDocumentHelper";
    protected static final String ELECTRONIC_PAYMENT_CLAIM_ACCOUNTS_PARAMETER = "ELECTRONIC_FUNDS_ACCOUNTS";

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingService#constructNotesForClaims(java.util.List)
     */
    public List<String> constructNoteTextsForClaims(List<ElectronicPaymentClaim> claims) {
        int summariesPerNote;
        List<String> noteTexts = new ArrayList<String>();
        try {
            summariesPerNote = Integer.parseInt(parameterService.getParameterValueAsString(ElectronicPaymentClaim.class, ELECTRONIC_FUNDS_CLAIM_SUMMARIES_PER_NOTE_PARAMETER));
            int i = 0;
            while (i < claims.size()) {
                String noteText = constructNoteText(claims, i, summariesPerNote);
                noteTexts.add(noteText);
                i += summariesPerNote;
            }
        } catch (NumberFormatException nfe) {
            throw new RuntimeException("The KFS-SYS / ElectronicPaymentClaim / " + ELECTRONIC_FUNDS_CLAIM_SUMMARIES_PER_NOTE_PARAMETER + " should have a value that can be parsed into an integer.", nfe);
        }
        return noteTexts;
    }

    /**
     * This creates a note for the given point in the list of summaries.
     *
     * @param claims              a List of ElectronicPaymentClaim records that are being claimed
     * @param startPoint          the point in the list the note is starting at
     * @param maxSummariesPerNote the number of ElectronicPaymentClaim summaries we can have on a note
     * @return a newly constructed note, that needs to have a user added
     */
    protected String constructNoteText(List<ElectronicPaymentClaim> claims, int startPoint, int maxSummariesPerNote) {
        StringBuilder sb = new StringBuilder();
        sb.append(CLAIMING_NOTE_PRELUDE);
        for (int i = startPoint; i < (startPoint + maxSummariesPerNote) && i < claims.size(); i++) {
            ElectronicPaymentClaim claim = claims.get(i);
            sb.append(createSummaryLineForClaim(claim));
        }
        // substring out the final " ; "
        String noteText = sb.substring(0, sb.length() - 3);
        return noteText;
    }

    /**
     * Creates a summary line for a note from a claim
     *
     * @param claim the electronic payment claim to summarize
     * @return a String with the summary of the claim.
     */
    protected String createSummaryLineForClaim(ElectronicPaymentClaim claim) {
        StringBuilder summary = new StringBuilder();
        summary.append(claim.getDocumentNumber());
        summary.append('-');
        summary.append(claim.getFinancialDocumentLineNumber().toString());
        summary.append(' ');
        final Date advanceDepositDate = claim.getGeneratingAdvanceDepositDetail().getFinancialDocumentAdvanceDepositDate();
        if (!ObjectUtils.isNull(advanceDepositDate)) {
            summary.append(dateTimeService.toDateString(advanceDepositDate));
            summary.append(' ');
        }
        summary.append('$');
        summary.append(claim.getGeneratingAccountingLine().getAmount());
        summary.append(' ');
        summary.append(';');
        summary.append(' ');
        return summary.toString();
    }

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingService#createPaymentClaimingDocument(java.util.List,
     * org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy)
     */
    public String createPaymentClaimingDocument(List<ElectronicPaymentClaim> claims, ElectronicPaymentClaimingDocumentGenerationStrategy documentCreationHelper, Person user) {
        return documentCreationHelper.createDocumentFromElectronicPayments(claims, user);
    }

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingService#getClaimingDocumentChoices(org.kuali.rice.kim.api.identity.Person)
     */
    public List<ElectronicPaymentClaimingDocumentGenerationStrategy> getClaimingDocumentChoices(Person user) {
        List<ElectronicPaymentClaimingDocumentGenerationStrategy> documentChoices = new ArrayList<ElectronicPaymentClaimingDocumentGenerationStrategy>();
        Map<String, ElectronicPaymentClaimingDocumentGenerationStrategy> claimingDocHelpers = SpringContext.getBeansOfType(ElectronicPaymentClaimingDocumentGenerationStrategy.class);
        ElectronicPaymentClaimingDocumentGenerationStrategy claimingDocHelper;

        // try the helper for no document case
        claimingDocHelper = claimingDocHelpers.get(CLAIMING_DOC_HELPER_BEAN_NAME);
        if (claimingDocHelper.userMayUseToClaim(user)) {
            documentChoices.add(claimingDocHelper);
        }

        // try the DI
        claimingDocHelper = claimingDocHelpers.get(DI_CLAIMING_DOC_HELPER_BEAN_NAME);
        if (claimingDocHelper.userMayUseToClaim(user)) {
            documentChoices.add(claimingDocHelper);
        }

        // try the YEDI
        claimingDocHelper = claimingDocHelpers.get(YEDI_CLAIMING_DOC_HELPER_BEAN_NAME);
        if (claimingDocHelper.userMayUseToClaim(user)) {
            documentChoices.add(claimingDocHelper);
        }

        // try the AR Cash Control
        claimingDocHelper = SpringContext.getBean(AccountsReceivableModuleService.class).getAccountsReceivablePaymentClaimingStrategy();
        if (claimingDocHelper.userMayUseToClaim(user)) {
            documentChoices.add(claimingDocHelper);
        }

        return documentChoices;
    }

    /**
     * Sets the referenceFinancialDocumentNumber on each of the payments passed in with the given document number and then saves
     * them.
     *
     * @param payments      a list of payments to claim
     * @param docmentNumber the document number of the claiming document
     */
    public void claimElectronicPayments(List<ElectronicPaymentClaim> payments, String documentNumber) {
        for (ElectronicPaymentClaim payment : payments) {
            payment.setReferenceFinancialDocumentNumber(documentNumber);
            payment.setPaymentClaimStatusCode(ElectronicPaymentClaim.ClaimStatusCodes.CLAIMED);
            businessObjectService.save(payment);
        }
    }

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingService#declaimElectronicPaymentClaimsForDocument(org.kuali.rice.krad.document.Document)
     */
    public void declaimElectronicPaymentClaimsForDocument(Document document) {
        Map<String, String> searchKeys = new HashMap<String, String>();
        searchKeys.put("referenceFinancialDocumentNumber", document.getDocumentNumber());
        Collection<ElectronicPaymentClaim> claimsAsObjects = businessObjectService.findMatching(ElectronicPaymentClaim.class, searchKeys);
        for (Object claimAsObject : claimsAsObjects) {
            ElectronicPaymentClaim claim = (ElectronicPaymentClaim) claimAsObject;
            claim.setReferenceFinancialDocumentNumber(null);
            claim.setPaymentClaimStatusCode(ElectronicPaymentClaim.ClaimStatusCodes.UNCLAIMED);
            businessObjectService.save(claim);
        }
    }

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingService#isAuthorizedForClaimingElectronicPayment(org.kuali.rice.kim.api.identity.Person,
     * java.lang.String, java.lang.String)
     */
    public boolean isAuthorizedForClaimingElectronicPayment(Person user, String workflowDocumentTypeName) {
        String principalId = user.getPrincipalId();
        String namespaceCode = KFSConstants.ParameterNamespaces.KFS;
        String permissionTemplateName = KFSConstants.PermissionTemplate.CLAIM_ELECTRONIC_PAYMENT.name;

        Map<String, String> permissionDetails = new HashMap<String, String>();
        permissionDetails.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, workflowDocumentTypeName);

        IdentityManagementService identityManagementService = SpringContext.getBean(IdentityManagementService.class);
        boolean isAuthorized = identityManagementService.hasPermissionByTemplateName(principalId, namespaceCode, permissionTemplateName, permissionDetails);

        return isAuthorized;
    }

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingService#generateElectronicPaymentClaimRecords(org.kuali.kfs.fp.document.AdvanceDepositDocument)
     */
    public List<ElectronicPaymentClaim> generateElectronicPaymentClaimRecords(AdvanceDepositDocument doc) {
        List<ElectronicPaymentClaim> claimRecords = new ArrayList<ElectronicPaymentClaim>();
        for (Object accountingLineAsObj : doc.getSourceAccountingLines()) {
            final AccountingLine accountingLine = (AccountingLine) accountingLineAsObj;
            if (this.representsElectronicFundAccount(accountingLine)) {
                ElectronicPaymentClaim electronicPayment = createElectronicPayment(doc, accountingLine);
                businessObjectService.save(electronicPayment);
                claimRecords.add(electronicPayment);
            }
        }
        return claimRecords;
    }

    /**
     * Determines if the given accounting line represents an electronic payment
     *
     * @param accountingLine the accounting line to check
     * @return true if the accounting line does represent an electronic payment, false otherwise
     */
    public boolean representsElectronicFundAccount(AccountingLine accountingLine) {
        return /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(AdvanceDepositDocument.class, ELECTRONIC_PAYMENT_CLAIM_ACCOUNTS_PARAMETER, accountingLine.getChartOfAccountsCode(), accountingLine.getAccountNumber()).evaluationSucceeds();
    }

    /**
     * Creates an electronic payment claim record to match the given accounting line on the document
     *
     * @param accountingLine an accounting line that an electronic payment claim record should be created for
     * @return the created ElectronicPaymentClaim business object
     */
    protected ElectronicPaymentClaim createElectronicPayment(AdvanceDepositDocument document, AccountingLine accountingLine) {
        ElectronicPaymentClaim electronicPayment = new ElectronicPaymentClaim();
        electronicPayment.setDocumentNumber(document.getDocumentNumber());
        electronicPayment.setFinancialDocumentLineNumber(accountingLine.getSequenceNumber());
        electronicPayment.setFinancialDocumentPostingPeriodCode(document.getPostingPeriodCode());
        electronicPayment.setFinancialDocumentPostingYear(document.getPostingYear());
        electronicPayment.setPaymentClaimStatusCode(ElectronicPaymentClaim.ClaimStatusCodes.UNCLAIMED);
        return electronicPayment;
    }

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

    /**
     * Sets the documentService attribute value.
     *
     * @param documentService The documentService to set.
     */
    public void setDocumentService(DocumentService documentService) {
        this.documentService = documentService;
    }

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

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