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

import au.com.bytecode.opencsv.bean.ColumnPositionMappingStrategy;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.gl.batch.CollectorBatch;
import org.kuali.kfs.kns.service.DictionaryValidationService;
import org.kuali.kfs.krad.service.SequenceAccessorService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.MessageMap;
import org.kuali.kfs.pdp.PdpConstants;
import org.kuali.kfs.pdp.PdpKeyConstants;
import org.kuali.kfs.pdp.batch.service.ExtractPaymentService;
import org.kuali.kfs.pdp.businessobject.LoadPaymentStatus;
import org.kuali.kfs.pdp.businessobject.PaymentAccountDetail;
import org.kuali.kfs.pdp.businessobject.PaymentDetail;
import org.kuali.kfs.pdp.businessobject.PaymentFileLoad;
import org.kuali.kfs.pdp.businessobject.PaymentGroup;
import org.kuali.kfs.pdp.businessobject.PaymentHeader;
import org.kuali.kfs.pdp.businessobject.PaymentNoteText;
import org.kuali.kfs.pdp.businessobject.ResearchParticipantPaymentDetail;
import org.kuali.kfs.pdp.businessobject.ResearchParticipantUpload;
import org.kuali.kfs.pdp.service.CustomerProfileService;
import org.kuali.kfs.pdp.service.PaymentFileService;
import org.kuali.kfs.pdp.service.ResearchParticipantPaymentValidationService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.batch.BatchInputFileTypeBase;
import org.kuali.kfs.sys.businessobject.MappingCSVReader;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiInteger;
import org.kuali.rice.location.api.country.Country;
import org.kuali.rice.location.api.country.CountryService;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

// Created for Research Participant Upload
public class ResearchParticipantInboundServiceInputType extends BatchInputFileTypeBase {
    private static Logger LOG = Logger.getLogger(ResearchParticipantInboundServiceInputType.class);
    private static final String FILE_NAME_PREFIX = "research_participant_";
    private static final String PAYMENT_HEADER = "Payment Header";
    private static final String ACCOUNTING_LINE = "Accounting Line";
    private static final String PAYMENT_DETAIL = "Payment Detail";
    private static final String GL_DESCRIPTION = "GL Description";

    private DateTimeService dateTimeService;
    private CustomerProfileService customerProfileService;
    private PaymentFileService paymentFileService;
    private DictionaryValidationService dictionaryValidationService;
    private SequenceAccessorService sequenceAccessorService;
    private CountryService countryService;
    private ResearchParticipantPaymentValidationService researchParticipantPaymentValidationService;
    private ExtractPaymentService extractPaymentService;

    /**
     * @see org.kuali.kfs.sys.batch.BatchInputFileType#getFileTypeIdentifer()
     */
    @Override
    public String getFileTypeIdentifer() {
        return PdpConstants.RESEARCH_PARTICIPANT_INPUT_FILE_TYPE_INDENTIFIER;
    }

    /**
     * Builds the file name using the following construction: All research participant inbound service files start with
     * research_participant_ append the username of the user who is uploading the file then the fileUserIdentifier
     * then the timestamp.
     *
     * @param user               who uploaded the file
     * @param parsedFileContents represents collector batch object
     * @param userIdentifier     user identifier for user who uploaded file
     * @return String returns file name using the convention mentioned in the description
     * @see org.kuali.kfs.sys.batch.BatchInputFileType#getFileName(org.kuali.rice.kim.bo.Person, java.lang.Object,
     * java.lang.String)
     */

    @Override
    public String getFileName(String principalName, Object parsedFileContents, String fileUserIdentifer) {
        return "";
    }

    public DateTimeService getDateTimeService() {
        return dateTimeService;
    }

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

    @Override
    public void process(String fileName, Object parsedFileContents) {
        LoadPaymentStatus status = new LoadPaymentStatus();
        status.setMessageMap(new MessageMap());
        paymentFileService.loadPayments((PaymentFileLoad) parsedFileContents, status, getFileName(GlobalVariables.getUserSession().getPrincipalName(), null, null));

    }

    public CustomerProfileService getCustomerProfileService() {
        return customerProfileService;
    }

    public void setCustomerProfileService(CustomerProfileService customerProfileService) {
        this.customerProfileService = customerProfileService;
    }

    public PaymentFileService getPaymentFileService() {
        return paymentFileService;
    }

    public void setPaymentFileService(PaymentFileService paymentFileService) {
        this.paymentFileService = paymentFileService;
    }

    public DictionaryValidationService getDictionaryValidationService() {
        return dictionaryValidationService;
    }

    public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
        this.dictionaryValidationService = dictionaryValidationService;
    }

    public SequenceAccessorService getSequenceAccessorService() {
        return sequenceAccessorService;
    }

    public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) {
        this.sequenceAccessorService = sequenceAccessorService;
    }

    public CountryService getCountryService() {
        return countryService;
    }

    public void setCountryService(CountryService countryService) {
        this.countryService = countryService;
    }

    public ExtractPaymentService getExtractPaymentService() {
        return extractPaymentService;
    }

    public void setExtractPaymentService(ExtractPaymentService extractPaymentService) {
        this.extractPaymentService = extractPaymentService;
    }

    /**
     * Override the superclass method to parse the incoming spreadsheet
     * file and convert into PaymentFileLoad, then return it to the invoker.
     *
     * @see org.kuali.kfs.sys.batch.BatchInputFileType#parse(byte[])
     */
    @Override
    public Object parse(byte[] fileByteContent) throws ParseException {

        List<CollectorBatch> batchList = new ArrayList<CollectorBatch>();
        ResearchParticipantUpload uploadFile = new ResearchParticipantUpload();

        try {

            InputStreamReader inputStreamReader = new InputStreamReader(new ByteArrayInputStream(fileByteContent));
            MappingCSVReader reader = new MappingCSVReader(inputStreamReader);

            String[] fileLine = null;

            while ((fileLine = reader.readNext()) != null) {

                ColumnPositionMappingStrategy strat = new ColumnPositionMappingStrategy();
                if (fileLine[0].equalsIgnoreCase(PAYMENT_HEADER)) {
                    //If there's already a payment header on the uploadFile,
                    //we'll give error message to the user that something's wrong
                    //with the file format because the file can only have 1 payment
                    //header.
                    if (uploadFile.getPaymentHeader() != null) {
                        GlobalVariables.getMessageMap().putError("uploadFile", PdpKeyConstants.ERROR_BATCH_UPLOAD_BAD_FORMAT, PdpConstants.MULTIPLE_PAYMENT_HEADERS);
                        return null;
                    }
                    PaymentHeader ph = parsePaymentHeader(fileLine, reader, strat);
                    uploadFile.setPaymentHeader(ph);
                } else if (fileLine[0].equalsIgnoreCase(ACCOUNTING_LINE)) {
                    //If there's already an accounting line on the uploadFile,
                    //we'll give error message to the user that something's wrong
                    //with the file format because the file can only have 1
                    //accountingLine
                    if (uploadFile.getPaymentAccountDetail() != null) {
                        GlobalVariables.getMessageMap().putError("uploadFile", PdpKeyConstants.ERROR_BATCH_UPLOAD_BAD_FORMAT, PdpConstants.MULTIPLE_ACCOUNTS);
                        return null;
                    }
                    PaymentAccountDetail ac = parseAccountingLine(fileLine, reader, strat);
                    uploadFile.setPaymentAccountDetail(ac);
                } else if (fileLine[0].equalsIgnoreCase(PAYMENT_DETAIL)) {
                    ResearchParticipantPaymentDetail pd = parsePaymentDetail(fileLine, reader, strat);
                    uploadFile.addPaymentDetail(pd);
                } else if (fileLine[0].equalsIgnoreCase(GL_DESCRIPTION)) {
                    uploadFile.setGenericDescription(fileLine[1]);
                }
            }
        } catch (IOException ioe) {
            LOG.error("Error encountered reading from file content", ioe);
            throw new ParseException("Error encountered reading from file content", ioe);
        }

        if ((uploadFile.getPaymentDetails().size() < 1) || uploadFile.getPaymentHeader() == null || uploadFile.getPaymentAccountDetail() == null) {
            GlobalVariables.getMessageMap().putError("uploadFile", PdpKeyConstants.ERROR_BATCH_UPLOAD_BAD_FORMAT, KFSConstants.EMPTY_STRING);
            return null;
        }
        PaymentFileLoad paymentFileLoad = convertToPaymentFileLoad(uploadFile);

        return paymentFileLoad;
    }

    /**
     * Converts the ResearchParticipantUpload which represents the spreadsheet
     * into the existing PaymentFileLoad.
     *
     * @param uploadFile
     * @return
     */
    protected PaymentFileLoad convertToPaymentFileLoad(ResearchParticipantUpload uploadFile) {
        PaymentFileLoad paymentFileLoad = new PaymentFileLoad();
        paymentFileLoad.setChart(uploadFile.getPaymentHeader().getChartOfAccountsCode());
        paymentFileLoad.setUnit(uploadFile.getPaymentHeader().getUnit());
        paymentFileLoad.setSubUnit(uploadFile.getPaymentHeader().getSubUnit());
        paymentFileLoad.setCreationDate(uploadFile.getPaymentHeader().getCreationDate());
        paymentFileLoad.setPaymentCount(uploadFile.getPaymentDetails().size());
        paymentFileLoad.setPaymentTotalAmount(uploadFile.getPaymentTotalAmount());
        paymentFileLoad.setPaymentGroups(createPaymentGroups(uploadFile));
        KualiInteger batchId = new KualiInteger(sequenceAccessorService.getNextAvailableSequenceNumber(PdpConstants.SequenceNames.PDP_PMT_FIL_ID, PaymentFileLoad.class).intValue());
        paymentFileLoad.setBatchId(batchId);

        return paymentFileLoad;
    }

    /**
     * Creates the list of payment groups given the upload file.
     *
     * @param uploadFile
     * @return
     */
    protected List<PaymentGroup> createPaymentGroups(ResearchParticipantUpload uploadFile) {
        List<PaymentGroup> result = new ArrayList<PaymentGroup>();
        for (ResearchParticipantPaymentDetail detail : uploadFile.getPaymentDetails()) {
            String payeeName = detail.getPayeeName();
            PaymentGroup paymentGroup = new PaymentGroup();
            paymentGroup.setPayeeName(payeeName);
            paymentGroup.setLine1Address(detail.getAddressLine1());
            paymentGroup.setLine2Address(detail.getAddressLine2());
            paymentGroup.setLine3Address(detail.getAddressLine3());
            paymentGroup.setCity(detail.getCity());
            paymentGroup.setState(detail.getState());
            paymentGroup.setZipCd(detail.getZip());
            paymentGroup.setPayeeIdTypeCd(uploadFile.getPaymentHeader().getVendorOrEmployee());
            Country defaultCountry = countryService.getDefaultCountry();
            paymentGroup.setCountry(defaultCountry.getCode());
            paymentGroup.setPaymentDate(uploadFile.getPaymentHeader().getPaymentDate());
            paymentGroup.setPaymentStatusCode(KFSConstants.PdpConstants.PAYMENT_OPEN_STATUS_CODE);

            PaymentDetail paymentDetail = new PaymentDetail();
            paymentDetail.setInvoiceDate(uploadFile.getPaymentHeader().getPaymentDate());
            paymentDetail.setCustPaymentDocNbr(uploadFile.getPaymentHeader().getSourceDocNumber());
            paymentDetail.setFinancialSystemOriginCode(KFSConstants.ORIGIN_CODE_KUALI);

            List<String> formattedCheckNoteLines = this.getExtractPaymentService().formatCheckNoteLines(detail.getCheckStubText());
            int count = 1;
            for (String noteText : formattedCheckNoteLines) {
                PaymentNoteText note = new PaymentNoteText();
                note.setCustomerNoteText(noteText);
                note.setCustomerNoteLineNbr(new KualiInteger(count++));
                paymentDetail.addNote(note);
            }

            uploadFile.getPaymentAccountDetail().setAccountNetAmount(detail.getAmount());

            PaymentAccountDetail ac = new PaymentAccountDetail();
            ac.setFinChartCode(uploadFile.getPaymentAccountDetail().getFinChartCode());
            ac.setAccountNbr(uploadFile.getPaymentAccountDetail().getAccountNbr());
            ac.setFinObjectCode(uploadFile.getPaymentAccountDetail().getFinObjectCode());
            ac.setOrgReferenceId(uploadFile.getPaymentAccountDetail().getOrgReferenceId());
            ac.setAccountNetAmount(uploadFile.getPaymentAccountDetail().getAccountNetAmount());

            // change nulls into ---'s for the fields that need it
            if (StringUtils.isBlank(uploadFile.getPaymentAccountDetail().getFinSubObjectCode())) {
                ac.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
            } else {
                ac.setFinSubObjectCode(uploadFile.getPaymentAccountDetail().getFinSubObjectCode());
            }
            if (StringUtils.isBlank(uploadFile.getPaymentAccountDetail().getSubAccountNbr())) {
                ac.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
            } else {
                ac.setSubAccountNbr(uploadFile.getPaymentAccountDetail().getSubAccountNbr());
            }
            if (StringUtils.isBlank(uploadFile.getPaymentAccountDetail().getProjectCode())) {
                ac.setProjectCode(KFSConstants.getDashProjectCode());
            } else {
                ac.setProjectCode(uploadFile.getPaymentAccountDetail().getProjectCode());
            }

            paymentDetail.addAccountDetail(ac);
            paymentGroup.addPaymentDetails(paymentDetail);
            result.add(paymentGroup);
        }

        return result;
    }

    /**
     * Parses the payment header information in the file.
     *
     * @param fileLine
     * @param reader
     * @param strat
     * @return
     */
    protected PaymentHeader parsePaymentHeader(String[] fileLine, MappingCSVReader reader, ColumnPositionMappingStrategy strat) {
        strat.setType(PaymentHeader.class);
        String[] headerColumns = new String[]{PdpConstants.PaymentHeader.CHART,
            PdpConstants.PaymentHeader.UNIT, PdpConstants.PaymentHeader.SUBUNIT,
            PdpConstants.PaymentHeader.CREATION_DATE, PdpConstants.PaymentHeader.VENDOR_OR_EMPLOYEE,
            PdpConstants.PaymentHeader.SOURCE_DOC_NUMBER, PdpConstants.PaymentHeader.PAYMENT_DATE};
        strat.setColumnMapping(headerColumns);
        String[] newNextLine = Arrays.copyOfRange(fileLine, 1, fileLine.length);
        PaymentHeader ph = (PaymentHeader) reader.processLine(strat, newNextLine);
        return ph;
    }

    /**
     * Parses the accounting line information in the file.
     *
     * @param fileLine
     * @param reader
     * @param strat
     * @return
     */
    protected PaymentAccountDetail parseAccountingLine(String[] fileLine, MappingCSVReader reader, ColumnPositionMappingStrategy strat) {
        strat.setType(PaymentAccountDetail.class);
        String[] headerColumns = new String[]{PdpConstants.PaymentAccountDetail.CHART,
            PdpConstants.PaymentAccountDetail.ACCOUNT_NBR,
            PdpConstants.PaymentAccountDetail.SUB_ACCOUNT_NBR, PdpConstants.PaymentAccountDetail.OBJECT_CODE,
            PdpConstants.PaymentAccountDetail.SUB_OBJECT_CODE, PdpConstants.PaymentAccountDetail.PROJECT_CODE,
            PdpConstants.PaymentAccountDetail.ORG_REF_ID};
        strat.setColumnMapping(headerColumns);
        String[] newNextLine = Arrays.copyOfRange(fileLine, 1, fileLine.length);
        PaymentAccountDetail ac = (PaymentAccountDetail) reader.processLine(strat, newNextLine);
        return ac;

    }

    /**
     * Parses the payment detail information in the file.
     *
     * @param fileLine
     * @param reader
     * @param strat
     * @return
     */
    protected ResearchParticipantPaymentDetail parsePaymentDetail(String[] fileLine, MappingCSVReader reader, ColumnPositionMappingStrategy strat) {
        strat.setType(ResearchParticipantPaymentDetail.class);
        String[] headerColumns = new String[]{PdpConstants.PaymentDetail.PAYEE_NAME,
            PdpConstants.PaymentDetail.ADDRESS_LINE_1, PdpConstants.PaymentDetail.ADDRESS_LINE_2,
            PdpConstants.PaymentDetail.ADDRESS_LINE_3, PdpConstants.PaymentDetail.CITY,
            PdpConstants.PaymentDetail.STATE, PdpConstants.PaymentDetail.ZIP,
            PdpConstants.PaymentDetail.CHECK_STUB_TEXT, PdpConstants.PaymentDetail.AMOUNT};
        strat.setColumnMapping(headerColumns);
        String[] newNextLine = Arrays.copyOfRange(fileLine, 1, fileLine.length);
        ResearchParticipantPaymentDetail pd = (ResearchParticipantPaymentDetail) reader.processLine(strat, newNextLine);
        return pd;
    }

    @Override
    public String getTitleKey() {
        return PdpKeyConstants.MESSAGE_BATCH_UPLOAD_TITLE_RESEARCH_PARTICIPANT_FILE;
    }

    @Override
    public String getAuthorPrincipalName(File file) {
        return org.apache.commons.lang.StringUtils.substringBetween(file.getName(), FILE_NAME_PREFIX, PdpConstants.FILE_NAME_PART_DELIMITER);
    }

    /**
     * Validates the information in the upload file using the doPaymentFileValidation
     * in the existing paymentFileService from the foundation. In addition to that,
     * it's also going to invoke Data Dictionary Validation for the PaymentFileLoad,
     * for all the PaymentGroup objects and all of the PaymentDetail and PaymentAccountDetail
     * objects to do some basic format validation such as length, requiredness, etc in place of
     * the xml schema validation.
     *
     * @see org.kuali.kfs.sys.batch.BatchInputFileType#validate(java.lang.Object)
     */
    @Override
    public boolean validate(Object parsedFileContents) {
        PaymentFileLoad paymentFile = (PaymentFileLoad) parsedFileContents;

        dictionaryValidationService.validateBusinessObject(paymentFile);
        for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
            dictionaryValidationService.validateBusinessObject(paymentGroup);
            for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
                dictionaryValidationService.validateBusinessObject(paymentDetail);
                for (PaymentAccountDetail account : paymentDetail.getAccountDetail()) {
                    dictionaryValidationService.validateBusinessObject(account);
                }
            }
        }

        MessageMap errorMessageMap = GlobalVariables.getMessageMap();

        boolean isAccountDetailValid = this.getResearchParticipantPaymentValidationService().validatePaymentAccount(paymentFile, errorMessageMap);
        if (isAccountDetailValid) {
            paymentFileService.doPaymentFileValidation(paymentFile, errorMessageMap);
        }

        return paymentFile.isPassedValidation();
    }

    /**
     * Gets the researchParticipantPaymentValidationService attribute.
     *
     * @return Returns the researchParticipantPaymentValidationService.
     */
    public ResearchParticipantPaymentValidationService getResearchParticipantPaymentValidationService() {
        return researchParticipantPaymentValidationService;
    }

    /**
     * Sets the researchParticipantPaymentValidationService attribute value.
     *
     * @param researchParticipantPaymentValidationService The researchParticipantPaymentValidationService to set.
     */
    public void setResearchParticipantPaymentValidationService(ResearchParticipantPaymentValidationService researchParticipantPaymentValidationService) {
        this.researchParticipantPaymentValidationService = researchParticipantPaymentValidationService;
    }

    /**
     * For Research Participant Upload, we intentionally don't want to save
     * the input file for security purposes. Therefore this method will return false.
     *
     * @see org.kuali.kfs.sys.batch.BatchInputFileTypeBase#shouldSave()
     */
    @Override
    public boolean shouldSave() {
        return false;
    }
}
