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

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.krad.exception.InvalidAddressException;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.MailService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.ar.ArConstants;
import org.kuali.kfs.module.ar.batch.LockboxLoadStep;
import org.kuali.kfs.module.ar.batch.service.LockboxLoadService;
import org.kuali.kfs.module.ar.businessobject.Lockbox;
import org.kuali.kfs.module.ar.businessobject.LockboxDetail;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.batch.BatchInputFileType;
import org.kuali.kfs.sys.batch.FlatFileInformation;
import org.kuali.kfs.sys.batch.FlatFileTransactionInformation;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.springframework.transaction.annotation.Transactional;

import javax.mail.MessagingException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @see org.kuali.kfs.module.ar.batch.service.LockboxLoadService
 */
@Transactional
public class LockboxLoadServiceImpl implements LockboxLoadService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LockboxLoadServiceImpl.class);

    private BatchInputFileType batchInputFileType;
    private String reportsDirectory;
    private BatchInputFileService batchInputFileService;
    private BusinessObjectService businessObjectService;
    private DateTimeService dateTimeService;
    private MailService mailService;
    private ParameterService parameterService;


    @Override
    public boolean loadFile() {

        boolean result = true;
        //List<LockboxLoadFileResult> fileResults = new ArrayList<LockboxLoadFileResult>();
        List<FlatFileInformation> flatFileInformationList = new ArrayList<FlatFileInformation>();
        //LockboxLoadFileResult fileResult = null;
        FlatFileInformation flatFileInformation = null;

        List<String> fileNamesToLoad = getListOfFilesToProcess();
        LOG.info("Found " + fileNamesToLoad.size() + " file(s) to process.");

        List<String> processedFiles = new ArrayList<String>();

        for (String inputFileName : fileNamesToLoad) {

            LOG.info("Beginning processing of filename: " + inputFileName + ".");
            flatFileInformation = new FlatFileInformation(inputFileName);
            flatFileInformationList.add(flatFileInformation);

            if (loadFile(inputFileName, flatFileInformation)) {
                processedFiles.add(inputFileName);
                flatFileInformation.addFileInfoMessage("File successfully completed processing.");
            } else {
                flatFileInformation.addFileErrorMessage("File failed to process successfully.");

            }

        }

        //  remove done files
        removeDoneFiles(processedFiles);

        // SendEmail
        sendLoadSummaryEmail(flatFileInformationList);

        return result;
    }

    public boolean loadFile(String fileName, FlatFileInformation flatFileInformation) {
        boolean valid = true;
        //  load up the file into a byte array
        byte[] fileByteContent = safelyLoadFileBytes(fileName);

        //  parse the file against the configuration define in the spring file and load it into an object
        LOG.info("Attempting to parse the file ");
        Object parsedObject = null;

        try {
            parsedObject = batchInputFileService.parse(batchInputFileType, fileByteContent);
        } catch (ParseException e) {
            LOG.error("Error parsing batch file: " + e.getMessage());
            flatFileInformation.addFileErrorMessage("Error parsing batch file: " + e.getMessage());
            valid = false;
            //    throw new ParseException(e.getMessage());
        }

        // validate the parsed data
        if (parsedObject != null) {
            valid = validate(parsedObject);
            copyAllMessage(parsedObject, flatFileInformation);
            if (valid) {
                loadLockbox(parsedObject);
            }
        }
        return valid;

    }


    @Override
    public boolean validate(Object parsedFileContents) {
        // compare header with detail record
        boolean valid = true;
        List<Lockbox> lockboxList = (List<Lockbox>) parsedFileContents;
        for (Lockbox lockbox : lockboxList) {
            if (!compareDetailsWithHeader(lockbox)) {
                valid = false;
                break;
            }
        }

        return valid;
    }


    /**
     * No processing
     */
    @Override
    public void process(String fileName, Object parsedFileContents) {
    }

    protected boolean compareDetailsWithHeader(Lockbox lockbox) {
        boolean isHeaderMatchedDetails = true;
        KualiDecimal headerTransBatchTotal = lockbox.getHeaderTransactionBatchTotal();
        Integer headerTransBatchCount = lockbox.getHeaderTransactionBatchCount();
        KualiDecimal detailInvPaidTotal = new KualiDecimal(0);
        Integer totalDetailRecords = 0;
        for (LockboxDetail detail : lockbox.getLockboxDetails()) {
            detailInvPaidTotal = detailInvPaidTotal.add(detail.getInvoicePaidOrAppliedAmount());
            totalDetailRecords++;
        }


        if (headerTransBatchTotal.compareTo(detailInvPaidTotal) == 0
            && headerTransBatchCount.compareTo(totalDetailRecords) == 0) {

            String message = "Good Transfer for lockbox number " + lockbox.getLockboxNumber() + "."
                + " Transaction count : " + lockbox.getHeaderTransactionBatchCount()
                + " Transaction total amount : $ " + lockbox.getHeaderTransactionBatchTotal();
            lockbox.getFlatFileTransactionInformation().addInfoMessage(message);
            GlobalVariables.getMessageMap().putInfo(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM, message);

        }


        if (headerTransBatchTotal.compareTo(detailInvPaidTotal) != 0) {
            String message = "Bad Transmmission for lock box number " + lockbox.getLockboxNumber() + "."
                + " Detail does not match header control values "
                + " Header total : $ " + lockbox.getHeaderTransactionBatchTotal()
                + " Detail total : $ " + detailInvPaidTotal;
            lockbox.getFlatFileTransactionInformation().addErrorMessage(message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM, message);
            isHeaderMatchedDetails = false;
        }

        if (headerTransBatchCount.compareTo(totalDetailRecords) != 0) {
            String message = "Bad Transmmission for lock box number " + lockbox.getLockboxNumber() + "."
                + " Detail does not match header control values "
                + " Header Count : " + lockbox.getHeaderTransactionBatchCount()
                + " Detail total : " + totalDetailRecords;
            lockbox.getFlatFileTransactionInformation().addErrorMessage(message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM, message);
            isHeaderMatchedDetails = false;
        }


        return isHeaderMatchedDetails;

    }


    /**
     * Send load lockbox file validation
     *
     * @param report
     */
    public void sendLoadSummaryEmail(List<FlatFileInformation> flatFileInformationList) {
        for (FlatFileInformation information : flatFileInformationList) {
            sendEmail(information);
        }
    }


    public void sendEmail(FlatFileInformation flatFileInformation) {
        LOG.debug("sendEmail() starting");


        MailMessage message = new MailMessage();

        // String returnAddress = parameterService.getParameterValueAsString(KFSConstants.OptionalModuleNamespaces.ACCOUNTS_RECEIVABLE, ParameterConstants.BATCH_COMPONENT, "IU_FROM_EMAIL_ADDRESS");
        // if(StringUtils.isEmpty(returnAddress)) {
        String returnAddress = mailService.getBatchMailingList();
        // }
        message.setFromAddress(returnAddress);
        String subject = parameterService.getParameterValueAsString(LockboxLoadStep.class, ArConstants.Lockbox.SUMMARY_AND_ERROR_NOTIFICATION_EMAIL_SUBJECT);
        //KFSMI-11479: discovered that the backslashes don't work for Linux machines. Need to use File.separator instead for fileName to work.
        String fileName = StringUtils.substringAfterLast(flatFileInformation.getFileName(), "\\");
        message.setSubject(subject + "[ " + fileName + " ]");
        List<String> toAddressList = new ArrayList<String>(parameterService.getParameterValuesAsString(LockboxLoadStep.class, ArConstants.Lockbox.SUMMARY_AND_ERROR_NOTIFICATION_TO_EMAIL_ADDRESSES));
        message.getToAddresses().addAll(toAddressList);
        String body = composeLockboxLoadBody(flatFileInformation);
        message.setMessage(body);

        try {
            mailService.sendMessage(message);
        } catch (InvalidAddressException e) {
            LOG.error("sendErrorEmail() Invalid email address. Message not sent", e);
        } catch (MessagingException me) {
            throw new RuntimeException("Could not send mail", me);
        }

    }

    protected String composeLockboxLoadBody(FlatFileInformation flatFileInformation) {

        String contactText = parameterService.getParameterValueAsString(LockboxLoadStep.class, ArConstants.Lockbox.CONTACTS_TEXT);
        StringBuffer body = new StringBuffer();
        body.append(contactText);
        body.append("\n");

        for (Object object : flatFileInformation.getFlatFileIdentifierToTransactionInfomationMap().values()) {
            for (String[] message : ((FlatFileTransactionInformation) object).getMessages()) {
                body.append(message[1]);
                body.append("\n");
            }
        }


        for (String[] resultMessage : flatFileInformation.getMessages()) {
            body.append(resultMessage[1]);
            body.append("\n");
        }

        return body.toString();
    }


    protected List<String> getListOfFilesToProcess() {

        //  create a list of the files to process
        List<String> fileNamesToLoad = batchInputFileService.listInputFileNamesWithDoneFile(batchInputFileType);

        if (fileNamesToLoad == null) {
            LOG.error("BatchInputFileService.listInputFileNamesWithDoneFile(" +
                batchInputFileType.getFileTypeIdentifer() + ") returned NULL which should never happen.");
            throw new RuntimeException("BatchInputFileService.listInputFileNamesWithDoneFile(" +
                batchInputFileType.getFileTypeIdentifer() + ") returned NULL which should never happen.");
        }

        //  filenames returned should never be blank/empty/null
        for (String inputFileName : fileNamesToLoad) {
            if (StringUtils.isBlank(inputFileName)) {
                LOG.error("One of the file names returned as ready to process [" + inputFileName +
                    "] was blank.  This should not happen, so throwing an error to investigate.");
                throw new RuntimeException("One of the file names returned as ready to process [" + inputFileName +
                    "] was blank.  This should not happen, so throwing an error to investigate.");
            }
        }

        return fileNamesToLoad;
    }

    /**
     * Accepts a file name and returns a byte-array of the file name contents, if possible.
     * <p>
     * Throws RuntimeExceptions if FileNotFound or IOExceptions occur.
     *
     * @param fileName String containing valid path & filename (relative or absolute) of file to load.
     * @return A Byte Array of the contents of the file.
     */
    protected byte[] safelyLoadFileBytes(String fileName) {
        byte[] fileByteContent;
        try (InputStream fileContents = new FileInputStream(fileName)) {
            fileByteContent = IOUtils.toByteArray(fileContents);
        } catch (FileNotFoundException e1) {
            LOG.error("Batch file not found [" + fileName + "]. " + e1.getMessage());
            throw new RuntimeException("Batch File not found [" + fileName + "]. " + e1.getMessage());
        } catch (IOException e1) {
            LOG.error("IO Exception loading: [" + fileName + "]. " + e1.getMessage());
            throw new RuntimeException("IO Exception loading: [" + fileName + "]. " + e1.getMessage());
        }
        return fileByteContent;
    }

    protected void loadLockbox(Object parsedObject) {
        // create the lockbox object to load data
        List loadLockboxList = new ArrayList<Lockbox>();
        List<Lockbox> lockboxList = (List<Lockbox>) parsedObject;
        int batchSequenceNumber = 1;
        for (Lockbox lockbox : lockboxList) {
            setLockboxToLoad(loadLockboxList, lockbox, batchSequenceNumber);
            batchSequenceNumber++;
        }

        // save lockbox data in AR_LOCKBOX_T
        businessObjectService.save(loadLockboxList);


    }


    /**
     * Clears out associated .done files for the processed data files.
     */
    protected void removeDoneFiles(List<String> dataFileNames) {
        for (String dataFileName : dataFileNames) {
            File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + ".done");
            if (doneFile.exists()) {
                doneFile.delete();
            }
        }
    }


    protected void setLockboxToLoad(List loadLockboxList, Lockbox lockbox, int batchSequenceNumber) {


        for (LockboxDetail detail : lockbox.getLockboxDetails()) {
            Lockbox lockboxToLoad = new Lockbox();
            lockboxToLoad.setLockboxNumber(lockbox.getLockboxNumber());
            lockboxToLoad.setScannedInvoiceDate(lockbox.getScannedInvoiceDate());
            lockboxToLoad.setCustomerNumber(detail.getCustomerNumber());
            lockboxToLoad.setBatchSequenceNumber(batchSequenceNumber);
            lockboxToLoad.setProcessedInvoiceDate(dateTimeService.getCurrentSqlDate());
            lockboxToLoad.setFinancialDocumentReferenceInvoiceNumber(detail.getFinancialDocumentReferenceInvoiceNumber());
            lockboxToLoad.setBillingDate(detail.getBillingDate());
            lockboxToLoad.setInvoiceTotalAmount(detail.getInvoiceTotalAmount());
            lockboxToLoad.setInvoicePaidOrAppliedAmount(detail.getInvoicePaidOrAppliedAmount());
            lockboxToLoad.setCustomerPaymentMediumCode(detail.getCustomerPaymentMediumCode());
            loadLockboxList.add(lockboxToLoad);
        }


    }

    protected void copyAllMessage(Object parsedObject, FlatFileInformation flatFileInformation) {
        List<Lockbox> lockboxList = (List<Lockbox>) parsedObject;
        for (Lockbox lockbox : lockboxList) {
            FlatFileTransactionInformation information = lockbox.getFlatFileTransactionInformation();
            flatFileInformation.getOrAddFlatFileData(lockbox.getLockboxNumber(), information);
        }
    }

    @Override
    public String getFileName(String principalName, Object parsedFileContents, String fileUserIdentifier) {
        // TODO Auto-generated method stub
        return null;
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    public void setBatchInputFileType(BatchInputFileType batchInputFileType) {
        this.batchInputFileType = batchInputFileType;
    }

    public void setReportsDirectory(String reportsDirectory) {
        this.reportsDirectory = reportsDirectory;
    }

    public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
        this.batchInputFileService = batchInputFileService;
    }


    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    public void setMailService(MailService mailService) {
        this.mailService = mailService;
    }


    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }


}
