/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.common.rule.service;

// Java classes
import java.util.Iterator;
import java.util.Calendar;
import java.sql.Date;

// Library classes
import org.apache.commons.lang.Validate;

// Framework classes
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.validation.ValidationServiceException;
import gov.va.med.fw.validation.ValidationMessages;
import gov.va.med.fw.validation.ValidationFieldMessage;

// ESR classes
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.Association;
import gov.va.med.esr.common.model.lookup.AssociationType;
import gov.va.med.esr.common.model.lookup.AddressType;
import gov.va.med.esr.common.util.RuleAbstractTestCase;

/**
 * The intent of this test case is to test the Association Validation rule flow.
 * The test uses the AssociationRuleService.
 * 
 * @author DNS   LEV
 
 * @version 1.0
 */

public class AssociationRuleServiceTest extends RuleAbstractTestCase {

	/**
	 * An instance of onFile
	 */
	private Person onFile = null;

	/**
	 * constructor
	 */
	public AssociationRuleServiceTest(String testName) throws Exception {
		super(testName);
	}

	/**
	 * @see gov.va.med.fw.util.AbstractTestCase#customSetUp()
	 */
	protected void customSetUp() throws Exception {
		super.customSetUp();
		onFile = this.buildSimplePerson();
	}

	/**
	 * @see gov.va.med.fw.util.AbstractTestCase#customTearDown()
	 */
	protected void customTearDown() throws Exception {
		super.customTearDown();
		onFile = null;
	}

	// Tests BAL rule AssociationIsMoreRecent in ProcessAssociations.irl
	// Mimic data coming from UI by calling manage associations
	public void testManageAssociations() throws Exception {
		// Persist the on-file person so we will have a pristine person in rule parameters
		// Prepare the on-file person with 1 association
		
		// Adding some associations to a person
		for( int i=0; i<4; i++ ) {
			onFile.addAssociation( this.createPersonAssociation( "HLS", "Manager " + (i+1), AssociationType.CODE_GUARDIAN_VA.getName() ) );
		}
		this.getPersonService().save( onFile );

		// Clone the pristine to have a working version
		Person working = (Person)onFile.clone();
		Person incoming = (Person)onFile.clone();

		// Modify data to accept changes
		Iterator incomingIterator = incoming.getAssociations().iterator();
		Iterator workingIterator = working.getAssociations().iterator();
		
		Calendar cal = Calendar.getInstance();
		Date updatedTime = new Date( cal.getTime().getTime() );
		Date lastUpdatedTime = new Date( cal.getTime().getTime() - 36000 );
		
		while( incomingIterator.hasNext() && workingIterator.hasNext() ) {
			Association current = (Association)workingIterator.next();
			Association modified = (Association)incomingIterator.next();
			
			modified.setRelationship( "Director" );
			modified.setOrganizationName( "FAA" );
			modified.setLastUpdateDate( updatedTime );
			
			current.setLastUpdateDate( lastUpdatedTime );
		}
	}

	// Tests BAL rule AssociationIsMoreRecent in ProcessAssociations.irl
	// Mimic data coming from UI by calling manage associations
	public void testProcessAssociations() throws Exception {
		// Persist the on-file person so we will have a pristine person in rule parameters
		// Prepare the on-file person with 1 association
		
		// Adding some associations to a person
		for( int i=0; i<2; i++ ) {
			onFile.addAssociation( this.createPersonAssociation( "ABC", "BIG GUY " + (i+1), AssociationType.CODE_GUARDIAN_VA.getName() ) );
		}
		this.getPersonService().save( onFile );

		// Clone the pristine to have a working version
		Person working = (Person)onFile.clone();
		Person incoming = (Person)onFile.clone();

		// Modify data to accept changes
		Iterator incomingIterator = incoming.getAssociations().iterator();
		Iterator workingIterator = working.getAssociations().iterator();
		
		Calendar cal = Calendar.getInstance();
		Date updatedTime = new Date( cal.getTime().getTime() );
		Date lastUpdatedTime = new Date( cal.getTime().getTime() - 36000 );
		
		while( incomingIterator.hasNext() && workingIterator.hasNext() ) {
			Association current = (Association)workingIterator.next();
			Association modified = (Association)incomingIterator.next();
			
			modified.setRelationship( "Nobody" );
			modified.setOrganizationName( "CNN" );
			modified.setLastUpdateDate( updatedTime );
			
			current.setLastUpdateDate( lastUpdatedTime );
		}
	}
	
	// Tests BAL rule AssociationIsNew in ProcessAssociations.irl
	public void testAssociationIsNew() throws Exception {
		// Persist the on-file person so we will have a pristine person in rule parameters
		
		Person updated = this.getPersonService().save( onFile );

		// Clone the pristine to have a working version
		Person incoming = (Person)updated.clone();

        incoming.removeAllAssociations();

		incoming.addAssociation( this.createPersonAssociation( "EDS", "Manager New" , AssociationType.CODE_GUARDIAN_CIVIL.getName() ) );

		try {
			this.getAssociationRuleService().processAssociations( incoming.getAssociations(), onFile, null );
			assertEquals( onFile.getAssociations().size(), 1 );
		}
		catch( ServiceException e ) {
			// We don't expect any exception here
			this.fail( "Failed due to exception ", e );
		}
	}
	
	// Tests BAL rule AssociationIsMoreRecent in ProcessAssociations.irl
	public void testAssociationIsMoreRecent() throws Exception {

		// Persist the on-file person so we will have a pristine person in rule parameters
		// Prepare the on-file person with 1 association
		
		// Create a time to mimic last updated date on a pristine person
		Calendar cal = DateUtils.createCalendar( 2005, 6, 1 );
		
		Date lastUpdatedTime = new Date( cal.getTime().getTime() );
		
		// Adding some associations to a person
		for( int i=0; i<1; i++ ) {
			Association association = this.createPersonAssociation( "EDS", "Manager " + (i+1), AssociationType.CODE_GUARDIAN_VA.getName() );
			association.setAddress( this.createAddress( this.getAddressType( AddressType.CODE_PERMANENT_ADDRESS)));
			association.setLastUpdateDate( lastUpdatedTime );
			onFile.addAssociation( association );
		}
		Person saved = this.getPersonService().save( onFile );

		// Clone the pristine to have a working version
		Person working = (Person)saved.clone();
		Person incoming = (Person)saved.clone();

		// Modify data to accept changes
		Iterator incomingIterator = incoming.getAssociations().iterator();
		Iterator workingIterator = working.getAssociations().iterator();
		
		Date updatedTime = new Date( Calendar.getInstance().getTime().getTime() );

		while( incomingIterator.hasNext() && workingIterator.hasNext() ) {
			Association current = (Association)workingIterator.next();
			Association modified = (Association)incomingIterator.next();
			
			modified.setRelationship( "Big Shot" );
			modified.setOrganizationName( "VHA" );
			modified.setLastUpdateDate( updatedTime );
			
			current.setLastUpdateDate( lastUpdatedTime );
		}
	}

	// Tests BAL rule IsAssociationTypeNotProvided
	public void testIsAssociationTypeNotProvided() throws Exception {
		// This rule should never fail because our BOM model doesn't
		// allow an Association with a null association type
		this.logger.info( "This rule isAssociationTypeNotProvided should always pass !!!!");
	}

	// Tests BAL rule IsNameForAssociationNotProvided
	public void testIsNameForAssociationNotProvided() throws Exception {

		/** 
		 * HL7 - 1876[UC34.3.11.1.2] 
		 * If an Association exists on the incoming message or is added, 
		 * the following fields are required:
		 *		Representative Last Name
		 *		Representative First Name OR
       *		Organization Name
       *		Association Relationship Type 
       *
       *	IF It is not true that 
       *	( 	( The last name is Provided AND The first name is Provided ) OR 
       *		( The association relationship type is provided AND The organization name is provided ) )
       *
       *	THEN
       *	Generate an Application error with the text "ASSOCIATION NAME IS NOT PROVIDED" 
       *	for field name: Association Name
		 */
		
		// Test condition 1: create test data to test a condition where all four attributes
		// First Name, Last Name, Org Name, and Relationship Type are NULL. This condition
		// should trigger a validation message
		Person incoming = onFile;
		incoming.removeAllAssociations();

		Association association = 
			this.createPersonAssociation(	null, // Org Name
													null, // Relation ship type
													AssociationType.CODE_GUARDIAN_VA.getName() );
		
		
		// create an association with NULL last Name and NULL first name
		association.setRepresentativeName( this.createName( null, null, this.getRandomNameType() ) );
		incoming.addAssociation( association );

		try {
			ValidationMessages messages = this.getRuleValidationService().validateAssociation(association, incoming, onFile, false); 
			assertTrue( !messages.isEmpty() );
			assertEquals( ((ValidationFieldMessage)messages.get().next()).getKey(), "ASSOCIATION_NAME_REQUIRED" );
		} 
		catch( ValidationServiceException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Got an expected validation exception ", e );
			}
		}
		
		// Test condition 2: create test data to test a condition where 2 attributes
		// First Name, and Relationship Type are NULL. This condition should trigger 
		// a validation message
		
		association.setRelationship( "Big Shot" );
		association.getRepresentativeName().setGivenName( "First Name" );
		
		try {
			ValidationMessages messages = this.getRuleValidationService().validateAssociation(association, incoming, onFile, false); 
			assertTrue( !messages.isEmpty() );
			assertEquals( ((ValidationFieldMessage)messages.get().next()).getKey(), "ASSOCIATION_NAME_REQUIRED" );
		} 
		catch( ValidationServiceException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Got an expected validation exception ", e );
			}
		}
		
		// Test condition 3: create test data to test a condition where only 2 attributes
		// First Name, and Last Name are provided. This condition should not trigger any 
		// validation message because First Name, and Last Name are exclusive from Org Name
		// and relationship type
		
		association.setRelationship( null );
		association.setOrganizationName( null );
		association.getRepresentativeName().setGivenName( "First Name" );
		association.getRepresentativeName().setFamilyName( "Family Name" );
		
		try {
			ValidationMessages messages = this.getRuleValidationService().validateAssociation(association, incoming, onFile, false); 
			assertTrue( messages.isEmpty() );
		} 
		catch( ValidationServiceException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Got an expected validation exception ", e );
			}
		}
		
		// Test condition 4: create test data to test a condition where only 2 attributes
		// Relation ship, and Orgnaization Name are provided. This condition should not trigger 
		// any validation message because First Name, and Last Name are exclusive from Org Name
		// and relationship type
		
		association.setRelationship( "Big Shot" );
		association.setOrganizationName( "EDS" );
		association.getRepresentativeName().setGivenName( null );
		association.getRepresentativeName().setFamilyName( null );
		
		try {
			ValidationMessages messages = this.getRuleValidationService().validateAssociation(association, incoming, onFile, false); 
			assertTrue( messages.isEmpty() );
		} 
		catch( ValidationServiceException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Got an expected validation exception ", e );
			}
		}
	}

	// Tests use case to delete an existing association
	public void testDeleteAssociation() throws Exception {

		// Persist the on-file person so we will have a pristine person in rule parameters
		// Prepare the on-file person with 1 association
		
		// Create a time to mimic last updated date on a pristine person
		Calendar cal = DateUtils.createCalendar( 2005, 6, 1 );
		
		Date lastUpdatedTime = new Date( cal.getTime().getTime() );
		
		// Adding some associations to a person
		for( int i=0; i<1; i++ ) {
			Association association = this.createPersonAssociation( "EDS", "Manager " + (i+1), AssociationType.CODE_GUARDIAN_VA.getName() );
			association.setAddress( this.createAddress( this.getAddressType( AddressType.CODE_PERMANENT_ADDRESS)));
			association.setLastUpdateDate( lastUpdatedTime );
			onFile.addAssociation( association );
		}
		Person saved = this.getPersonService().save( onFile );
		Person working = (Person)onFile.clone();

		// Clone the pristine to have a working version
		Person incoming = (Person)saved.clone();

		// Remove association from an incoming person
		incoming.removeAllAssociations();
	}
	
	// Tests BAL rule IsOrganzationNameAllowed
	public void testIsOrganzationNameAllowed() throws Exception {

		/*
		 * IF The Organization Name of the association for the veteran is provided 
		 * AND It is not true that 
		 * ( 	The association type for the veteran is VA GUARDIAN OR 
		 * 	The association type for the veteran is CIVIL GUARDIAN OR 
		 * 	The association type for the veteran is POWER OF ATTORNEY ) 
		 * THEN Generate an Application error with the text 
		 * 	"ORGANIZATION NAME IS ONLY ALLOWED FOR GUARDIAN AND POWER OF ATTORNEY"
		 */
   	 
		// create test data to test when a rule should throw an exception as we expect
		onFile.removeAllAssociations();
		onFile.addAssociation( this.createPersonAssociation("EDS", "Big Shot", AssociationType.CODE_EMERGENCY_CONTACT.getName()) );

		// Get the one just added to validate
		Association association = (Association)onFile.getAssociations().iterator().next();

		try {
			this.getRuleValidationService().validateAssociation(association, onFile, onFile, true);
		} 
		catch( ValidationServiceException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Got an expected validation exception ", e );
			}
			ValidationMessages messages = e.getValidationMessages();
			assertTrue( !messages.isEmpty() );
			assertEquals( ((ValidationFieldMessage)messages.get().next()).getKey(), "ORGANIZATION_NAME_NOT_ALLOWED" );
		}
		
		// create test data to test when a rule should NOT throw an exception as we expect
		onFile.removeAllAssociations();
		onFile.addAssociation( this.createPersonAssociation("EDS", "Big Shot", AssociationType.CODE_GUARDIAN_VA.getName()) );
		
		// Get the one just added
		association = (Association)onFile.getAssociations().iterator().next();

		try {
			this.getRuleValidationService().validateAssociation(association, onFile, onFile, true);
		} 
		catch( ValidationServiceException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Got an unexpected validation exception ", e );
			}
			this.fail( "Got an un-expected validation exception", e );
		}
	}
	/**
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();
		Validate.notNull(this.getAssociationRuleService(), "An association rule service is required");
		Validate.notNull(this.getAssociationService(), "An association service is required");
		Validate.notNull(this.getRuleValidationService(), "A rule validation service is required");
	}
}