package de.ssmits.hibernateEavModel.persistence.core.eav;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.UUID;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import de.ssmits.hibernateEavModel.persistence.core.eav.dataObjects.EavObject;
import de.ssmits.hibernateEavModel.persistence.core.eav.dataObjects.Employee;
import de.ssmits.hibernateEavModel.persistence.core.eav.dataObjects.JunitTestObject;
import de.ssmits.hibernateEavModel.persistence.core.entity.CoreAttribute;
import de.ssmits.hibernateEavModel.persistence.core.entity.CoreEntity;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/applicationContext-bootstrap.xml")
@Transactional
public class EavStoreManagerTest {
	@Autowired private EavFactory eavFactory;
	
	private static JunitTestObject testObject;
	private static Employee testEmployee;
	
	@Before public void createTestRecords() throws Exception {
		testObject = new JunitTestObject(1, Calendar.getInstance(), true, "EAV JUNIT TEST OBJECT");
		testEmployee = new Employee("Ray", "Charles", "ray.charles@example.org", Locale.US, "+1 404 123 567");
	}
	
	@After public void deleteTestRecords() throws Exception {
		testObject = null;
		testEmployee = null;
	}
	
	@Test public void testImportGenericObject_JpaTestObject() throws Exception {
		CoreEntity entity = this.eavFactory.getEavStoreManager().importGenericObject(testObject).getEntity();
		
		assertNotNull(entity);
		assertEquals(4, entity.getAttributeValuePairs().size());
	}
	
	@Test public void testImportGenericObject_Employee() throws Exception {
		CoreEntity entity = this.eavFactory.getEavStoreManager().importGenericObject(testEmployee).getEntity();

		assertNotNull(entity);
		assertEquals(5, entity.getAttributeValuePairs().size());
	}
	
	@Test public void testImportGenericObject_JpaEntity() throws Exception {
		CoreEntity entity = this.eavFactory.getEavStoreManager().importGenericObject(testObject).getEntity();
		assertNotNull(entity);
		
		CoreEntity entityWithJpaObject = this.eavFactory.getEavStoreManager().importGenericObject(entity).getEntity();
		assertNotNull(entityWithJpaObject);
		
		// TODO: Implement more tests to verify its correctly persisted into the eav store
	}
	
	@Test public void testReconstructObjectFromEav() throws Exception {
		EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testObject);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		JunitTestObject reconstructedObject = (JunitTestObject) this.eavFactory.getEavStoreManager().reconstructObjectFromEav(eavObj);
		
		assertEquals(testObject.getId(), reconstructedObject.getId());
		assertEquals(testObject.getDate(), reconstructedObject.getDate());
		assertEquals(testObject.getIsActive(), reconstructedObject.getIsActive());
		assertEquals(testObject.getLabel(), reconstructedObject.getLabel());
	}
	
	@Test public void testReconstructObjectFromEav_Employee() throws Exception {
		EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testEmployee);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		Employee reconstructedObject = (Employee) this.eavFactory.getEavStoreManager().reconstructObjectFromEav(eavObj);
		assertNotNull("Assuming reconstructed object is not null.", reconstructedObject);
		
		assertEquals(testEmployee.getFirstname(), reconstructedObject.getFirstname());
		assertEquals(testEmployee.getLastname(), reconstructedObject.getLastname());
		assertEquals(testEmployee.getEmail(), reconstructedObject.getEmail());
		assertEquals(testEmployee.getLanguage(), reconstructedObject.getLanguage());
		assertEquals(testEmployee.getPhoneNumber(), reconstructedObject.getPhoneNumber());
	}
	
	@Test public void testReconstructObjectFromEav_JunitTestObject() throws Exception {
		EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testObject);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		JunitTestObject reconstructedObject = (JunitTestObject) this.eavFactory.getEavStoreManager().reconstructObjectFromEav(eavObj);
		assertNotNull("Assuming reconstructed object is not null.", reconstructedObject);
		
		assertEquals(testObject.getId(), reconstructedObject.getId());
		assertEquals(testObject.getDate(), reconstructedObject.getDate());
		assertEquals(testObject.getIsActive(), reconstructedObject.getIsActive());
		assertEquals(testObject.getLabel(), reconstructedObject.getLabel());
	}
	
	@Test public void testFindOneById() throws Exception {
		final EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testEmployee);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		final EavObject searchResult = this.eavFactory.getEavStoreManager().findOneById(eavObj.getId());
		assertNotNull("Assuming persisted eav object has been found.", searchResult);
		
		assertEquals("Assuming eav entity id equals test entity id", eavObj.getId(), searchResult.getId());
	}
	
	@Test public void testFindAll() throws Exception {
		final EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testEmployee);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		final List<EavObject> searchResult = this.eavFactory.getEavStoreManager().findAll();
		assertNotNull("Expecting search result to be not null", searchResult);
		
		assertEquals("Assuming eav store contains 1 object.", 1, searchResult.size());
		assertEquals("Assuming eav entity id equals test entity id", eavObj.getId(), searchResult.get(0).getId());
	}

	@Test public void testFindByType() throws Exception {
		final EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testEmployee);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		final List<EavObject> searchResult = this.eavFactory.getEavStoreManager().findByType(testEmployee.getClass());
		assertNotNull("Expecting search result to be not null", searchResult);
		
		assertEquals("Assuming eav store contains 1 object.", 1, searchResult.size());
		assertEquals("Assuming eav entity id equals test entity id", eavObj.getId(), searchResult.get(0).getId());
		assertEquals("Expecting classes to be equal", eavObj.getType(), searchResult.get(0).getType());
	}
	
	@Test public void testFindByAttribute() throws Exception {
		final EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testObject);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		CoreAttribute testAttribute = null;
		
		for (CoreAttribute attribute : eavObj.getAttributes().keySet()) {
			testAttribute = attribute;
			break;
		}
		
		final List<EavObject> searchResult = this.eavFactory.getEavStoreManager().findByAttribute(testAttribute);
		assertNotNull("Expecting search result to be not null", searchResult);
		assertTrue("Expecting result object contains test attribute", searchResult.get(0).getAttributes().containsKey(testAttribute));
	}
	
	@Test public void testFindByAttributeAndValue() throws Exception {
		final EavObject eavObj = this.eavFactory.getEavStoreManager().importGenericObject(testObject);
		assertNotNull("Assuming test object was successfully imported into the eav store.", eavObj);
		
		for (CoreAttribute attribute : eavObj.getAttributes().keySet()) {
			final Object attributeValue = eavObj.getAttributes().get(attribute);
			final List<EavObject> searchResult = this.eavFactory.getEavStoreManager().findByAttributeAndValue(attribute, attributeValue);
			
			assertNotNull("Expecting search result to be not null", searchResult);
			assertEquals("Expecting CoreEntity id to be equal", eavObj.getEntity().getId(), searchResult.get(0).getEntity().getId());
			
			assertEquals(
				"Expecting attribute/value pair to match test objects attribute/value pair",
				eavObj.getAttributes().get(attribute),
				searchResult.get(0).getAttributes().get(attribute)
			);			
		}
	}
	
	@Test public void testFindByAttributeAndValue_recordCount_1K() throws Exception {
		List<EavObject> testObjects = new ArrayList<EavObject>();
		
		// Generate 1K test records
		for (int i=1; i<=1000; i++) {
			testObjects.add(
				this.eavFactory.getEavStoreManager().importGenericObject(
					new JunitTestObject(
						new Random().nextInt(), 
						Calendar.getInstance(), 
						true, 
						"JUNIT TEST OBJECT " + UUID.randomUUID().toString()
					)
				)
			);
		}
		
		// define attribute/value pair to search for
		CoreAttribute testAttribute = null;
		Object testValue = null;
		
		for (CoreAttribute attr : testObjects.get(596).getAttributes().keySet()) {
			testAttribute = attr;
			testValue = testObjects.get(596).getAttributes().get(attr);
			break;
		}
		
		List<EavObject> searchResult = this.eavFactory.getEavStoreManager().findByAttributeAndValue(testAttribute, testValue);

		assertNotNull(searchResult);		
		assertEquals(1, searchResult.size());
		assertTrue(searchResult.get(0).getAttributes().containsKey(testAttribute));
		assertEquals(testValue, searchResult.get(0).getAttributes().get(testAttribute));
	}
}
