/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2017 Kuali, Inc.
 *
 * 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.gl.businessobject.lookup;

import org.kuali.kfs.gl.Constant;
import org.kuali.kfs.gl.businessobject.AccountBalance;
import org.kuali.kfs.gl.businessobject.AccountBalanceByConsolidation;
import org.kuali.kfs.gl.service.AccountBalanceService;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.lookup.LookupableSpringContext;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.context.TestUtils;

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

/**
 * This class contains the test cases that can be applied to the method in AccountBalanceLookupableImpl class.
 */
@ConfigureContext
public class AccountBalanceLookupableHelperServiceTest extends AbstractGeneralLedgerLookupableHelperServiceTestBase {

    private AccountBalanceService accountBalanceService;

    /**
     * Initializes the services needed for this test
     *
     * @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#setUp()
     */
    @Override
    protected void setUp() throws Exception {
        super.setUp();

        setAccountBalanceService(SpringContext.getBean(AccountBalanceService.class));
        lookupableHelperServiceImpl = LookupableSpringContext.getLookupableHelperService("glAccountBalanceLookupableHelperService");
        lookupableHelperServiceImpl.setBusinessObjectClass(AccountBalanceByConsolidation.class);
    }

    /**
     * Covers the search results returned by AccountBalanceLookupableService
     *
     * @throws Exception thrown if any exception is encountered for any reason
     * @see org.kuali.module.gl.web.lookupable.AbstractGLLookupableTestBase#testGetSearchResults()
     */
    public void testGetSearchResults() throws Exception {
        testDataGenerator.generateTransactionData(pendingEntry);
        AccountBalance accountBalance = new AccountBalance(pendingEntry);

        // test the search results before the specified entry is inserted into the database
        Map fieldValues = getLookupFieldValues(accountBalance, true);
        List searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        assertTrue(!contains(searchResults, accountBalance));

        // add a new entry into database. If complete this step, such entry should not in the database
        insertNewRecord(accountBalance);

        // test the search results with only the required fields
        fieldValues = getLookupFieldValues(accountBalance, true);
        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        assertTrue(searchResults.size() == 1);
        assertTrue(contains(searchResults, accountBalance));

        // add a new entry into database. If complete this step, such entry should not in the database
        // Here, we can reuse encumbrance declared above
        fieldValues = getLookupFieldValues(accountBalance, true);
        AccountBalance accountBalance1 = new AccountBalance(pendingEntry);
        accountBalance1.setSubAccountNumber(testDataGenerator.getPropertyValue("genericSubAccountNumber"));

        insertNewRecord(accountBalance1);

        // test the search results with all specified fields. The new record cannot meet the search criteria.
        fieldValues = getLookupFieldValues(accountBalance, true);
        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        assertTrue(searchResults.size() == 1);
        assertTrue(!contains(searchResults, accountBalance1));

        // test the search results with all specified fields. The new record cannot meet the search criteria.
        fieldValues = getLookupFieldValues(accountBalance, false);
        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        assertTrue(searchResults.size() >= 2);
        assertTrue(contains(searchResults, accountBalance));
        assertTrue(contains(searchResults, accountBalance1));
    }

    /**
     * This method includes the test cases applied to the consolidation option: Consolidate and Detail
     *
     * @throws Exception thrown if any exception is encountered for any reason
     */
    public void testConsolidationOption() throws Exception {
        // ensure the transaction data does not exist in enty table. Otherwise, execption may be raised
        testDataGenerator.generateTransactionData(pendingEntry);
        AccountBalance accountBalanceOne = new AccountBalance(pendingEntry);

        insertNewRecord(accountBalanceOne);

        // get the number of the search results before adding the second record into database
        Map fieldValues = getLookupFieldValues(accountBalanceOne, true);
        fieldValues.put(Constant.CONSOLIDATION_OPTION, Constant.CONSOLIDATION);

        List searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        int numOfFirstResults = searchResults.size();

        String subAccountNumber = testDataGenerator.getPropertyValue("genericSubAccountNumber");
        pendingEntry.setSubAccountNumber(subAccountNumber);
        AccountBalance accountBalanceTwo = new AccountBalance(pendingEntry);

        insertNewRecord(accountBalanceTwo);

        // test if the second record is consolidated with others
        fieldValues = getLookupFieldValues(accountBalanceOne, true);
        fieldValues.put(Constant.CONSOLIDATION_OPTION, Constant.CONSOLIDATION);

        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        int numOfSecondResults = searchResults.size();
        assertTrue(numOfSecondResults == numOfFirstResults);

        // test if the search results appear in details
        fieldValues = getLookupFieldValues(accountBalanceOne, false);
        fieldValues.put(Constant.CONSOLIDATION_OPTION, Constant.DETAIL);

        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        int numOfThirdResults = searchResults.size();
        assertTrue(numOfSecondResults < numOfThirdResults);
    }

    /**
     * This method includes the test cases applied to the consolidation option: Consolidate and Detail
     *
     * @throws Exception thrown if any exception is encountered for any reason
     */
    public void testPerformance() throws Exception {
        long threshlod = 60000;

        // get the number of the search results before adding the second record into database
        AccountBalance accountBalance = new AccountBalance();
        accountBalance.setAccountNumber("1031400");
        accountBalance.setUniversityFiscalYear(TestUtils.getFiscalYearForTesting());
        accountBalance.setChartOfAccountsCode("BL");

        Map fieldValues = getLookupFieldValues(accountBalance, true);
        fieldValues.put(Constant.CONSOLIDATION_OPTION, Constant.CONSOLIDATION);

        long startTime = System.currentTimeMillis();
        List searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;

        System.out.println(duration + "ms");
        assertTrue("Too slow", duration < threshlod);

        // test if the search results appear in details
        fieldValues = getLookupFieldValues(accountBalance, false);
        fieldValues.put(Constant.CONSOLIDATION_OPTION, Constant.DETAIL);

        startTime = System.currentTimeMillis();
        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
        endTime = System.currentTimeMillis();
        duration = endTime - startTime;

        System.out.println(duration + "ms");
        assertTrue("Too slow", duration < threshlod);
    }

    /**
     * Returns a List of field names to check in the search results
     *
     * @param isExtended true if extended attributes should be included for checking, false otherwise
     * @return a List of field names to check
     * @see org.kuali.module.gl.web.lookupable.AbstractGLLookupableTestBase#getLookupFields(boolean)
     */
    public List getLookupFields(boolean isExtended) {
        List lookupFields = new ArrayList();

        lookupFields.add(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR);
        lookupFields.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
        lookupFields.add(KFSPropertyConstants.ACCOUNT_NUMBER);
        lookupFields.add(Constant.CONSOLIDATION_OPTION);

        // include the extended fields
        if (isExtended) {
            lookupFields.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER);
            lookupFields.add(KFSPropertyConstants.OBJECT_CODE);
            lookupFields.add(KFSPropertyConstants.SUB_OBJECT_CODE);
        }
        return lookupFields;
    }


    /**
     * This method inserts a new account balance record into database
     *
     * @param accounBalance the given account balance
     */
    protected void insertNewRecord(AccountBalance accounBalance) {
        try {
            getAccountBalanceService().save(accounBalance);
        } catch (Exception e) {
        }
    }

    /**
     * Gets the accountBalanceService attribute.
     *
     * @return Returns the accountBalanceService.
     */
    public AccountBalanceService getAccountBalanceService() {
        return accountBalanceService;
    }

    /**
     * Sets the accountBalanceService attribute value.
     *
     * @param accountBalanceService The accountBalanceService to set.
     */
    public void setAccountBalanceService(AccountBalanceService accountBalanceService) {
        this.accountBalanceService = accountBalanceService;
    }
}
