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

import org.apache.log4j.Logger;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.fp.document.DistributionOfIncomeAndExpenseDocument;
import org.kuali.kfs.krad.bo.Note;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.ElectronicPaymentClaim;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
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.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.doctype.DocumentTypeService;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;

import java.util.List;

public class DistributionOfIncomeAndExpenseElectronicPaymentClaimingHelperStrategyImpl implements ElectronicPaymentClaimingDocumentGenerationStrategy {
    private static final Logger LOG = Logger.getLogger(DistributionOfIncomeAndExpenseElectronicPaymentClaimingHelperStrategyImpl.class);

    protected DocumentService documentService;
    protected ElectronicPaymentClaimingService electronicPaymentClaimingService;
    protected ParameterService parameterService;

    /**
     * The name of the parameter to get the description for this document; without a description, we can't save the document, and if
     * we don't save the document, there's a chance that electronic payment claims will go to limbo
     */
    protected final static String DOCUMENT_DESCRIPTION_PARAM_NAME = "ELECTRONIC_FUNDS_DOCUMENT_DESCRIPTION";
    protected final static String URL_PREFIX = "financial";
    protected final static String URL_MIDDLE = ".do?methodToCall=docHandler&command=";
    protected final static String URL_SUFFIX = "&docId=";
    public final static String URL_DOC_TYPE = "DistributionOfIncomeAndExpense";

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy#createDocumentFromElectronicPayments(java.util.List)
     */
    public String createDocumentFromElectronicPayments(List<ElectronicPaymentClaim> electronicPayments, Person user) {
        DistributionOfIncomeAndExpenseDocument document = null;
        try {
            document = (DistributionOfIncomeAndExpenseDocument) documentService.getNewDocument(getClaimingDocumentWorkflowDocumentType());
            addAccountingLinesToDocument(document, electronicPayments);
            addDescriptionToDocument(document);
            addNotesToDocument(document, electronicPayments, user);
            documentService.saveDocument(document);
            electronicPaymentClaimingService.claimElectronicPayments(electronicPayments, document.getDocumentNumber());
        } catch (WorkflowException we) {
            throw new RuntimeException("WorkflowException while creating a DistributionOfIncomeAndExpenseDocument to claim ElectronicPaymentClaim records.", we);
        }

        return getURLForDocument(document);
    }

    /**
     * Builds the URL that can be used to redirect to the correct document
     *
     * @param doc the document to build the URL for
     * @return the relative URL to redirect to
     */
    protected String getURLForDocument(DistributionOfIncomeAndExpenseDocument doc) {
        StringBuilder url = new StringBuilder();
        url.append(URL_PREFIX);
        url.append(getUrlDocType());
        url.append(URL_MIDDLE);
        url.append(KewApiConstants.DOCSEARCH_COMMAND);
        url.append(URL_SUFFIX);
        url.append(doc.getDocumentNumber());
        return url.toString();
    }

    /**
     * Creates notes for the claims (using the ElectronicPaymentClaimingService) and then adds them to the document
     *
     * @param claimingDoc the claiming document
     * @param claims      the electronic payments being claimed
     * @param user        the user doing the claiming
     */
    protected void addNotesToDocument(DistributionOfIncomeAndExpenseDocument claimingDoc, List<ElectronicPaymentClaim> claims, Person user) {
        for (String noteText : electronicPaymentClaimingService.constructNoteTextsForClaims(claims)) {
            try {
                Note note = documentService.createNoteFromDocument(claimingDoc, noteText);
                claimingDoc.addNote(note);
            } catch (Exception e) {
                LOG.error("Exception while attempting to create or add note: ", e);
            }
        }
    }

    /**
     * Adds an accounting line to the document for each ElectronicPaymentClaim record that is being added
     *
     * @param document           the claiming Distribution of Income and Expense document
     * @param electronicPayments the list of ElectronicPaymentClaim records that are being claimed
     */
    protected void addAccountingLinesToDocument(DistributionOfIncomeAndExpenseDocument document, List<ElectronicPaymentClaim> electronicPayments) {
        for (ElectronicPaymentClaim payment : electronicPayments) {
            SourceAccountingLine claimingAccountingLine = copyAccountingLineToNew(payment.getGeneratingAccountingLine(), createNewAccountingLineForDocument(document));
            document.addSourceAccountingLine(claimingAccountingLine);
        }
    }

    /**
     * Adds the parameterized description to the document, so the doc can be saved
     *
     * @param document the document to add a description to
     */
    protected void addDescriptionToDocument(DistributionOfIncomeAndExpenseDocument document) {
        String description = parameterService.getParameterValueAsString(DistributionOfIncomeAndExpenseDocument.class, DistributionOfIncomeAndExpenseElectronicPaymentClaimingHelperStrategyImpl.DOCUMENT_DESCRIPTION_PARAM_NAME);
        if (description != null) {
            document.getDocumentHeader().setDocumentDescription(description);
        } else {
            throw new RuntimeException("There is evidently no value for Parameter KFS-FP / Distribution of Income and Expense / " + DistributionOfIncomeAndExpenseElectronicPaymentClaimingHelperStrategyImpl.DOCUMENT_DESCRIPTION_PARAM_NAME + "; please set a value before claiming Electronic Payments");
        }
    }

    /**
     * Creates a new accounting line, based on what the source accounting line class for the document is
     *
     * @param document the document that is claiming these payments
     * @return a new, ready-to-be-filled in accounting line of the class that the given document uses for Source Accounting Lines
     */
    protected SourceAccountingLine createNewAccountingLineForDocument(DistributionOfIncomeAndExpenseDocument document) {
        try {
            Class<? extends SourceAccountingLine> accountingLineClass = document.getSourceAccountingLineClass();
            return accountingLineClass.newInstance();
        } catch (Exception ex) {
            throw new RuntimeException("Unable to create source accounting line for document: " + document, ex);
        }
    }

    /**
     * Copies an original accounting line to a new accounting line
     *
     * @param line the original accounting line
     * @return an accounting line that copies that accounting line
     */
    protected SourceAccountingLine copyAccountingLineToNew(SourceAccountingLine line, SourceAccountingLine newLine) {
        newLine.setChartOfAccountsCode(line.getChartOfAccountsCode());
        newLine.setAccountNumber(line.getAccountNumber());
        newLine.setSubAccountNumber(line.getSubAccountNumber());
        newLine.setFinancialObjectCode(line.getFinancialObjectCode());
        newLine.setFinancialSubObjectCode(line.getFinancialSubObjectCode());
        newLine.setProjectCode(line.getProjectCode());
        newLine.setOrganizationReferenceId(line.getOrganizationReferenceId());
        newLine.setAmount(line.getAmount());
        newLine.setFinancialDocumentLineDescription(line.getFinancialDocumentLineDescription());
        return newLine;
    }

    /**
     * @return the name DistributionOfIncomeAndExpenseDocument workflow document type
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy#getClaimingDocumentWorkflowDocumentType()
     */
    public String getClaimingDocumentWorkflowDocumentType() {
        return KFSConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE;
    }

    /**
     * @return the class of the document which claims these electronic payments
     */
    protected String getUrlDocType() {
        return DistributionOfIncomeAndExpenseElectronicPaymentClaimingHelperStrategyImpl.URL_DOC_TYPE;
    }

    /**
     * Uses the data dictionary to find the label for this document
     *
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy#getDocumentLabel()
     */
    public String getDocumentLabel() {
        try {
            return SpringContext.getBean(DocumentTypeService.class).getDocumentTypeByName(getClaimingDocumentWorkflowDocumentType()).getLabel();
        } catch (Exception e) {
            LOG.error("Caught Exception trying to get Workflow Document Type" + e);
        }
        return "DI";
    }

    /**
     * This always returns true if the given user in the claiming workgroup.
     *
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy#userMayUseToClaim(org.kuali.rice.kim.api.identity.Person)
     */
    public boolean userMayUseToClaim(Person claimingUser) {
        final String documentTypeName = getClaimingDocumentWorkflowDocumentType();

        final boolean canClaim = electronicPaymentClaimingService.isAuthorizedForClaimingElectronicPayment(claimingUser, documentTypeName)
            || electronicPaymentClaimingService.isAuthorizedForClaimingElectronicPayment(claimingUser, null);

        return canClaim;
    }

    /**
     * @see org.kuali.kfs.sys.service.ElectronicPaymentClaimingDocumentGenerationStrategy#isDocumentReferenceValid(java.lang.String)
     */
    public boolean isDocumentReferenceValid(String referenceDocumentNumber) {
        boolean valid = false;
        try {
            long docNumberAsLong = Long.parseLong(referenceDocumentNumber);
            if (docNumberAsLong > 0L) {
                valid = documentService.documentExists(referenceDocumentNumber);
            }
        } catch (NumberFormatException nfe) {
            // the doc # can't be parsed into a Long? Then it ain't no valid!
            LOG.warn("Unable to parse referenceDocumentNumber (" + referenceDocumentNumber + ") into a number: " + nfe.getMessage());
        }
        return valid;
    }

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

    /**
     * Sets the electronicPaymentClaimingService attribute value.
     *
     * @param electronicPaymentClaimingService The electronicPaymentClaimingService to set.
     */
    public void setElectronicPaymentClaimingService(ElectronicPaymentClaimingService electronicPaymentClaimingService) {
        this.electronicPaymentClaimingService = electronicPaymentClaimingService;
    }

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


}
