/*
 * 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.ar.document.web.struts;

import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.kuali.kfs.kns.web.struts.form.KualiDocumentFormBase;
import org.kuali.kfs.krad.exception.UnknownDocumentIdException;
import org.kuali.kfs.krad.rules.rule.event.SaveDocumentEvent;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.service.KualiRuleService;
import org.kuali.kfs.krad.service.SessionDocumentService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.ar.ArConstants;
import org.kuali.kfs.module.ar.ArKeyConstants;
import org.kuali.kfs.module.ar.ArPropertyConstants;
import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader;
import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
import org.kuali.kfs.module.ar.document.CashControlDocument;
import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService;
import org.kuali.kfs.module.ar.document.service.CashControlDocumentService;
import org.kuali.kfs.module.ar.document.validation.event.AddCashControlDetailEvent;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kew.api.exception.WorkflowException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

public class CashControlAction extends FinancialSystemTransactionalDocumentActionBase {

    /**
     * @see org.kuali.kfs.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.kfs.kns.web.struts.form.KualiDocumentFormBase)
     */
    @Override
    protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {

        super.loadDocument(kualiDocumentFormBase);
        CashControlForm ccForm = (CashControlForm) kualiDocumentFormBase;
        CashControlDocument cashControlDocument = ccForm.getCashControlDocument();

        // now that the form has been originally loaded, we need to set a few Form variables that are used by
        // JSP JSTL expressions because they are used directly and immediately upon initial form display
        if (cashControlDocument != null && cashControlDocument.getCustomerPaymentMediumCode() != null) {
            ccForm.setCashPaymentMediumSelected(ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()));
        }

        if (cashControlDocument != null) {
            // get the PaymentApplicationDocuments by reference number
            for (CashControlDetail cashControlDetail : cashControlDocument.getCashControlDetails()) {
                String docId = cashControlDetail.getReferenceFinancialDocumentNumber();
                PaymentApplicationDocument doc = (PaymentApplicationDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(docId);
                if (doc == null) {
                    throw new UnknownDocumentIdException("Document " + docId + " no longer exists.  It may have been cancelled before being saved.");
                }

                cashControlDetail.setReferenceFinancialDocument(doc);
                WorkflowDocument workflowDoc = doc.getDocumentHeader().getWorkflowDocument();
                // KualiDocumentFormBase.populate() needs this updated in the session
                SpringContext.getBean(SessionDocumentService.class).addDocumentToUserSession(GlobalVariables.getUserSession(), workflowDoc);
            }
        }

    }

    /**
     * Adds handling for cash control detail amount updates.
     *
     * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
     * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        CashControlForm ccForm = (CashControlForm) form;
        CashControlDocument ccDoc = ccForm.getCashControlDocument();

        if (ccDoc != null) {
            ccForm.setCashPaymentMediumSelected(ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(ccDoc.getCustomerPaymentMediumCode()));
        }

        if (ccForm.hasDocumentId()) {
            ccDoc = ccForm.getCashControlDocument();
            ccDoc.refreshReferenceObject("customerPaymentMedium");
            // recalc b/c changes to the amounts could have happened
            ccDoc.recalculateTotals();
        }

        return super.execute(mapping, form, request, response);
    }

    /**
     * @see org.kuali.kfs.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.kfs.kns.web.struts.form.KualiDocumentFormBase)
     */
    @Override
    protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
        super.createDocument(kualiDocumentFormBase);
        CashControlForm form = (CashControlForm) kualiDocumentFormBase;
        CashControlDocument document = form.getCashControlDocument();

        //get the default bank code for the given document type, which is CTRL for this document.

        document.setBankCode("");
        Bank defaultBank = SpringContext.getBean(BankService.class).getDefaultBankByDocType(form.getDocTypeName());
        if (defaultBank != null) {
            document.setBankCode(defaultBank.getBankCode());
        }

        // set up the default values for the AR DOC Header (SHOULD PROBABLY MAKE THIS A SERVICE)
        AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService = SpringContext.getBean(AccountsReceivableDocumentHeaderService.class);
        AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = accountsReceivableDocumentHeaderService.getNewAccountsReceivableDocumentHeaderForCurrentUser();
        accountsReceivableDocumentHeader.setDocumentNumber(document.getDocumentNumber());
        document.setAccountsReceivableDocumentHeader(accountsReceivableDocumentHeader);

    }

    /**
     * @see org.kuali.kfs.kns.web.struts.action.KualiDocumentActionBase#cancel(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        CashControlForm cashControlDocForm = (CashControlForm) form;
        CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();

        // If the cancel works, proceed to canceling the cash control doc
        if (cancelLinkedPaymentApplicationDocuments(cashControlDocument)) {
            return super.cancel(mapping, form, request, response);
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * @see org.kuali.kfs.kns.web.struts.action.KualiDocumentActionBase#disapprove(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward disapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        boolean success = true;
        CashControlForm cashControlDocForm = (CashControlForm) form;
        CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();

        success = cancelLinkedPaymentApplicationDocuments(cashControlDocument);

        if (!success) {
            return mapping.findForward(KFSConstants.MAPPING_BASIC);
        }

        return super.disapprove(mapping, form, request, response);
    }

    /**
     * This method cancels all linked Payment Application documents that are not already in approved status.
     *
     * @param cashControlDocument
     * @throws WorkflowException
     */
    protected boolean cancelLinkedPaymentApplicationDocuments(CashControlDocument cashControlDocument) throws WorkflowException {
        boolean success = true;
        List<CashControlDetail> details = cashControlDocument.getCashControlDetails();

        for (CashControlDetail cashControlDetail : details) {
            DocumentService documentService = SpringContext.getBean(DocumentService.class);

            PaymentApplicationDocument applicationDocument = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(cashControlDetail.getReferenceFinancialDocumentNumber());
            if (KFSConstants.DocumentStatusCodes.CANCELLED.equals(applicationDocument.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
                // Ignore this case, as it should not impact the ability to cancel a cash control doc.
            } else if (!KFSConstants.DocumentStatusCodes.APPROVED.equals(applicationDocument.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
                documentService.cancelDocument(applicationDocument, ArKeyConstants.DOCUMENT_DELETED_FROM_CASH_CTRL_DOC);
            } else {
                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDetailFields.CASH_CONTROL_DETAILS_TAB, ArKeyConstants.ERROR_CANT_CANCEL_CASH_CONTROL_DOC_WITH_ASSOCIATED_APPROVED_PAYMENT_APPLICATION);
                success = false;
            }
        }
        return success;
    }

    /**
     * This method adds a new cash control detail
     *
     * @param mapping  action mapping
     * @param form     action form
     * @param request
     * @param response
     * @return forward action
     * @throws Exception
     */
    public ActionForward addCashControlDetail(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        CashControlForm cashControlDocForm = (CashControlForm) form;
        CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
        ConfigurationService kualiConfiguration = SpringContext.getBean(ConfigurationService.class);

        CashControlDetail newCashControlDetail = cashControlDocForm.getNewCashControlDetail();
        newCashControlDetail.setDocumentNumber(cashControlDocument.getDocumentNumber());

        String customerNumber = newCashControlDetail.getCustomerNumber();
        if (StringUtils.isNotEmpty(customerNumber)) {
            // force customer numbers to upper case, since its a primary key
            customerNumber = customerNumber.toUpperCase();
        }
        newCashControlDetail.setCustomerNumber(customerNumber);

        // save the document, which will run business rules and make sure the doc is ready for lines
        KualiRuleService ruleService = SpringContext.getBean(KualiRuleService.class);
        boolean rulePassed = true;

        // apply save rules for the doc
        rulePassed &= ruleService.applyRules(new SaveDocumentEvent(KFSConstants.DOCUMENT_HEADER_ERRORS, cashControlDocument));

        // apply rules for the new cash control detail
        rulePassed &= ruleService.applyRules(new AddCashControlDetailEvent(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX, cashControlDocument, newCashControlDetail));

        // add the new detail if rules passed
        if (rulePassed) {

            CashControlDocumentService cashControlDocumentService = SpringContext.getBean(CashControlDocumentService.class);

            // add cash control detail. implicitly saves the cash control document
            cashControlDocumentService.addNewCashControlDetail(kualiConfiguration.getPropertyValueAsString(ArKeyConstants.CREATED_BY_CASH_CTRL_DOC), cashControlDocument, newCashControlDetail);

            // set a new blank cash control detail
            cashControlDocForm.setNewCashControlDetail(new CashControlDetail());

        }

        // recalc totals, including the docHeader total
        cashControlDocument.recalculateTotals();

        return mapping.findForward(KFSConstants.MAPPING_BASIC);

    }

    /**
     * This method deletes a cash control detail
     *
     * @param mapping  action mapping
     * @param form     action form
     * @param request
     * @param response
     * @return action forward
     * @throws Exception
     */
    public ActionForward deleteCashControlDetail(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        CashControlForm cashControlDocForm = (CashControlForm) form;
        CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();

        int indexOfLineToDelete = getLineToDelete(request);
        CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(indexOfLineToDelete);
        DocumentService documentService = SpringContext.getBean(DocumentService.class);

        PaymentApplicationDocument payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(cashControlDetail.getReferenceFinancialDocumentNumber());

        // this if statement is to catch the situation where a person deletes the line, but doesnt save
        // and then reloads. This will bring the deleted line back on the screen. If they then click
        // the delete line, and this test isnt here, it will try to cancel the already cancelled
        // document, which will throw a workflow error and barf.
        if (!payAppDoc.getDocumentHeader().getWorkflowDocument().isCanceled()) {
            documentService.cancelDocument(payAppDoc, ArKeyConstants.DOCUMENT_DELETED_FROM_CASH_CTRL_DOC);
        }
        cashControlDocument.deleteCashControlDetail(indexOfLineToDelete);

        // recalc totals, including the docHeader total
        cashControlDocument.recalculateTotals();

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * This method generates the GLPEs.
     *
     * @param mapping  action mapping
     * @param form     action form
     * @param request
     * @param response
     * @return action forward
     * @throws Exception
     */
    public ActionForward generateGLPEs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        CashControlForm cashControlDocForm = (CashControlForm) form;
        CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
        String paymentMediumCode = cashControlDocument.getCustomerPaymentMediumCode();

        // refresh reference objects
        cashControlDocument.refreshReferenceObject("customerPaymentMedium");
        cashControlDocument.refreshReferenceObject("generalLedgerPendingEntries");

        // payment medium might have been changed meanwhile so we save first the document
        BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
        businessObjectService.save(cashControlDocument);

        // generate the GLPEs
        GeneralLedgerPendingEntryService glpeService = SpringContext.getBean(GeneralLedgerPendingEntryService.class);
        boolean success = glpeService.generateGeneralLedgerPendingEntries(cashControlDocument);

        if (!success) {
            GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED);
        }

        // approve the GLPEs
        cashControlDocument.changeGeneralLedgerPendingEntriesApprovedStatusCode();

        // save the GLPEs in the database
        CashControlDocumentService cashControlDocumentService = SpringContext.getBean(CashControlDocumentService.class);
        cashControlDocumentService.saveGLPEs(cashControlDocument);

        // approve the document when the GLPEs are generated
        // DocumentService docService = SpringContext.getBean(DocumentService.class);
        // docService.approveDocument(cashControlDocument, "Automatically approved document with GLPE generation.", null);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);

    }

    /**
     * Recalculates the cash control total since user could have changed it during their update.
     *
     * @param cashControlDocument
     */
    protected KualiDecimal calculateCashControlTotal(CashControlDocument cashControlDocument) {
        KualiDecimal total = KualiDecimal.ZERO;
        for (CashControlDetail cashControlDetail : cashControlDocument.getCashControlDetails()) {

            total = total.add(cashControlDetail.getFinancialDocumentLineAmount());
        }
        return total;
    }

}
