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

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.service.AccountingPeriodService;
import org.kuali.kfs.fp.businessobject.VoucherSourceAccountingLine;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.kns.service.TransactionalDocumentDictionaryService;
import org.kuali.kfs.krad.document.Copyable;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.DocumentTestUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
import org.kuali.kfs.sys.context.KualiTestBase;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.AccountingDocumentTestUtils;
import org.kuali.kfs.sys.document.Correctable;
import org.kuali.kfs.sys.document.workflow.WorkflowTestUtils;
import org.kuali.kfs.sys.fixture.AccountingLineFixture;
import org.kuali.kfs.sys.monitor.ChangeMonitor;
import org.kuali.kfs.sys.monitor.FinancialSystemDocumentStatusMonitor;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.document.DocumentStatus;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import static org.kuali.kfs.sys.KualiTestAssertionUtils.assertEquality;
import static org.kuali.kfs.sys.KualiTestAssertionUtils.assertInequality;
import static org.kuali.kfs.sys.fixture.AccountingLineFixture.LINE5;
import static org.kuali.kfs.sys.fixture.UserNameFixture.dfogle;

/**
 * This class is used to test JournalVoucherDocument.
 */
@ConfigureContext(session = dfogle)
public class JournalVoucherDocumentTest extends KualiTestBase {

    public static final Class<JournalVoucherDocument> DOCUMENT_CLASS = JournalVoucherDocument.class;

    private JournalVoucherDocument buildDocument() throws Exception {
        // put accounting lines into document parameter for later
        JournalVoucherDocument document = (JournalVoucherDocument) getDocumentParameterFixture();
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        document.getDocumentHeader().setDocumentDescription(StringUtils.abbreviate("Unit Test doc for " + trace[3].getMethodName(), SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(document.getDocumentHeader().getClass(), "documentDescription")));
        document.getDocumentHeader().setExplanation(StringUtils.abbreviate("Unit test created document for " + trace[3].getClassName() + "." + trace[3].getMethodName(), SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(document.getDocumentHeader().getClass(), "explanation")));

        // set accountinglines to document
        for (AccountingLineFixture sourceFixture : getSourceAccountingLineParametersFromFixtures()) {
            sourceFixture.addAsVoucherSourceTo(document);
        }

        document.setBalanceTypeCode(KFSConstants.BALANCE_TYPE_ACTUAL);
        return document;
    }

    /**
     * Had to override b/c there are too many differences between the JV and the standard document structure (i.e. GLPEs generate
     * differently, routing isn't standard, etc).
     *
     * @see org.kuali.rice.krad.document.AccountingDocumentTestBase#testConvertIntoCopy()
     */
    @ConfigureContext(session = dfogle, shouldCommitTransactions = true)
    public void testConvertIntoCopy() throws Exception {
        // save the original doc, wait for status change
        AccountingDocument document = buildDocument();
        SpringContext.getBean(DocumentService.class).routeDocument(document, "saving copy source document", null);
        // collect some preCopy data
        String preCopyId = document.getDocumentNumber();
        String preCopyCopiedFromId = document.getDocumentHeader().getDocumentTemplateNumber();

        int preCopyPECount = document.getGeneralLedgerPendingEntries().size();
        // int preCopyNoteCount = document.getDocumentHeader().getNotes().size();

        List preCopySourceLines = (List) ObjectUtils.deepCopy((Serializable) document.getSourceAccountingLines());
        List preCopyTargetLines = (List) ObjectUtils.deepCopy((Serializable) document.getTargetAccountingLines());
        // validate preCopy state
        assertNotNull(preCopyId);
        assertNull(preCopyCopiedFromId);

        assertEquals(1, preCopyPECount);
        // assertEquals(0, preCopyNoteCount);

        // do the copy
        ((Copyable) document).toCopy();
        // compare to preCopy state

        String postCopyId = document.getDocumentNumber();
        assertFalse(postCopyId.equals(preCopyId));
        // verify that docStatus has changed
        // pending entries should be cleared
        int postCopyPECount = document.getGeneralLedgerPendingEntries().size();
        assertEquals(0, postCopyPECount);
        // TODO: revisit this is it still needed
        // count 1 note, compare to "copied" text
        // int postCopyNoteCount = document.getDocumentHeader().getNotes().size();
        // assertEquals(1, postCopyNoteCount);
        // DocumentNote note = document.getDocumentHeader().getNote(0);
        // assertTrue(note.getFinancialDocumentNoteText().indexOf("copied from") != -1);
        // copiedFrom should be equal to old id
        String copiedFromId = document.getDocumentHeader().getDocumentTemplateNumber();
        assertEquals(preCopyId, copiedFromId);
        // accounting lines should be have different docHeaderIds but same amounts
        List postCopySourceLines = document.getSourceAccountingLines();
        assertEquals(preCopySourceLines.size(), postCopySourceLines.size());
        for (int i = 0; i < preCopySourceLines.size(); ++i) {
            SourceAccountingLine preCopyLine = (SourceAccountingLine) preCopySourceLines.get(i);
            SourceAccountingLine postCopyLine = (SourceAccountingLine) postCopySourceLines.get(i);

            assertInequality(preCopyLine.getDocumentNumber(), postCopyLine.getDocumentNumber());
            assertEquality(preCopyLine.getAmount(), postCopyLine.getAmount());
        }

        List postCopyTargetLines = document.getTargetAccountingLines();
        assertEquals(preCopyTargetLines.size(), postCopyTargetLines.size());
        for (int i = 0; i < preCopyTargetLines.size(); ++i) {
            TargetAccountingLine preCopyLine = (TargetAccountingLine) preCopyTargetLines.get(i);
            TargetAccountingLine postCopyLine = (TargetAccountingLine) postCopyTargetLines.get(i);

            assertInequality(preCopyLine.getDocumentNumber(), postCopyLine.getDocumentNumber());
            assertEquality(preCopyLine.getAmount(), postCopyLine.getAmount());
        }
    }

    /**
     * Had to override b/c there are too many differences between the JV and the standard document structure (i.e. GLPEs generate
     * differently, routing isn't standard, etc).
     *
     * @see org.kuali.rice.krad.document.AccountingDocumentTestBase#testConvertIntoErrorCorrection()
     */
    @ConfigureContext(session = dfogle, shouldCommitTransactions = true)
    public void testConvertIntoErrorCorrection() throws Exception {
        AccountingDocument document = buildDocument();

        // replace the broken sourceLines with one that lets the test succeed
        KualiDecimal balance = new KualiDecimal("21.12");
        ArrayList sourceLines = new ArrayList();
        {
            VoucherSourceAccountingLine sourceLine = new VoucherSourceAccountingLine();
            sourceLine.setDocumentNumber(document.getDocumentNumber());
            sourceLine.setSequenceNumber(new Integer(1));
            sourceLine.setChartOfAccountsCode("BL");
            sourceLine.setAccountNumber("1031400");
            sourceLine.setFinancialObjectCode("1663");
            sourceLine.setAmount(balance);
            sourceLine.setObjectTypeCode("AS");
            sourceLine.setBalanceTypeCode("AC");
            sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
            sourceLine.refresh();
            sourceLines.add(sourceLine);
        }
        document.setSourceAccountingLines(sourceLines);


        String documentHeaderId = document.getDocumentNumber();
        // route the original doc, wait for status change
        SpringContext.getBean(DocumentService.class).routeDocument(document, "saving errorCorrection source document", null);
        // jv docs go straight to final
        WorkflowTestUtils.waitForDocumentApproval(documentHeaderId);
        document = (AccountingDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(documentHeaderId);
        // collect some preCorrect data
        String preCorrectId = document.getDocumentNumber();
        String preCorrectCorrectsId = document.getFinancialSystemDocumentHeader().getFinancialDocumentInErrorNumber();

        int preCorrectPECount = document.getGeneralLedgerPendingEntries().size();
        // int preCorrectNoteCount = document.getDocumentHeader().getNotes().size();
        DocumentStatus preCorrectStatus = document.getDocumentHeader().getWorkflowDocument().getStatus();

        ArrayList preCorrectSourceLines = (ArrayList) ObjectUtils.deepCopy(new ArrayList(document.getSourceAccountingLines()));
        ArrayList preCorrectTargetLines = (ArrayList) ObjectUtils.deepCopy(new ArrayList(document.getTargetAccountingLines()));
        // validate preCorrect state
        assertNotNull("Pre-correct document number should not be null.", preCorrectId);
        assertNull("preCorrectCorrectsId should have been null", preCorrectCorrectsId);

        // assertEquals(0, preCorrectNoteCount);
        assertEquals("Document status is incorrect", DocumentStatus.FINAL, preCorrectStatus);
        // do the copy
        ((Correctable) document).toErrorCorrection();
        // compare to preCorrect state

        String postCorrectId = document.getDocumentNumber();
        assertFalse("document number should be different before and after correction.  Both were: " + postCorrectId, postCorrectId.equals(preCorrectId));
        // pending entries should be cleared
        int postCorrectPECount = document.getGeneralLedgerPendingEntries().size();
        assertEquals("pending entry count should have been zero after correction", 0, postCorrectPECount);
        // TODO: revisit this is it still needed
        // count 1 note, compare to "correction" text
        // int postCorrectNoteCount = document.getDocumentHeader().getNotes().size();
        // assertEquals(1, postCorrectNoteCount);
        // DocumentNote note = document.getDocumentHeader().getNote(0);
        // assertTrue(note.getFinancialDocumentNoteText().indexOf("correction") != -1);
        // correctsId should be equal to old id
        String correctsId = document.getFinancialSystemDocumentHeader().getFinancialDocumentInErrorNumber();
        assertEquals(preCorrectId, correctsId);
        // accounting lines should have sign reversed on amounts
        List postCorrectSourceLines = document.getSourceAccountingLines();
        assertEquals(preCorrectSourceLines.size(), postCorrectSourceLines.size());
        for (int i = 0; i < preCorrectSourceLines.size(); ++i) {
            SourceAccountingLine preCorrectLine = (SourceAccountingLine) preCorrectSourceLines.get(i);
            SourceAccountingLine postCorrectLine = (SourceAccountingLine) postCorrectSourceLines.get(i);

            assertEquality(postCorrectId, postCorrectLine.getDocumentNumber());
            assertEquality(preCorrectLine.getAmount(), postCorrectLine.getAmount());
            assertEquality(postCorrectLine.getDebitCreditCode(), KFSConstants.GL_CREDIT_CODE);
        }

        List postCorrectTargetLines = document.getTargetAccountingLines();
        assertEquals(preCorrectTargetLines.size(), postCorrectTargetLines.size());
        for (int i = 0; i < preCorrectTargetLines.size(); ++i) {
            TargetAccountingLine preCorrectLine = (TargetAccountingLine) preCorrectTargetLines.get(i);
            TargetAccountingLine postCorrectLine = (TargetAccountingLine) postCorrectTargetLines.get(i);

            assertEquality(postCorrectId, postCorrectLine.getDocumentNumber());
            assertEquality(preCorrectLine.getAmount().negated(), postCorrectLine.getAmount());
        }
    }

    /**
     * Override b/c the status changing is flakey with this doc b/c routing is special (goes straight to final).
     *
     * @see org.kuali.rice.krad.document.DocumentTestBase#testRouteDocument()
     */
    // @RelatesTo(JiraIssue.KULRNE4926)
    @ConfigureContext(session = dfogle, shouldCommitTransactions = true)
    public void testRouteDocument() throws Exception {
        // save the original doc, wait for status change
        Document document = buildDocument();
        assertFalse(DocumentStatus.ENROUTE.equals(document.getDocumentHeader().getWorkflowDocument().getStatus()));
        SpringContext.getBean(DocumentService.class).routeDocument(document, "saving copy source document", null);
        // jv docs go sttraight to final
        WorkflowTestUtils.waitForDocumentApproval(document.getDocumentNumber());
        // also check the Kuali (not Workflow) document status
        FinancialSystemDocumentStatusMonitor statusMonitor = new FinancialSystemDocumentStatusMonitor(SpringContext.getBean(DocumentService.class), document.getDocumentHeader().getDocumentNumber(), KFSConstants.DocumentStatusCodes.APPROVED);
        assertTrue(ChangeMonitor.waitUntilChange(statusMonitor, 30, 5));
    }

    private Document getDocumentParameterFixture() throws Exception {
        return DocumentTestUtils.createDocument(SpringContext.getBean(DocumentService.class), JournalVoucherDocument.class);
    }

    private List<AccountingLineFixture> getTargetAccountingLineParametersFromFixtures() {
        ArrayList list = new ArrayList();
        return list;
    }

    private List<AccountingLineFixture> getSourceAccountingLineParametersFromFixtures() {
        List<AccountingLineFixture> list = new ArrayList<AccountingLineFixture>();
        list.add(LINE5);
        return list;
    }


    public final void testAddAccountingLine() throws Exception {
        List<SourceAccountingLine> sourceLines = generateSouceAccountingLines();
        List<TargetAccountingLine> targetLines = generateTargetAccountingLines();
        int expectedSourceTotal = sourceLines.size();
        int expectedTargetTotal = targetLines.size();
        JournalVoucherDocument document = DocumentTestUtils.createDocument(SpringContext.getBean(DocumentService.class), DOCUMENT_CLASS);
        document.setBalanceTypeCode(KFSConstants.BALANCE_TYPE_ACTUAL);

        AccountingDocumentTestUtils.testAddAccountingLine(document, sourceLines, targetLines, expectedSourceTotal, expectedTargetTotal);
    }

    public final void testGetNewDocument() throws Exception {
        AccountingDocumentTestUtils.testGetNewDocument_byDocumentClass(DOCUMENT_CLASS, SpringContext.getBean(DocumentService.class));
    }

    public final void testConvertIntoCopy_copyDisallowed() throws Exception {
        AccountingDocumentTestUtils.testConvertIntoCopy_copyDisallowed(buildDocument(), SpringContext.getBean(DataDictionaryService.class));

    }

    public final void testConvertIntoErrorCorrection_documentAlreadyCorrected() throws Exception {
        AccountingDocumentTestUtils.testConvertIntoErrorCorrection_documentAlreadyCorrected(buildDocument(), SpringContext.getBean(TransactionalDocumentDictionaryService.class));
    }

    public final void testConvertIntoErrorCorrection_errorCorrectionDisallowed() throws Exception {
        AccountingDocumentTestUtils.testConvertIntoErrorCorrection_errorCorrectionDisallowed(buildDocument(), SpringContext.getBean(DataDictionaryService.class));
    }

    public final void testConvertIntoErrorCorrection_invalidYear() throws Exception {
        AccountingDocumentTestUtils.testConvertIntoErrorCorrection_invalidYear(buildDocument(), SpringContext.getBean(TransactionalDocumentDictionaryService.class), SpringContext.getBean(AccountingPeriodService.class));
    }

    @ConfigureContext(session = dfogle, shouldCommitTransactions = true)
    public final void testSaveDocument() throws Exception {
        AccountingDocumentTestUtils.testSaveDocument(buildDocument(), SpringContext.getBean(DocumentService.class));
    }


    // test util methods
    private List<SourceAccountingLine> generateSouceAccountingLines() throws Exception {
        List<SourceAccountingLine> sourceLines = new ArrayList<SourceAccountingLine>();
        // set accountinglines to document
        for (AccountingLineFixture sourceFixture : getSourceAccountingLineParametersFromFixtures()) {
            sourceLines.add(sourceFixture.createSourceAccountingLine());
        }

        return sourceLines;
    }

    private List<TargetAccountingLine> generateTargetAccountingLines() throws Exception {
        List<TargetAccountingLine> targetLines = new ArrayList<TargetAccountingLine>();
        for (AccountingLineFixture targetFixture : getTargetAccountingLineParametersFromFixtures()) {
            targetLines.add(targetFixture.createTargetAccountingLine());
        }

        return targetLines;
    }


}

