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

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.integration.ar.AccountsReceivableCustomer;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.cg.CGConstants;
import org.kuali.kfs.module.cg.CGKeyConstants;
import org.kuali.kfs.module.cg.CGPropertyConstants;
import org.kuali.kfs.module.cg.businessobject.Agency;
import org.kuali.kfs.module.cg.businessobject.AgencyAddress;
import org.kuali.kfs.module.cg.businessobject.AgencyType;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;

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

/**
 * Rules for processing Agency instances.
 */
public class AgencyRule extends CGMaintenanceDocumentRuleBase {
    protected static Logger LOG = org.apache.log4j.Logger.getLogger(AgencyRule.class);

    protected Agency newAgency;
    protected Agency oldAgency;

    BusinessObjectService businessObjectService;

    /**
     * Default constructor.
     */
    public AgencyRule() {
        super();
        businessObjectService = SpringContext.getBean(BusinessObjectService.class);
    }

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
        LOG.debug("Entering AgencyRule.processCustomApproveDocumentBusinessRules");
        boolean success = super.processCustomApproveDocumentBusinessRules(document);

        success &= checkAgencyReportsTo(document);

        LOG.info("Leaving AgencyRule.processCustomApproveDocumentBusinessRules");
        return success;
    }

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
        LOG.debug("Entering AgencyRule.processCustomRouteDocumentBusinessRules");
        boolean success = super.processCustomRouteDocumentBusinessRules(document);

        success &= checkAgencyReportsTo(document);

        // Only do further custom Contracts & Grants Billing validations for route document, if the enhancements are active
        if (contractsGrantsBillingEnhancementActive) {
            // There must be at least one primary Agency Address in Agency
            success &= checkPrimary(newAgency.getAgencyAddresses(), AgencyAddress.class, KFSPropertyConstants.AGENCY_ADDRESSES, Agency.class);
            success &= validateAddresses(newAgency);

            // Make sure new agency customers have a Customer Type
            success &= validateCustomerType(newAgency);
        }

        LOG.info("Leaving AgencyRule.processCustomRouteDocumentBusinessRules");
        return success;
    }

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
        LOG.debug("Entering AgencyRule.processCustomSaveDocumentBusinessRules");
        boolean success = super.processCustomSaveDocumentBusinessRules(document);

        success &= checkAgencyReportsTo(document);
        success &= validateAgencyType(document);
        success &= validateAgencyReportingName(document);

        LOG.info("Leaving AgencyRule.processCustomSaveDocumentBusinessRules");
        return success;
    }

    /**
     * @param document
     * @return
     */
    protected boolean validateAgencyType(MaintenanceDocument document) {
        String agencyType = newAgency.getAgencyTypeCode();
        Map params = new HashMap();
        params.put("code", agencyType);
        Object o = businessObjectService.findByPrimaryKey(AgencyType.class, params);
        if (null == o) {
            putFieldError("agencyTypeCode", KFSKeyConstants.ERROR_AGENCY_TYPE_NOT_FOUND, agencyType);
            return false;
        }
        return true;
    }

    /**
     * @param document
     * @return
     */
    protected boolean validateAgencyReportingName(MaintenanceDocument document) {
        String agencyReportingName = newAgency.getReportingName();
        String agencyExistsValue = newAgency.getCustomerCreationOptionCode();
        if (CGConstants.AGENCY_CREATE_NEW_CUSTOMER_CODE.equalsIgnoreCase(agencyExistsValue)) {
            if (agencyReportingName.length() < 3) {
                putFieldError("reportingName", CGKeyConstants.AgencyConstants.ERROR_AGENCY_NAME_LESS_THAN_THREE_CHARACTERS);
                return false;
            } else if (agencyReportingName.substring(0, 3).contains(" ")) {
                putFieldError("reportingName", CGKeyConstants.AgencyConstants.ERROR_AGENCY_NAME_NO_SPACES_IN_FIRST_THREE_CHARACTERS);
                return false;
            }
        }
        return true;
    }

    /**
     * @param document
     * @return
     */
    protected boolean checkAgencyReportsTo(MaintenanceDocument document) {
        boolean success = true;

        if (newAgency.getReportsToAgencyNumber() != null) {
            if (newAgency.getReportsToAgency() == null) { // Agency must exist

                putFieldError("reportsToAgencyNumber", KFSKeyConstants.ERROR_AGENCY_NOT_FOUND, newAgency.getReportsToAgencyNumber());
                success = false;

            } else if (!newAgency.getReportsToAgency().isActive()) { // Agency must be active. See KULCG-263

                putFieldError("reportsToAgencyNumber", KFSKeyConstants.ERROR_AGENCY_INACTIVE, newAgency.getReportsToAgencyNumber());
                success = false;

            } else if (newAgency.getAgencyNumber().equals(newAgency.getReportsToAgencyNumber())) {

                putFieldError("reportsToAgencyNumber", KFSKeyConstants.ERROR_AGENCY_REPORTS_TO_SELF, newAgency.getAgencyNumber());
                success = false;

            } else { // No circular references

                List agencies = new ArrayList();

                Agency agency = newAgency;

                while (agency.getReportsToAgency() != null && success) {
                    if (!agencies.contains(agency.getAgencyNumber())) {
                        agencies.add(agency.getAgencyNumber());
                    } else {

                        putFieldError("reportsToAgencyNumber", KFSKeyConstants.ERROR_AGENCY_CIRCULAR_REPORTING, agency.getAgencyNumber());
                        success = false;
                    }

                    agency = agency.getReportsToAgency();
                }
            }
        }
        return success;
    }

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#setupConvenienceObjects()
     */
    @Override
    public void setupConvenienceObjects() {
        newAgency = (Agency) super.getNewBo();
        oldAgency = (Agency) super.getOldBo();
    }


    /**
     * Overrides the method in MaintenanceDocumentRuleBase to give error message to the user when the user tries to add multiple
     * Primary Agency Addresses. At most one Primary Agency Address is allowed. contract.
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
     * java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
     */
    @Override
    public boolean processAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) {

        // Only do if the CG Billing enhancements are turned on
        if (contractsGrantsBillingEnhancementActive) {
            if (line instanceof AgencyAddress) {
                AgencyAddress newAgencyAddress = (AgencyAddress) line;
                if (collectionName.equals(KFSPropertyConstants.AGENCY_ADDRESSES)) {
                    newAgency = (Agency) document.getNewMaintainableObject().getBusinessObject();
                    List<AgencyAddress> agencyAddresses = newAgency.getAgencyAddresses();
                    String tmpCode = newAgencyAddress.getCustomerAddressTypeCode();

                    // Check if there is an Agency Primary Address in the collection lines or not.
                    for (AgencyAddress agencyAddress : agencyAddresses) {
                        String customerAddressTypeCode = agencyAddress.getCustomerAddressTypeCode();
                        if (customerAddressTypeCode != null && customerAddressTypeCode.equals(CGConstants.AGENCY_PRIMARY_ADDRESSES_TYPE_CODE)) {
                            if (ObjectUtils.isNotNull(tmpCode) && !tmpCode.isEmpty() && tmpCode.equals(CGConstants.AGENCY_PRIMARY_ADDRESSES_TYPE_CODE)) {
                                String elementLabel = SpringContext.getBean(DataDictionaryService.class).getCollectionElementLabel(Agency.class.getName(), collectionName, AgencyAddress.class);
                                putFieldError(collectionName, KFSKeyConstants.ERROR_MULTIPLE_PRIMARY, elementLabel);
                                return false;
                            } else {
                                boolean isValid = checkAddressIsValid(newAgencyAddress);
                                if (!isValid) {
                                    return isValid;
                                }
                            }
                        }
                    }
                }
            }

        }

        return super.processAddCollectionLineBusinessRules(document, collectionName, line);
    }

    /**
     * This method validates the addresses when Customer is created from Agency
     *
     * @param agencyAddress
     * @return
     */
    public boolean checkAddressIsValid(AgencyAddress agencyAddress) {
        boolean isValid = true;
        // To validate only if the Agency agency exists field says "Create New Customer".
        if (CGConstants.AGENCY_CREATE_NEW_CUSTOMER_CODE.equalsIgnoreCase(newAgency.getCustomerCreationOptionCode())) {
            if (CGKeyConstants.AgencyConstants.AGENCY_ADDRESS_TYPE_CODE_US.equalsIgnoreCase(agencyAddress.getAgencyCountryCode())) {
                if (StringUtils.isBlank(agencyAddress.getAgencyZipCode())) {
                    isValid = false;
                    GlobalVariables.getMessageMap().putError(CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_ZIP_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_ZIP_CODE_REQUIRED_WHEN_COUNTTRY_US);
                }
                if (StringUtils.isBlank(agencyAddress.getAgencyStateCode())) {
                    isValid = false;
                    GlobalVariables.getMessageMap().putError(CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_STATE_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_STATE_CODE_REQUIRED_WHEN_COUNTTRY_US);
                }
            } else {
                if (StringUtils.isBlank(agencyAddress.getAgencyInternationalMailCode())) {
                    isValid = false;
                    GlobalVariables.getMessageMap().putError(CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_INTERNATIONAL_MAIL_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_INTERNATIONAL_MAIL_CODE_REQUIRED_WHEN_COUNTTRY_NON_US);
                }
                if (StringUtils.isBlank(agencyAddress.getAgencyAddressInternationalProvinceName())) {
                    isValid = false;
                    GlobalVariables.getMessageMap().putError(CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_INTERNATIONAL_PROVINCE_NAME, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_INTERNATIONAL_PROVINCE_NAME_REQUIRED_WHEN_COUNTTRY_NON_US);
                }
            }
        }
        return isValid;
    }

    /**
     * This method validates the addresses when Customer is created from Agency
     *
     * @param agencyAddress
     * @param ind
     * @return
     */
    public boolean checkAddressIsValid(AgencyAddress agencyAddress, int ind) {
        boolean isValid = true;
        String propertyName = CGPropertyConstants.AgencyFields.AGENCY_TAB_ADDRESSES + "[" + ind + "].";
        // To validate only if the Agency agencyexists field says "Create New Customer".
        if (CGConstants.AGENCY_CREATE_NEW_CUSTOMER_CODE.equalsIgnoreCase(newAgency.getCustomerCreationOptionCode())) {
            if (CGKeyConstants.AgencyConstants.AGENCY_ADDRESS_TYPE_CODE_US.equalsIgnoreCase(agencyAddress.getAgencyCountryCode())) {
                if (StringUtils.isBlank(agencyAddress.getAgencyZipCode())) {
                    isValid = false;
                    putFieldError(propertyName + CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_ZIP_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_ZIP_CODE_REQUIRED_WHEN_COUNTTRY_US);
                }
                if (StringUtils.isBlank(agencyAddress.getAgencyStateCode())) {
                    isValid = false;
                    putFieldError(propertyName + CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_STATE_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_STATE_CODE_REQUIRED_WHEN_COUNTTRY_US);
                }
            } else {
                if (StringUtils.isBlank(agencyAddress.getAgencyInternationalMailCode())) {
                    isValid = false;
                    putFieldError(propertyName + CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_INTERNATIONAL_MAIL_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_INTERNATIONAL_MAIL_CODE_REQUIRED_WHEN_COUNTTRY_NON_US);
                }
                if (StringUtils.isBlank(agencyAddress.getAgencyAddressInternationalProvinceName())) {
                    isValid = false;
                    putFieldError(propertyName + CGPropertyConstants.AgencyFields.AGENCY_ADDRESS_INTERNATIONAL_PROVINCE_NAME, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ADDRESS_INTERNATIONAL_PROVINCE_NAME_REQUIRED_WHEN_COUNTTRY_NON_US);
                }
            }
        }
        return isValid;
    }


    /**
     * This method validates the addresses when Customer is created from Agency
     *
     * @param agency
     * @return
     */
    public boolean validateAddresses(Agency agency) {
        boolean isValid = true;
        int i = 0;

        for (AgencyAddress agencyAddress : agency.getAgencyAddresses()) {
            isValid &= checkAddressIsValid(agencyAddress, i);
            i++;
        }


        return isValid;
    }

    /**
     * This method validates that a customer type is selected when the create new customer option is selected, and that if existing customer option
     * is selected that customer number is filled in with an existing customer.
     *
     * @param agency agency record to verify
     * @return true if agency passes verification, false otherwise
     */
    public boolean validateCustomerType(Agency agency) {
        boolean isValid = true;

        // Only validate if new customer create option is selected
        if (StringUtils.equalsIgnoreCase(CGConstants.AGENCY_CREATE_NEW_CUSTOMER_CODE, agency.getCustomerCreationOptionCode())) {
            // Customer Type must be filled-in
            if (StringUtils.isEmpty(agency.getCustomerTypeCode())) {
                putFieldError(CGPropertyConstants.AgencyFields.AGENCY_CUSTOMER_TYPE_CODE, CGKeyConstants.AgencyConstants.ERROR_AGENCY_CUSTOMER_TYPE_CODE_REQUIRED_WHEN_AGENCY_CUSTOMER_NEW);
                isValid &= false;
            }
        } else if (StringUtils.equalsIgnoreCase(CGConstants.AGENCY_USE_EXISTING_CUSTOMER_CODE, agency.getCustomerCreationOptionCode())) {
            if (StringUtils.isBlank(agency.getCustomerNumber())) {
                putFieldError(CGPropertyConstants.AgencyFields.AGENCY_CUSTOMER_NUMBER, CGKeyConstants.AgencyConstants.ERROR_AGECNY_CUSTOMER_NUMBER_REQUIRED_WHEN_AGENCY_CUSTOMER_EXISTING);
                isValid = false;
            } else {
                final AccountsReceivableCustomer customer = agency.getCustomer();
                if (ObjectUtils.isNull(customer) || !customer.isActive()) {
                    putFieldError(CGPropertyConstants.AgencyFields.AGENCY_CUSTOMER_NUMBER, CGKeyConstants.AgencyConstants.ERROR_AGENCY_ACTUAL_CUSTOMER_REQUIRED_WHEN_AGENCY_CUSTOMER_EXISTING, new String[]{agency.getCustomerNumber()});
                    isValid = false;
                }
            }
        }

        return isValid;
    }
}
