/**
 * Copyright 2005-2018 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.kim.document.rule;

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

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.uif.RemotableAttributeError;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
import org.kuali.rice.coreservice.api.namespace.Namespace;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.group.Group;
import org.kuali.rice.kim.api.identity.IdentityService;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kim.api.type.KimType;
import org.kuali.rice.kim.bo.ui.GroupDocumentMember;
import org.kuali.rice.kim.bo.ui.GroupDocumentQualifier;
import org.kuali.rice.kim.document.IdentityManagementGroupDocument;
import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
import org.kuali.rice.kim.framework.type.KimTypeService;
import org.kuali.rice.kim.rule.event.ui.AddGroupMemberEvent;
import org.kuali.rice.kim.rule.ui.AddGroupMemberRule;
import org.kuali.rice.kim.rules.ui.GroupDocumentMemberRule;
import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.MessageMap;

/**
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
public class IdentityManagementGroupDocumentRule extends TransactionalDocumentRuleBase implements AddGroupMemberRule {

	protected AddGroupMemberRule addGroupMemberRule;
	protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();
	
	protected Class<? extends GroupDocumentMemberRule> addGroupMemberRuleClass = GroupDocumentMemberRule.class;

	protected IdentityService identityService; 
	
    public IdentityService getIdentityService() {
        if ( identityService == null) {
            identityService = KimApiServiceLocator.getIdentityService();
        }
        return identityService;
    }

    @Override
    protected boolean processCustomSaveDocumentBusinessRules(Document document) {
        if (!(document instanceof IdentityManagementGroupDocument)) {
            return false;
        }

        IdentityManagementGroupDocument groupDoc = (IdentityManagementGroupDocument)document;

        boolean valid = true;
        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        valid &= validAssignGroup(groupDoc);
        valid &= validDuplicateGroupName(groupDoc);
        getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document, getMaxDictionaryValidationDepth(), true, false);
        valid &= validateGroupQualifier(groupDoc.getQualifiers(), groupDoc.getKimType());
        valid &= validGroupMemberActiveDates(groupDoc.getMembers());
        //KULRICE-6858 Validate group members are in identity system
        valid &= validGroupMemberPrincipalIDs(groupDoc.getMembers());
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);

        return valid;
    }

    //Added method to validate existence of a group namespace on a submitted IdentityManagementGroupDocument
    protected boolean validGroupNamespace(IdentityManagementGroupDocument document){
        boolean rulePassed = true;
        String namespacePassed = document.getGroupNamespace();
        if(StringUtils.isNotBlank(namespacePassed)){
            rulePassed = true;
            Namespace matchedNamespace = CoreServiceApiServiceLocator.getNamespaceService().getNamespace(namespacePassed);
            if(matchedNamespace == null){
                rulePassed = false;
            }
        }else{
            rulePassed = false;
        }
        if(!rulePassed){
            GlobalVariables.getMessageMap().putError("document.groupNamespace",
                    RiceKeyConstants.ERROR_REQUIRED,
                    new String[] {"Group Namespace"});
        }
        return rulePassed;
    }

	protected boolean validAssignGroup(IdentityManagementGroupDocument document){
        boolean rulePassed = true;
        Map<String,String> additionalPermissionDetails = new HashMap<String,String>();
        additionalPermissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE, document.getGroupNamespace());
        additionalPermissionDetails.put(KimConstants.AttributeConstants.GROUP_NAME, document.getGroupName());
		if(document.getMembers()!=null && document.getMembers().size()>0){
			if(!getDocumentDictionaryService().getDocumentAuthorizer(document).isAuthorizedByTemplate(
					document, KimConstants.NAMESPACE_CODE, KimConstants.PermissionTemplateNames.POPULATE_GROUP,
					GlobalVariables.getUserSession().getPrincipalId(), additionalPermissionDetails, null)){
	    		GlobalVariables.getMessageMap().putError("document.groupName", 
	    				RiceKeyConstants.ERROR_ASSIGN_GROUP, 
	    				new String[] {document.getGroupNamespace(), document.getGroupName()});
	            rulePassed = false;
			}
		}
		return rulePassed;
	}

	protected boolean validDuplicateGroupName(IdentityManagementGroupDocument groupDoc){
        Group group = null;
        if(null != groupDoc.getGroupNamespace() && null != groupDoc.getGroupName()){
            group = KimApiServiceLocator.getGroupService().getGroupByNamespaceCodeAndName(
                groupDoc.getGroupNamespace(), groupDoc.getGroupName());
        }
        boolean rulePassed = true;
    	if(group!=null){
    		if(group.getId().equals(groupDoc.getGroupId())) {
    			rulePassed = true;
            }
    		else{
	    		GlobalVariables.getMessageMap().putError("document.groupName", 
	    				RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] {"Group Name"});
	    		rulePassed = false;
    		}
    	}
    	return rulePassed;
    }
    
    protected boolean validGroupMemberActiveDates(List<GroupDocumentMember> groupMembers) {
    	boolean valid = true;
		int i = 0;
    	for(GroupDocumentMember groupMember: groupMembers) {
   			valid &= validateActiveDate("document.members["+i+"].activeToDate", groupMember.getActiveFromDate(), groupMember.getActiveToDate());
    		i++;
    	}
    	return valid;
    }

    protected boolean validGroupMemberPrincipalIDs(List<GroupDocumentMember> groupMembers) {
        boolean valid = true;
        List<String> principalIds = new ArrayList<String>();
        for(GroupDocumentMember groupMember: groupMembers) {
            if (StringUtils.equals(groupMember.getMemberTypeCode(), KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode()) ) {
                principalIds.add(groupMember.getMemberId());
            }
        }
        if(!principalIds.isEmpty())       {
            // retrieve valid principals/principal-ids from identity service
            List<Principal> validPrincipals = getIdentityService().getPrincipals(principalIds);
            List<String> validPrincipalIds = new ArrayList<String>(validPrincipals.size());
            for (Principal principal : validPrincipals) {
                validPrincipalIds.add(principal.getPrincipalId());
            }
            // check that there are no invalid principals in the principal list, return false
            List<String> invalidPrincipalIds = new ArrayList<String>(CollectionUtils.subtract(principalIds, validPrincipalIds));
            // if list is not empty add error messages and return false
            if(CollectionUtils.isNotEmpty(invalidPrincipalIds)) {
                GlobalVariables.getMessageMap().putError("document.member.memberId", RiceKeyConstants.ERROR_MEMBERID_MEMBERTYPE_MISMATCH,
                        invalidPrincipalIds.toArray(new String[invalidPrincipalIds.size()]));
                valid = false;
            }
        }
        return valid;
    }

    protected boolean validateGroupQualifier(List<GroupDocumentQualifier> groupQualifiers, KimType kimType){
		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();

		List<RemotableAttributeError> errorsTemp;
		Map<String, String> mapToValidate;
        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
		mapToValidate = attributeValidationHelper.convertQualifiersToMap(groupQualifiers);
		errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
		validationErrors.addAll(attributeValidationHelper.convertErrors("",
                attributeValidationHelper.convertQualifiersToAttrIdxMap(groupQualifiers), errorsTemp));
		GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
		
    	if (validationErrors.isEmpty()) {
    		return true;
    	} 
    	attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
    	return false;
    }
    
	protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
		// TODO : do not have detail bus rule yet, so just check this for now.
		boolean valid = true;
		if (activeFromDate != null && activeToDate !=null && activeToDate.before(activeFromDate)) {
	        MessageMap errorMap = GlobalVariables.getMessageMap();
            errorMap.putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
            valid = false;
			
		}
		return valid;
	}
	
	/**
	 * @return the addGroupMemberRule
	 */
	public AddGroupMemberRule getAddGroupMemberRule() {
		if(addGroupMemberRule == null){
			try {
				addGroupMemberRule = addGroupMemberRuleClass.newInstance();
			} catch ( Exception ex ) {
				throw new RuntimeException( "Unable to create AddMemberRule instance using class: " + addGroupMemberRuleClass, ex );
			}
		}
		return addGroupMemberRule;
	}

    public boolean processAddGroupMember(AddGroupMemberEvent addGroupMemberEvent) {
        return getAddGroupMemberRule().processAddGroupMember(addGroupMemberEvent);    
    }

}
