/**
   * This method tests whether the accountNumber passed in is prefixed with an allowed prefix, or an
   * illegal one. The illegal prefixes are passed in as an array of strings.
   *
   * @param accountNumber - The Account Number to be tested.
   * @return false if the accountNumber starts with any of the illegalPrefixes, true otherwise
   */
  protected boolean checkAccountNumberPrefix(
      String accountNumber, AccountCreationStatusDTO accountCreationStatus) {

    boolean success = true;

    // Enforce institutionally specified restrictions on account number prefixes
    // (e.g. the account number cannot begin with a 3 or with 00.)
    // Only bother trying if there is an account string to test
    if (!StringUtils.isBlank(accountNumber)) {

      List<String> illegalValues =
          new ArrayList<String>(
              getParameterService()
                  .getParameterValuesAsString(Account.class, ACCT_PREFIX_RESTRICTION));

      for (String illegalValue : illegalValues) {
        if (accountNumber.startsWith(illegalValue)) {
          success = false;
          setFailStatus(
              accountCreationStatus,
              KcUtils.getErrorMessage(
                  KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_ALLOWED,
                  new String[] {accountNumber, illegalValue}));
        }
      }
    }

    return success;
  }
  /**
   * This method check to see if the user can create the account maintenance document and set the
   * user session
   *
   * @param String principalId
   * @return boolean
   */
  protected boolean isValidUser(String principalId) {

    PersonService personService = SpringContext.getBean(PersonService.class);
    if (principalId == null) {
      return false;
    }
    Person user = personService.getPerson(principalId);
    if (user == null) {
      return false;
    }
    DocumentAuthorizer documentAuthorizer = new MaintenanceDocumentAuthorizerBase();
    if (documentAuthorizer.canInitiate(
        SpringContext.getBean(MaintenanceDocumentDictionaryService.class)
            .getDocumentTypeName(Account.class),
        user)) {
      // set the user session so that the user name can be displayed in the saved document
      GlobalVariables.setUserSession(new UserSession(user.getPrincipalName()));
      return true;
    }

    LOG.error(
        KcUtils.getErrorMessage(
            KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_INVALID_USER,
            new String[] {principalId}));

    return false;
  }
  /**
   * This is the web service method that creates a new account 1. Creates an account object using
   * the parameters from KC and the default Account table 2. Creates an account automatic
   * maintenance document and puts the account object into it 3. Returns the status object
   *
   * @param AccountAutoCreateDefaults
   * @return AccountCreationStatusDTO
   */
  @Override
  public AccountCreationStatusDTO createAccount(AccountParametersDTO accountParameters) {

    AccountCreationStatusDTO accountCreationStatus = new AccountCreationStatusDTO();
    accountCreationStatus.setErrorMessages(new ArrayList<String>());
    accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_SUCCESS);

    // check to see if the user has the permission to create account
    String principalId = accountParameters.getPrincipalId();
    LOG.debug("principalId  ::::" + principalId);
    if (!isValidUser(principalId)) {
      this.setFailStatus(
          accountCreationStatus,
          KcUtils.getErrorMessage(
              KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_INVALID_USER,
              new String[] {principalId}));
      return accountCreationStatus;
    }

    // get the defaults table
    String unitNumber = accountParameters.getUnit();
    AccountAutoCreateDefaults defaults = getAccountDefaultsService().getAccountDefaults(unitNumber);

    if (defaults == null) {
      this.setFailStatus(
          accountCreationStatus,
          KcConstants.AccountCreationService.ERROR_KC_ACCOUNT_PARAMS_UNIT_NOT_DEFINED);
      return accountCreationStatus;
    }

    try {
      // create an account object
      Account account = createAccountObject(accountParameters, defaults);

      // if invalid chart/account number, failure status and return to KC
      if (!isValidAccount(account, accountCreationStatus)) {
        return accountCreationStatus;
      }
      // create an account automatic maintenance document
      createAutomaticCGAccountMaintenanceDocument(account, accountCreationStatus);

    } catch (Exception ex) {
      this.setFailStatus(
          accountCreationStatus,
          KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_ACCOUNT_GENERATION_PROBLEM);
      return accountCreationStatus;
    }

    // set required values to AccountCreationStatus
    if (accountCreationStatus.getStatus().equals(KcConstants.KcWebService.STATUS_KC_SUCCESS)) {
      accountCreationStatus.setAccountNumber(accountParameters.getAccountNumber());
      accountCreationStatus.setChartOfAccountsCode(defaults.getChartOfAccountsCode());
    }

    return accountCreationStatus;
  }
  /**
   * This method looks up the default table
   *
   * @param String unitNumber
   * @return AccountAutoCreateDefaults
   */
  protected AccountAutoCreateDefaults getAccountDefaults(String unitNumber) {

    AccountAutoCreateDefaults defaults = null;

    if (unitNumber == null || unitNumber.isEmpty()) {
      return null;
    }

    Map<String, String> criteria = new HashMap<String, String>();
    criteria.put("kcUnit", unitNumber);
    defaults = businessObjectService.findByPrimaryKey(AccountAutoCreateDefaults.class, criteria);

    // if the matching defaults is null, try the parents in the hierarchy
    if (defaults == null) {

      List<String> parentUnits = null;
      try {
        parentUnits =
            SpringContext.getBean(ContractsAndGrantsModuleService.class).getParentUnits(unitNumber);
      } catch (Exception ex) {
        LOG.error(
            KcUtils.getErrorMessage(
                    KcConstants.AccountCreationService.ERROR_KC_ACCOUNT_PARAMS_UNIT_NOTFOUND, null)
                + ": "
                + ex.getMessage());

        GlobalVariables.getMessageMap()
            .putError(
                KcConstants.AccountCreationService.ERROR_KC_ACCOUNT_PARAMS_UNIT_NOTFOUND,
                "kcUnit",
                ex.getMessage());
      }

      if (parentUnits != null) {
        for (String unit : parentUnits) {
          criteria.put("kcUnit", unit);
          defaults =
              businessObjectService.findByPrimaryKey(AccountAutoCreateDefaults.class, criteria);
          if (defaults != null) {
            break;
          }
        }
      }
    }

    return defaults;
  }
  /**
   * Checks an account numbers exact length
   *
   * @param accountNumber
   * @param size to be returned
   * @return
   */
  protected boolean isValidAccountNumberLength(
      String accountNumber, AccountCreationStatusDTO accountCreationStatus) {

    boolean isValid = false;
    int fieldSize = -1;

    // grab account number length from DD and set size
    final org.kuali.rice.krad.datadictionary.BusinessObjectEntry entry =
        SpringContext.getBean(DataDictionaryService.class)
            .getDataDictionary()
            .getBusinessObjectEntry(Account.class.getName());
    AttributeDefinition attributeDefinition =
        entry.getAttributeDefinition(KFSPropertyConstants.ACCOUNT_NUMBER);

    if (ObjectUtils.isNotNull(attributeDefinition)) {
      final ValidationPattern validationPattern = attributeDefinition.getValidationPattern();

      if (ObjectUtils.isNotNull(validationPattern)
          && validationPattern instanceof AlphaNumericValidationPattern) {
        AlphaNumericValidationPattern alphaPattern =
            (AlphaNumericValidationPattern) validationPattern;
        fieldSize = alphaPattern.getExactLength();
      }
    }

    // skip if account number null
    if (ObjectUtils.isNotNull(accountNumber)) {

      // data dictionary defined size must equal length of incoming value
      if (fieldSize == accountNumber.length()) {
        isValid = true;
      }
    }

    if (isValid == false) {
      setFailStatus(
          accountCreationStatus,
          KcUtils.getErrorMessage(
              KcConstants.AccountCreationService.ERROR_KR_ALPHANUMERIC_VALIDATION_EXACT_LENGTH,
              new String[] {"account number", String.valueOf(fieldSize)}));
    }

    return isValid;
  }
  /**
   * Check to see if the main link between KFS and KC is valid, namely the chart and account number.
   * If these two values have some kind of error, then we don't want to generate an Account document
   * and we'll want to return a failure to KC.
   *
   * @param account
   * @param accountCreationStatus
   * @return
   */
  protected boolean isValidAccount(
      Account account, AccountCreationStatusDTO accountCreationStatus) {
    boolean isValid = true;
    String errorMessage = "";
    String strSize = "";

    if (account == null) {
      // account was not created
      setFailStatus(
          accountCreationStatus,
          KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_ACCOUNT_GENERATION_PROBLEM);
      return false;
    }

    if (StringUtils.isBlank(account.getChartOfAccountsCode())
        || StringUtils.isBlank(account.getAccountNumber())) {
      // chart of accounts or account number blank
      setFailStatus(
          accountCreationStatus,
          KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_ACCOUNT_MISSING_CHART_OR_ACCT_NBR);
      return false;
    }

    if (!isValidChartCode(account.getChartOfAccountsCode())) {
      // the chart of accounts code is not valid
      setFailStatus(
          accountCreationStatus,
          KcConstants.AccountCreationService.AUTOMATCICG_ACCOUNT_MAINTENANCE_CHART_NOT_DEFINED);
      return false;
    }

    if (!isValidAccountNumberLength(account.getAccountNumber(), accountCreationStatus)) {
      // the account number is an inappropriate length
      // error set in method
      return false;
    }

    if (!checkUniqueAccountNumber(account.getAccountNumber())) {
      // account is not unique
      setFailStatus(
          accountCreationStatus,
          KcUtils.getErrorMessage(
              KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_UNIQUE,
              new String[] {account.getAccountNumber()}));
      return false;
    }

    if (isValidChartAccount(account.getChartOfAccountsCode(), account.getAccountNumber())) {
      // the chart and account already exist
      setFailStatus(
          accountCreationStatus,
          KcConstants.AccountCreationService.AUTOMATCICG_ACCOUNT_MAINTENANCE_ACCT_ALREADY_DEFINED);
      return false;
    }

    if (!checkAccountNumberPrefix(account.getAccountNumber(), accountCreationStatus)) {
      // account begins with invalid prefix
      // error set in method
      return false;
    }

    return isValid;
  }
  /**
   * This method processes the workflow document actions like save, route and blanket approve
   * depending on the ACCOUNT_AUTO_CREATE_ROUTE system parameter value. If the system parameter
   * value is not of save or submit or blanketapprove, put an error message and quit. Throws an
   * document WorkflowException if the specific document action fails to perform.
   *
   * @param maintenanceAccountDocument, errorMessages
   * @return
   */
  protected void createRouteAutomaticCGAccountDocument(
      MaintenanceDocument maintenanceAccountDocument,
      AccountCreationStatusDTO accountCreationStatus) {

    try {
      String accountAutoCreateRouteValue =
          getParameterService()
              .getParameterValueAsString(
                  Account.class,
                  KcConstants.AccountCreationService
                      .PARAMETER_KC_ACCOUNT_ADMIN_AUTO_CREATE_ACCOUNT_WORKFLOW_ACTION);

      // if the accountAutoCreateRouteValue is not save or submit or blanketApprove then put an
      // error message and quit.
      if (!accountAutoCreateRouteValue.equalsIgnoreCase(KFSConstants.WORKFLOW_DOCUMENT_SAVE)
          && !accountAutoCreateRouteValue.equalsIgnoreCase("submit")
          && !accountAutoCreateRouteValue.equalsIgnoreCase(
              KFSConstants.WORKFLOW_DOCUMENT_BLANKET_APPROVE)) {
        this.setFailStatus(
            accountCreationStatus,
            KcConstants.AccountCreationService
                .ERROR_KC_DOCUMENT_SYSTEM_PARAMETER_INCORRECT_DOCUMENT_ACTION_VALUE);
        LOG.error(
            "Incorrect document status::::: "
                + accountCreationStatus.getErrorMessages().toString());
        return;
      }

      if (accountAutoCreateRouteValue.equalsIgnoreCase(KFSConstants.WORKFLOW_DOCUMENT_SAVE)) {

        // attempt to save if apply rules were successful and there are no errors
        boolean rulesPassed =
            SpringContext.getBean(KualiRuleService.class)
                .applyRules(new SaveDocumentEvent(maintenanceAccountDocument));
        LOG.debug(
            "global variable messages :::  "
                + GlobalVariables.getMessageMap().getErrorMessages().toString());
        if (rulesPassed && GlobalVariables.getMessageMap().hasNoErrors()) {
          getDocumentService().saveDocument(maintenanceAccountDocument);
        } else {
          // get errors from apply rules invocation, also clears global variables
          LOG.info("rule fail formatting errors messages ");
          accountCreationStatus.setErrorMessages(
              GlobalVariablesExtractHelper.extractGlobalVariableErrors());
          try {
            // save document, and catch VE's as we want to do this silently
            getDocumentService().saveDocument(maintenanceAccountDocument);
          } catch (ValidationException ve) {
          }

          accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_SUCCESS);
          LOG.error(
              KcUtils.getErrorMessage(
                  KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_ACCOUNT_RULES_EXCEPTION,
                  new String[] {maintenanceAccountDocument.getDocumentNumber()}));
        }

      } else if (accountAutoCreateRouteValue.equalsIgnoreCase(
          KFSConstants.WORKFLOW_DOCUMENT_BLANKET_APPROVE)) {

        // attempt to blanket approve if apply rules were successful and there are no errors
        boolean rulesPassed =
            SpringContext.getBean(KualiRuleService.class)
                .applyRules(new BlanketApproveDocumentEvent(maintenanceAccountDocument));

        if (rulesPassed && GlobalVariables.getMessageMap().hasNoErrors()) {
          getDocumentService().blanketApproveDocument(maintenanceAccountDocument, "", null);
        } else {
          // get errors from apply rules invocation, also clears global variables
          accountCreationStatus.setErrorMessages(
              GlobalVariablesExtractHelper.extractGlobalVariableErrors());
          try {
            // save document, and catch VE's as we want to do this silently
            getDocumentService().saveDocument(maintenanceAccountDocument);
          } catch (ValidationException ve) {
          }

          accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_SUCCESS);
          LOG.error(
              KcUtils.getErrorMessage(
                  KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_ACCOUNT_RULES_EXCEPTION,
                  new String[] {maintenanceAccountDocument.getDocumentNumber()}));
        }

      } else if (accountAutoCreateRouteValue.equalsIgnoreCase("submit")) {

        // attempt to route if apply rules were successful and there are no errors
        boolean rulesPassed =
            SpringContext.getBean(KualiRuleService.class)
                .applyRules(new RouteDocumentEvent(maintenanceAccountDocument));

        if (rulesPassed && GlobalVariables.getMessageMap().hasNoErrors()) {
          getDocumentService().routeDocument(maintenanceAccountDocument, "", null);
        } else {
          // get errors from apply rules invocation, also clears global variables
          accountCreationStatus.setErrorMessages(
              GlobalVariablesExtractHelper.extractGlobalVariableErrors());
          try {
            // save document, and catch VE's as we want to do this silently
            getDocumentService().saveDocument(maintenanceAccountDocument);
          } catch (ValidationException ve) {
          }

          accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_SUCCESS);
          LOG.error(
              KcUtils.getErrorMessage(
                  KcConstants.AccountCreationService.ERROR_KC_DOCUMENT_ACCOUNT_RULES_EXCEPTION,
                  new String[] {maintenanceAccountDocument.getDocumentNumber()}));
        }
      }

      // set the document number
      accountCreationStatus.setDocumentNumber(maintenanceAccountDocument.getDocumentNumber());

    } catch (WorkflowException wfe) {

      LOG.error(
          KcUtils.getErrorMessage(
                  KcConstants.AccountCreationService
                      .ERROR_KC_DOCUMENT_WORKFLOW_EXCEPTION_DOCUMENT_ACTIONS,
                  null)
              + ": "
              + wfe.getMessage());
      accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_FAILURE);
      accountCreationStatus
          .getErrorMessages()
          .add(
              KcUtils.getErrorMessage(
                      KcConstants.AccountCreationService
                          .WARNING_KC_DOCUMENT_WORKFLOW_EXCEPTION_DOCUMENT_ACTIONS,
                      null)
                  + ": "
                  + wfe.getMessage());

      try {
        // save it even though it fails to route or blanket approve the document
        try {
          getDocumentService().saveDocument(maintenanceAccountDocument);
        } catch (ValidationException ve) {
          // ok to catch validation exceptions at this point
        }
        accountCreationStatus.setDocumentNumber(maintenanceAccountDocument.getDocumentNumber());
        accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_SUCCESS);
      } catch (WorkflowException e) {
        LOG.error(
            KcUtils.getErrorMessage(
                    KcConstants.AccountCreationService
                        .WARNING_KC_DOCUMENT_WORKFLOW_EXCEPTION_DOCUMENT_ACTIONS,
                    null)
                + ": "
                + e.getMessage());
        accountCreationStatus.setErrorMessages(
            GlobalVariablesExtractHelper.extractGlobalVariableErrors());
        accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_FAILURE);
      }

    } catch (Exception ex) {

      LOG.error("Unknown exception occurred: " + ex.getMessage());
      accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_FAILURE);
      accountCreationStatus.setErrorMessages(
          GlobalVariablesExtractHelper.extractGlobalVariableErrors());
      accountCreationStatus
          .getErrorMessages()
          .add(
              KcUtils.getErrorMessage(
                      KcConstants.AccountCreationService
                          .WARNING_KC_DOCUMENT_WORKFLOW_EXCEPTION_DOCUMENT_ACTIONS,
                      null)
                  + ": "
                  + ex.getMessage());

      try {
        // save it even though it fails to route or blanket approve the document
        try {
          getDocumentService().saveDocument(maintenanceAccountDocument);
        } catch (ValidationException ve) {
          // ok to catch validation exceptions at this point
        }
        accountCreationStatus.setDocumentNumber(maintenanceAccountDocument.getDocumentNumber());
        accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_SUCCESS);
      } catch (WorkflowException e) {
        LOG.error(
            KcUtils.getErrorMessage(
                    KcConstants.AccountCreationService
                        .WARNING_KC_DOCUMENT_WORKFLOW_EXCEPTION_DOCUMENT_ACTIONS,
                    null)
                + ": "
                + e.getMessage());
        accountCreationStatus.setErrorMessages(
            GlobalVariablesExtractHelper.extractGlobalVariableErrors());
        accountCreationStatus.setStatus(KcConstants.KcWebService.STATUS_KC_FAILURE);
      }
    }
  }