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

import junit.framework.Assert;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.krad.exception.ValidationException;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderDocTypes;
import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.fixture.PaymentRequestDocumentFixture;
import org.kuali.kfs.module.purap.fixture.PurApItemFixture;
import org.kuali.kfs.module.purap.fixture.PurchaseOrderDocumentFixture;
import org.kuali.kfs.module.purap.fixture.PurchaseOrderItemFixture;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.context.KualiTestBase;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocumentTestUtils;
import org.kuali.rice.kew.api.document.DocumentStatus;

import java.util.ArrayList;
import java.util.List;

import static org.kuali.kfs.sys.fixture.UserNameFixture.appleton;
import static org.kuali.kfs.sys.fixture.UserNameFixture.kfs;
import static org.kuali.kfs.sys.fixture.UserNameFixture.parke;

public class PurchaseOrderChangeDocumentTest extends KualiTestBase {

    protected static DocumentService docService = null;
    protected static PurchaseOrderService poService = null;
    protected static PurchaseOrderDocument poTest = null;
    protected static PurchaseOrderDocument poChange = null;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        docService = SpringContext.getBean(DocumentService.class);
        poService = SpringContext.getBean(PurchaseOrderService.class);
        // Create and save a minimally-populated basic PO document for each test.
        poTest = PurchaseOrderDocumentFixture.PO_ONLY_REQUIRED_FIELDS_MULTI_ITEMS.createPurchaseOrderDocument();
        poTest.setApplicationDocumentStatus(PurchaseOrderStatuses.APPDOC_OPEN);
        poTest.refreshNonUpdateableReferences();
        poTest.prepareForSave();
        AccountingDocumentTestUtils.saveDocument(poTest, docService);
    }

    @Override
    protected void tearDown() throws Exception {
        docService = null;
        poService = null;
        poTest = null;
        poChange = null;
        super.tearDown();
    }

    /**
     * Refreshes poTest and poChange by fetching them from DB,
     * so that changes made to them during routing will be reflected.
     */
    private void refreshPO() {
        poTest = poService.getPurchaseOrderByDocumentNumber(poTest.getDocumentNumber());
        //poChange = poService.getPurchaseOrderByDocumentNumber(poChange.getDocumentNumber());
    }

    /**
     * Calls PO service to create and save a PO change document and assigns the new document to poChange;
     * refreshes poTest by fetching it from DB so that changes made to the existing PO will be reflected;
     * also, makes reasons for test exceptions more obvious by inserting into the exception's
     * message the error maps which contain the keys to human-readable reasons for rule failure.
     *
     * @param documentType   A String, one of the PurchaseOrderDocumentTypes.
     * @param documentStatus A String, one of the PurchaseOrderDocumentStatuses.
     * @throws Exception
     */
    private void createAndSavePOChangeDocument(String documentType, String documentStatus) throws Exception {
        try {
            poChange = poService.createAndSavePotentialChangeDocument(
                poTest.getDocumentNumber(), documentType, documentStatus);
            poChange = (PurchaseOrderDocument) docService.getByDocumentHeaderId(poChange.getDocumentNumber());
            poTest = poService.getPurchaseOrderByDocumentNumber(poTest.getDocumentNumber());
        } catch (ValidationException ve) {
            throw new ValidationException(GlobalVariables.getMessageMap().toString() + ve);
        }
    }

    /**
     * Calls PO service to create and route a PO change document and assigns the new document to poChange;
     * refreshes poTest by fetching it from DB so that changes made to the existing PO will be reflected;
     * also, makes reasons for test exceptions more obvious by inserting into the exception's
     * message the error maps which contain the keys to human-readable reasons for rule failure.
     *
     * @param documentType   A String, one of the PurchaseOrderDocumentTypes.
     * @param documentStatus A String, one of the PurchaseOrderDocumentStatuses.
     * @throws Exception
     */
    private void createAndRoutePOChangeDocument(String documentType, String documentStatus) throws Exception {
        try {
            poChange = poService.createAndRoutePotentialChangeDocument(
                poTest.getDocumentNumber(), documentType, "unit test", new ArrayList(), documentStatus);
            poTest = poService.getPurchaseOrderByDocumentNumber(poTest.getDocumentNumber());
        } catch (ValidationException ve) {
            throw new ValidationException(GlobalVariables.getMessageMap().toString() + ve);
        }
    }

    private void createAndSavePOSplitDocument(List<PurchaseOrderItem> newPOItems, boolean copyNotes, String splitNoteText) throws Exception {
        try {
            poTest.setApplicationDocumentStatus(PurchaseOrderStatuses.APPDOC_IN_PROCESS);
            poChange = poService.createAndSavePurchaseOrderSplitDocument(
                newPOItems, poTest, copyNotes, splitNoteText);
            poTest = poService.getPurchaseOrderByDocumentNumber(poTest.getDocumentNumber());
        } catch (ValidationException ve) {
            throw new ValidationException(GlobalVariables.getMessageMap().toString() + ve);
        }
    }

    @ConfigureContext(session = kfs, shouldCommitTransactions = true)
    public final void testPurchaseOrderClose() throws Exception {
        // There must be a PREQ against this PO in order to close this PO.

        changeCurrentUser(appleton);
        PaymentRequestDocument preq = PaymentRequestDocumentFixture.PREQ_FOR_PO_CLOSE_DOC.createPaymentRequestDocument();
        preq.setPurchaseOrderIdentifier(poTest.getPurapDocumentIdentifier());
        AccountingDocumentTestUtils.saveDocument(preq, docService);
        createAndRoutePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_PENDING_CLOSE);
        assertMatchChangePO(poTest, poChange); /*
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.PENDING_CLOSE));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        }
        WorkflowTestUtils.waitForStatusChange(poChange.getDocumentHeader().getWorkflowDocument(), DocumentStatus.FINAL);
        refreshPO();
        if (poChange.getDocumentHeader().getWorkflowDocument().isFinal()) { */
        assertFalse(poTest.isPurchaseOrderCurrentIndicator());
        assertFalse(poTest.isPendingActionIndicator());
        assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_RETIRED_VERSION));
        assertTrue(poChange.isPurchaseOrderCurrentIndicator());
        assertFalse(poChange.isPendingActionIndicator());
        assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CLOSED));
        //}
    }

    @ConfigureContext(session = parke, shouldCommitTransactions = true)
    public void testSplitPurchaseOrder() throws Exception {
        List<PurchaseOrderItem> items = new ArrayList<PurchaseOrderItem>();
        items.add((PurchaseOrderItem) PurchaseOrderItemFixture.PO_QTY_UNRESTRICTED_ITEM_2.createPurchaseOrderItem(PurApItemFixture.BASIC_QTY_ITEM_2));
        createAndSavePOSplitDocument(items, true, "Reason for splitting.");
        // Proving that most things other than items are the same.
        assertMatchChangePO(poTest, poChange);
        assertTrue(poTest.getPurapDocumentIdentifier().compareTo(poChange.getPurapDocumentIdentifier()) < 0);
        // Neither the original nor the resulting PO may have no items.
        assertFalse(poChange.getItems().size() == 1);
        assertFalse(poTest.getItems().size() == 1);
        List<PurchaseOrderItem> splitPOItems = poChange.getItems();
        // Check renumbering.
        int i = 0;
        for (PurchaseOrderItem splitPOItem : splitPOItems) {
            if (splitPOItem.getItemType().isLineItemIndicator()) {
                assertTrue(splitPOItem.getItemLineNumber().intValue() == ++i);
            }
        }
    }

    @ConfigureContext(session = parke, shouldCommitTransactions = true)
    public final void testAmendPurchaseOrder() throws Exception {
        createAndSavePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_AMENDMENT_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_AMENDMENT);
        assertMatchChangePO(poTest, poChange);
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_AMENDMENT));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        } /*
        WorkflowTestUtils.waitForStatusChange(poChange.getDocumentHeader().getWorkflowDocument(), DocumentStatus.FINAL);
        refreshPO();
        if (poChange.getDocumentHeader().getWorkflowDocument().isFinal()) {
            assertFalse(poTest.isPurchaseOrderCurrentIndicator());
            assertFalse(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_RETIRED_VERSION));
            assertTrue(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_OPEN));
        } */
    }

    /**
     * Creates and saves a PurchaseOrderAmendmentDocument, then calls
     * the documentService to cancel the PurchaseOrderAmendmentDocument.
     * This is to test the case for canceling a PurchaseOrderAmendmentDocument.
     *
     * @throws Exception
     */
    @ConfigureContext(session = kfs, shouldCommitTransactions = true)
    public final void testCancelAmendPurchaseOrder() throws Exception {
        createAndSavePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_AMENDMENT_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_AMENDMENT);
        assertMatchChangePO(poTest, poChange);
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_AMENDMENT));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        }
        SpringContext.getBean(DocumentService.class).cancelDocument(poChange, "");
        assertTrue(poChange.getDocumentHeader().getWorkflowDocument().getStatus().getCode().equals(KFSConstants.DocumentStatusCodes.CANCELLED));
    }

    @ConfigureContext(session = kfs, shouldCommitTransactions = true)
    public final void testPurchaseOrderPaymentHold() throws Exception {
        createAndRoutePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_PAYMENT_HOLD_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_PENDING_PAYMENT_HOLD);
        assertMatchChangePO(poTest, poChange); /*
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_PENDING_APPDOC_PAYMENT_HOLD));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        }
        WorkflowTestUtils.waitForStatusChange(poChange.getDocumentHeader().getWorkflowDocument(), DocumentStatus.FINAL);
        refreshPO();
        if (poChange.getDocumentHeader().getWorkflowDocument().isFinal()) { */
        assertFalse(poTest.isPurchaseOrderCurrentIndicator());
        assertFalse(poTest.isPendingActionIndicator());
        assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_RETIRED_VERSION));
        assertTrue(poChange.isPurchaseOrderCurrentIndicator());
        assertFalse(poChange.isPendingActionIndicator());
        assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD));
        //}
    }

    @ConfigureContext(session = kfs, shouldCommitTransactions = true)
    public final void testPurchaseOrderRemoveHold() throws Exception {
        poTest.setApplicationDocumentStatus(PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD);
        poTest.refreshNonUpdateableReferences();
        createAndRoutePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_REMOVE_HOLD_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_PENDING_REMOVE_HOLD);
        assertMatchChangePO(poTest, poChange); /*
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_PENDING_REMOVE_HOLD));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        }
        WorkflowTestUtils.waitForStatusChange(poChange.getDocumentHeader().getWorkflowDocument(), DocumentStatus.FINAL);
        refreshPO();
        if (poChange.getDocumentHeader().getWorkflowDocument().isFinal()) { */
        assertFalse(poTest.isPurchaseOrderCurrentIndicator());
        assertFalse(poTest.isPendingActionIndicator());
        assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_RETIRED_VERSION));
        assertTrue(poChange.isPurchaseOrderCurrentIndicator());
        assertFalse(poChange.isPendingActionIndicator());
        assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_OPEN));
        //}
    }

    @ConfigureContext(session = parke, shouldCommitTransactions = true)
    public final void testPurchaseOrderReopen() throws Exception {
        poTest.setApplicationDocumentStatus(PurchaseOrderStatuses.APPDOC_CLOSED);
        poTest.refreshNonUpdateableReferences();
        createAndRoutePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_PENDING_REOPEN);
        assertMatchChangePO(poTest, poChange); /*
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_PENDING_REOPEN));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        }
        WorkflowTestUtils.waitForStatusChange(poChange.getDocumentHeader().getWorkflowDocument(), DocumentStatus.FINAL);
        refreshPO();
        if (poChange.getDocumentHeader().getWorkflowDocument().isFinal()) { */
        assertFalse(poTest.isPurchaseOrderCurrentIndicator());
        assertFalse(poTest.isPendingActionIndicator());
        assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_RETIRED_VERSION));
        assertTrue(poChange.isPurchaseOrderCurrentIndicator());
        assertFalse(poChange.isPendingActionIndicator());
        assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_OPEN));
        //}
    }

    @ConfigureContext(session = parke, shouldCommitTransactions = true)
    public final void testPurchaseOrderVoid() throws Exception {
        createAndRoutePOChangeDocument(
            PurchaseOrderDocTypes.PURCHASE_ORDER_VOID_DOCUMENT,
            PurchaseOrderStatuses.APPDOC_PENDING_VOID);
        assertMatchChangePO(poTest, poChange); /*
        if (!poChange.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
            assertTrue(poTest.isPurchaseOrderCurrentIndicator());
            assertTrue(poTest.isPendingActionIndicator());
            assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_PENDING_VOID));
            assertFalse(poChange.isPurchaseOrderCurrentIndicator());
            assertFalse(poChange.isPendingActionIndicator());
            assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS));
        }
        WorkflowTestUtils.waitForStatusChange(poChange.getDocumentHeader().getWorkflowDocument(), DocumentStatus.FINAL);
        refreshPO();
        if (poChange.getDocumentHeader().getWorkflowDocument().isFinal()) { */
        assertFalse(poTest.isPurchaseOrderCurrentIndicator());
        assertFalse(poTest.isPendingActionIndicator());
        assertTrue(poTest.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_RETIRED_VERSION));
        assertTrue(poChange.isPurchaseOrderCurrentIndicator());
        assertFalse(poChange.isPendingActionIndicator());
        assertTrue(poChange.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_VOID));
        //}
    }

    /**
     * Matches an existing Purchase Order Document with a change PO Document newly generated from it;
     * Fails the assertion if any of the copied persistent fields don't match.
     */
    public static void assertMatchChangePO(PurchaseOrderDocument doc1, PurchaseOrderDocument doc2) {
        // match posting year
        if (StringUtils.isNotBlank(doc1.getPostingPeriodCode()) && StringUtils.isNotBlank(doc2.getPostingPeriodCode())) {
            Assert.assertEquals(doc1.getPostingPeriodCode(), doc2.getPostingPeriodCode());
        }
        Assert.assertEquals(doc1.getPostingYear(), doc2.getPostingYear());

        // match important fields in PO
        Assert.assertEquals(doc1.getVendorHeaderGeneratedIdentifier(), doc2.getVendorHeaderGeneratedIdentifier());
        Assert.assertEquals(doc1.getVendorDetailAssignedIdentifier(), doc2.getVendorDetailAssignedIdentifier());
        Assert.assertEquals(doc1.getVendorName(), doc2.getVendorName());
        Assert.assertEquals(doc1.getVendorNumber(), doc2.getVendorNumber());

        Assert.assertEquals(doc1.getChartOfAccountsCode(), doc2.getChartOfAccountsCode());
        Assert.assertEquals(doc1.getOrganizationCode(), doc2.getOrganizationCode());
        Assert.assertEquals(doc1.getDeliveryCampusCode(), doc2.getDeliveryCampusCode());
        Assert.assertEquals(doc1.getDeliveryRequiredDate(), doc2.getDeliveryRequiredDate());
        Assert.assertEquals(doc1.getRequestorPersonName(), doc2.getRequestorPersonName());
        Assert.assertEquals(doc1.getContractManagerCode(), doc2.getContractManagerCode());
        Assert.assertEquals(doc1.getVendorContractName(), doc2.getVendorContractName());
        Assert.assertEquals(doc1.getPurchaseOrderAutomaticIndicator(), doc2.getPurchaseOrderAutomaticIndicator());
        Assert.assertEquals(doc1.getPurchaseOrderTransmissionMethodCode(), doc2.getPurchaseOrderTransmissionMethodCode());

        Assert.assertEquals(doc1.getRequisitionIdentifier(), doc2.getRequisitionIdentifier());
        Assert.assertEquals(doc1.getPurchaseOrderPreviousIdentifier(), doc2.getPurchaseOrderPreviousIdentifier());
        Assert.assertEquals(doc1.getPurchaseOrderCreateTimestamp(), doc2.getPurchaseOrderCreateTimestamp());
        Assert.assertEquals(doc1.getPurchaseOrderLastTransmitTimestamp(), doc2.getPurchaseOrderLastTransmitTimestamp());
    }


}

