/*
 * 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.util.WebUtils;
import org.kuali.kfs.kns.web.struts.action.KualiTransactionalDocumentActionBase;
import org.kuali.kfs.krad.bo.Note;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.service.NoteService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.krad.util.UrlFactory;
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.ContractsGrantsInvoiceDocumentErrorLog;
import org.kuali.kfs.module.ar.businessobject.ContractsGrantsLetterOfCreditReviewDetail;
import org.kuali.kfs.module.ar.document.ContractsGrantsLetterOfCreditReviewDocument;
import org.kuali.kfs.module.ar.report.service.ContractsGrantsInvoiceReportService;
import org.kuali.kfs.module.ar.service.AccountsReceivablePdfHelperService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.util.KfsWebUtils;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.IdentityService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

/**
 * Action class for Contracts & Grants LetterOfCredit Review Document.
 */
public class ContractsGrantsLetterOfCreditReviewAction extends KualiTransactionalDocumentActionBase {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ContractsGrantsLetterOfCreditReviewAction.class);

    /**
     * Clears out init tab.
     *
     * @param mapping  An ActionMapping
     * @param form     An ActionForm
     * @param request  The HttpServletRequest
     * @param response The HttpServletResponse
     * @return An ActionForward
     * @throws Exception
     */
    public ActionForward clearInitTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ContractsGrantsLetterOfCreditReviewForm contractsGrantsLetterOfCreditReviewForm = (ContractsGrantsLetterOfCreditReviewForm) form;
        ContractsGrantsLetterOfCreditReviewDocument contractsGrantsLetterOfCreditReviewDocument = (ContractsGrantsLetterOfCreditReviewDocument) contractsGrantsLetterOfCreditReviewForm.getDocument();
        contractsGrantsLetterOfCreditReviewDocument.clearInitFields();

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

    /**
     * Handles continue request. This request comes from the initial screen which gives Letter of Credit Fund Group and other
     * details Based on that, the Contracts & Grants LetterOfCredit Review document is initially populated.
     *
     * @param mapping  An ActionMapping
     * @param form     An ActionForm
     * @param request  The HttpServletRequest
     * @param response The HttpServletResponse
     * @return An ActionForward
     * @throws Exception
     */
    public ActionForward continueLOCReview(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ContractsGrantsLetterOfCreditReviewForm contractsGrantsLetterOfCreditReviewForm = (ContractsGrantsLetterOfCreditReviewForm) form;
        ContractsGrantsLetterOfCreditReviewDocument contractsGrantsLetterOfCreditReviewDocument = (ContractsGrantsLetterOfCreditReviewDocument) contractsGrantsLetterOfCreditReviewForm.getDocument();

        if (StringUtils.isBlank(contractsGrantsLetterOfCreditReviewDocument.getLetterOfCreditFundCode()) && StringUtils.isBlank(contractsGrantsLetterOfCreditReviewDocument.getLetterOfCreditFundGroupCode())) {
            GlobalVariables.getMessageMap().putError(ArConstants.LETTER_OF_CREDIT_REVIEW_INIT_SECTION, ArKeyConstants.ERROR_LOC_REVIEW_FUND_OR_FUND_GROUP_REQUIRED);
        } else if (!StringUtils.isBlank(contractsGrantsLetterOfCreditReviewDocument.getLetterOfCreditFundCode()) && !StringUtils.isBlank(contractsGrantsLetterOfCreditReviewDocument.getLetterOfCreditFundGroupCode())) {
            GlobalVariables.getMessageMap().putError(ArConstants.LETTER_OF_CREDIT_REVIEW_INIT_SECTION, ArKeyConstants.ERROR_LOC_REVIEW_ONLY_ONE_FUND_OR_FUND_GROUP);
        } else {
            ContractsGrantsLetterOfCreditReviewDocument document = (ContractsGrantsLetterOfCreditReviewDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(contractsGrantsLetterOfCreditReviewDocument.getDocumentNumber());
            if (ObjectUtils.isNull(document)) {
                contractsGrantsLetterOfCreditReviewDocument.getDocumentHeader().setDocumentDescription(ArConstants.LETTER_OF_CREDIT_REVIEW_DOCUMENT);
                Collection<ContractsGrantsInvoiceDocumentErrorLog> contractsGrantsInvoiceDocumentErrorLogs = new ArrayList<ContractsGrantsInvoiceDocumentErrorLog>();
                if (contractsGrantsLetterOfCreditReviewDocument.populateContractsGrantsLOCReviewDetails(contractsGrantsInvoiceDocumentErrorLogs)) {
                    saveDocumentAndNote(contractsGrantsLetterOfCreditReviewDocument, contractsGrantsInvoiceDocumentErrorLogs);
                }
            }
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * To recalculate the amount to Draw for every contract control account
     *
     * @param mapping  An ActionMapping
     * @param form     An ActionForm
     * @param request  The HttpServletRequest
     * @param response The HttpServletResponse
     * @return An ActionForward
     * @throws Exception
     */
    public ActionForward recalculateAmountToDraw(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ContractsGrantsLetterOfCreditReviewForm contractsGrantsLetterOfCreditReviewForm = (ContractsGrantsLetterOfCreditReviewForm) form;
        ContractsGrantsLetterOfCreditReviewDocument contractsGrantsLetterOfCreditReviewDocument = (ContractsGrantsLetterOfCreditReviewDocument) contractsGrantsLetterOfCreditReviewForm.getDocument();

        int indexOfLineToRecalculate = getSelectedLine(request);
        ContractsGrantsLetterOfCreditReviewDetail contractsGrantsLetterOfCreditReviewDetail = contractsGrantsLetterOfCreditReviewDocument.getHeaderReviewDetails().get(indexOfLineToRecalculate);
        contractsGrantsLetterOfCreditReviewDetail.setAmountToDraw(KualiDecimal.ZERO); // clear the cca amount to draw.
        contractsGrantsLetterOfCreditReviewDetail.setClaimOnCashBalance(KualiDecimal.ZERO); // clear the cca claim on cash balance
        for (ContractsGrantsLetterOfCreditReviewDetail detail : contractsGrantsLetterOfCreditReviewDocument.getAccountReviewDetails()) {
            // To set amount to Draw to 0 if there are blank values, to avoid exceptions.
            if (ObjectUtils.isNull(detail.getAmountToDraw())) {
                detail.setAmountToDraw(KualiDecimal.ZERO);
            }

            if (detail.getProposalNumber().equals(contractsGrantsLetterOfCreditReviewDetail.getProposalNumber())) { // To get the appropriate individual award account rows.
                contractsGrantsLetterOfCreditReviewDetail.setAmountToDraw(contractsGrantsLetterOfCreditReviewDetail.getAmountToDraw().add(detail.getAmountToDraw()));
                contractsGrantsLetterOfCreditReviewDetail.setClaimOnCashBalance(contractsGrantsLetterOfCreditReviewDetail.getClaimOnCashBalance().add(detail.getClaimOnCashBalance()));

            }

            // Now to set funds Not Drawn as a difference betwen amountToDraw and hiddenAmountToDraw.
            detail.setFundsNotDrawn(detail.getHiddenAmountToDraw().subtract(detail.getAmountToDraw()));
            if (detail.getFundsNotDrawn().isNegative()) {
                GlobalVariables.getMessageMap().putError(ArPropertyConstants.FUNDS_NOT_DRAWN, ArKeyConstants.ContractsGrantsInvoiceConstants.ERROR_DOCUMENT_AMOUNT_TO_DRAW_INVALID);
                detail.setFundsNotDrawn(KualiDecimal.ZERO);
                detail.setAmountToDraw(detail.getHiddenAmountToDraw().subtract(detail.getFundsNotDrawn()));
            }
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * This method is called when export button is clicked and export a csv file.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward export(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ContractsGrantsLetterOfCreditReviewForm locForm = (ContractsGrantsLetterOfCreditReviewForm) form;
        ContractsGrantsLetterOfCreditReviewDocument document = (ContractsGrantsLetterOfCreditReviewDocument) locForm.getDocument();

        ContractsGrantsInvoiceReportService reportService = SpringContext.getBean(ContractsGrantsInvoiceReportService.class);
        byte[] report = reportService.convertLetterOfCreditReviewToCSV(document);
        if (report.length == 0) {
            throw new RuntimeException("Error: non-existent file or directory provided");
        }

        final String fundforFile = !StringUtils.isBlank(document.getLetterOfCreditFundGroupCode())
            ? document.getLetterOfCreditFundGroupCode()
            : document.getLetterOfCreditFundCode();
        WebUtils.saveMimeInputStreamAsFile(response, KFSConstants.ReportGeneration.CSV_MIME_TYPE, new ByteArrayInputStream(report), "CSV-Export-" + document.getDocumentNumber() + "-" + fundforFile + KFSConstants.ReportGeneration.CSV_FILE_EXTENSION, report.length);
        return null;
    }

    /**
     * This method forward the action to printInvoicePDF after checking the JS compatibility with the browser.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward print(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String basePath = getApplicationBaseUrl();
        ContractsGrantsLetterOfCreditReviewForm locForm = (ContractsGrantsLetterOfCreditReviewForm) form;
        ContractsGrantsLetterOfCreditReviewDocument document = (ContractsGrantsLetterOfCreditReviewDocument) locForm.getDocument();

        String printInvoicePDFUrl = getUrlForPrintInvoice(basePath, document.getDocumentNumber(), ArConstants.PRINT_INVOICE_PDF_METHOD);
        String displayInvoiceTabbedPageUrl = getUrlForPrintInvoice(basePath, document.getDocumentNumber(), KFSConstants.DOC_HANDLER_METHOD);

        request.setAttribute(ArPropertyConstants.PRINT_PDF_URL, printInvoicePDFUrl);
        request.setAttribute(ArPropertyConstants.DISPLAY_TABBED_PAGE_URL, displayInvoiceTabbedPageUrl);
        return mapping.findForward(ArConstants.MAPPING_PRINT_PDF);
    }

    /**
     * This method generates the invoice and provides it to the user to Print it.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward printInvoicePDF(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String invoiceDocId = request.getParameter(KFSConstants.PARAMETER_DOC_ID);
        ContractsGrantsLetterOfCreditReviewDocument contractsGrantsLOCReviewDocument = (ContractsGrantsLetterOfCreditReviewDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(invoiceDocId);

        if (ObjectUtils.isNotNull(contractsGrantsLOCReviewDocument)) {
            ContractsGrantsInvoiceReportService reportService = SpringContext.getBean(ContractsGrantsInvoiceReportService.class);
            byte[] report = reportService.generateLOCReviewAsPdf(contractsGrantsLOCReviewDocument);

            if (report.length == 0) {
                LOG.warn("No Report Generated");
                return mapping.findForward(KFSConstants.MAPPING_BASIC);
            }

            ByteArrayOutputStream baos = SpringContext.getBean(AccountsReceivablePdfHelperService.class).buildPdfOutputStream(report);

            StringBuilder fileName = new StringBuilder();
            fileName.append(contractsGrantsLOCReviewDocument.getLetterOfCreditFundCode());
            fileName.append(KFSConstants.DASH);
            fileName.append(contractsGrantsLOCReviewDocument.getDocumentNumber());
            fileName.append(KFSConstants.ReportGeneration.PDF_FILE_EXTENSION);

            KfsWebUtils.saveMimeOutputStreamAsFile(response, KFSConstants.ReportGeneration.PDF_MIME_TYPE, baos, fileName.toString(), Boolean.parseBoolean(request.getParameter(KFSConstants.ReportGeneration.USE_JAVASCRIPT)));
        }

        return null;
    }

    /**
     * Saves the ContractsGrantsLetterOfCreditReviewDocument and if necessary (i.e. there were validation errors for some
     * of the awards) saves a note indicating some awards didn't pass validation and aren't included on the document.
     *
     * @param contractsGrantsLetterOfCreditReviewDocument document to save
     * @param contractsGrantsInvoiceDocumentErrorLogs     Collection of validation errors, if any
     * @throws WorkflowException
     */
    protected void saveDocumentAndNote(ContractsGrantsLetterOfCreditReviewDocument contractsGrantsLetterOfCreditReviewDocument, Collection<ContractsGrantsInvoiceDocumentErrorLog> contractsGrantsInvoiceDocumentErrorLogs) throws WorkflowException {
        SpringContext.getBean(DocumentService.class).saveDocument(contractsGrantsLetterOfCreditReviewDocument);
        if (contractsGrantsInvoiceDocumentErrorLogs.size() > 0) {
            String noteText = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(ArKeyConstants.ContractsGrantsInvoiceConstants.ERROR_SOME_AWARDS_INVALID);
            Note note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(contractsGrantsLetterOfCreditReviewDocument, noteText);
            note.setAuthorUniversalIdentifier(SpringContext.getBean(IdentityService.class).getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER).getPrincipalId());
            contractsGrantsLetterOfCreditReviewDocument.addNote(note);
            SpringContext.getBean(NoteService.class).save(note);
        }
    }

    /**
     * Creates a URL to be used in printing the customer invoice document.
     *
     * @param basePath     String: The base path of the current URL
     * @param docId        String: The document ID of the document to be printed
     * @param methodToCall String: The name of the method that will be invoked to do this particular print
     * @return The URL
     */
    protected String getUrlForPrintInvoice(String basePath, String docId, String methodToCall) {
        String baseUrl = basePath + "/" + ArConstants.UrlActions.CONTRACTS_GRANTS_LETTER_OF_CREDIT_REVIEW_DOCUMENT;
        Properties parameters = new Properties();
        parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, methodToCall);
        parameters.put(KFSConstants.PARAMETER_DOC_ID, docId);
        parameters.put(KFSConstants.PARAMETER_COMMAND, KewApiConstants.DOCSEARCH_COMMAND);

        return UrlFactory.parameterizeUrl(baseUrl, parameters);
    }
}
