/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.test.naturalid.immutableentity;

import org.hibernate.NaturalIdLoadAccess;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.annotations.Immutable;
import org.hibernate.cfg.Configuration;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.Statistics;

import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.tuple.entity.EntityMetamodel;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

/**
 * Test case for NaturalId annotation on an {@link Immutable} entity
 *
 * @author Eric Dalquist
 */
@SuppressWarnings("unchecked")
@TestForIssue( jiraKey = "HHH-7085" )
public class ImmutableEntityNaturalIdTest extends BaseCoreFunctionalTestCase {
	@Test
	public void testMappingProperties() {
		ClassMetadata metaData = sessionFactory().getClassMetadata(
				Building.class
		);
		assertTrue(
				"Class should have a natural key", metaData
						.hasNaturalIdentifier()
		);
		int[] propertiesIndex = metaData.getNaturalIdentifierProperties();
		assertEquals( "Wrong number of elements", 3, propertiesIndex.length );
	}

	@Test
	@TestForIssue( jiraKey = "HHH-10360")
	public void testNaturalIdNullability() {
		final EntityPersister persister = sessionFactory().getEntityPersister( Building.class.getName() );
		final EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
		// nullability is not specified, so they should be nullable by annotations-specific default
		assertTrue( persister.getPropertyNullability()[entityMetamodel.getPropertyIndex( "address" )] );
		assertTrue( persister.getPropertyNullability()[entityMetamodel.getPropertyIndex( "city" )] );
		assertTrue( persister.getPropertyNullability()[entityMetamodel.getPropertyIndex( "state" )] );
	}

	@Test
	public void testImmutableNaturalIdLifecycle() {
		Statistics stats = sessionFactory().getStatistics();
		stats.setStatisticsEnabled( true );
		stats.clear();

		assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
		assertEquals( "Cache misses should be empty", 0, stats.getNaturalIdCacheMissCount() );
		assertEquals( "Cache put should be empty", 0, stats.getNaturalIdCachePutCount() );
		assertEquals( "Query count should be empty", 0, stats.getNaturalIdQueryExecutionCount() );
		
		Building b1 = new Building();
		b1.setName( "Computer Science" );
		b1.setAddress( "1210 W. Dayton St." );
		b1.setCity( "Madison" );
		b1.setState( "WI" );

		Session s = openSession();
		Transaction tx = s.beginTransaction();
		s.persist( b1 );
		tx.commit();
		s.close();

		assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
		assertEquals( "Cache misses should be empty", 0, stats.getNaturalIdCacheMissCount() );
		assertEquals( "Cache put should be one after insert", 1, stats.getNaturalIdCachePutCount() );
		assertEquals( "Query count should be empty", 0, stats.getNaturalIdQueryExecutionCount() );
		
		s = openSession();
		tx = s.beginTransaction();
		
		//Clear caches and reset cache stats
		s.getSessionFactory().getCache().evictNaturalIdRegions();
		stats.clear();
		
		NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Building.class );
		naturalIdLoader.using( "address", "1210 W. Dayton St." ).using( "city", "Madison" ).using( "state", "WI" );

		// first query
		Building building = (Building) naturalIdLoader.load();
		assertNotNull( building );
		assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
		assertEquals( "Cache misses should be one after first query", 1, stats.getNaturalIdCacheMissCount() );
		assertEquals( "Cache put should be one after first query", 1, stats.getNaturalIdCachePutCount() );
		assertEquals( "Query count should be one after first query", 1, stats.getNaturalIdQueryExecutionCount() );

		// cleanup
		tx.rollback();
		s.close();
		
		//Try two, should be a cache hit
		
		s = openSession();
		tx = s.beginTransaction();
		naturalIdLoader = s.byNaturalId( Building.class );
		naturalIdLoader.using( "address", "1210 W. Dayton St." ).using( "city", "Madison" ).using( "state", "WI" );

		// second query
		building = (Building) naturalIdLoader.load();
		assertNotNull( building );
		assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() );
		assertEquals( "Cache misses should be one after second query", 1, stats.getNaturalIdCacheMissCount() );
		assertEquals( "Cache put should be one after second query", 1, stats.getNaturalIdCachePutCount() );
		assertEquals( "Query count should be one after second query", 1, stats.getNaturalIdQueryExecutionCount() );
		
		// Try Deleting
		s.delete( building );
		
		// third query
		building = (Building) naturalIdLoader.load();
		assertNull( building );
		assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() );
		assertEquals( "Cache misses should be two after second query", 2, stats.getNaturalIdCacheMissCount() );
		assertEquals( "Cache put should be one after second query", 2, stats.getNaturalIdCachePutCount() );
		assertEquals( "Query count should be two after second query", 2, stats.getNaturalIdQueryExecutionCount() );

		// cleanup
		tx.commit();
		s.close();
		
		//Try three, should be db lookup and miss
		
		s = openSession();
		tx = s.beginTransaction();
		naturalIdLoader = s.byNaturalId( Building.class );
		naturalIdLoader.using( "address", "1210 W. Dayton St." ).using( "city", "Madison" ).using( "state", "WI" );

		// second query
		building = (Building) naturalIdLoader.load();
		assertNull( building );
		assertEquals( "Cache hits should be one after third query", 1, stats.getNaturalIdCacheHitCount() );
		assertEquals( "Cache misses should be one after third query", 3, stats.getNaturalIdCacheMissCount() );
		assertEquals( "Cache put should be one after third query", 2, stats.getNaturalIdCachePutCount() );
		assertEquals( "Query count should be one after third query", 3, stats.getNaturalIdQueryExecutionCount() );

		// cleanup
		tx.rollback();
		s.close();
	}
	
	@Test
	@TestForIssue( jiraKey = "HHH-7371" )
	public void testImmutableNaturalIdLifecycle2() {
		Building b1 = new Building();
		b1.setName( "Computer Science" );
		b1.setAddress( "1210 W. Dayton St." );
		b1.setCity( "Madison" );
		b1.setState( "WI" );

		Session s = openSession();
		Transaction tx = s.beginTransaction();
		s.persist( b1 );
		tx.commit();
		s.close();


		s = openSession();
		tx = s.beginTransaction();
		NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Building.class );
		naturalIdLoader.using( "address", "1210 W. Dayton St." ).using( "city", "Madison" ).using( "state", "WI" );
		Building building = (Building) naturalIdLoader.getReference();
		assertNotNull( building );

		s.delete( building );
		building = (Building) naturalIdLoader.load();
		//org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [org.hibernate.test.naturalid.immutableentity.Building#1]
//		at org.hibernate.internal.SessionFactoryImpl$1$1.handleEntityNotFound(SessionFactoryImpl.java:247)
//		at org.hibernate.event.internal.DefaultLoadEventListener.returnNarrowedProxy(DefaultLoadEventListener.java:282)
//		at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:248)
//		at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:148)
//		at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1079)
//		at org.hibernate.internal.SessionImpl.access$13(SessionImpl.java:1075)
//		at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2425)
//		at org.hibernate.internal.SessionImpl$NaturalIdLoadAccessImpl.load(SessionImpl.java:2586)
//		at org.hibernate.test.naturalid.immutableentity.ImmutableEntityNaturalIdTest.testImmutableNaturalIdLifecycle2(ImmutableEntityNaturalIdTest.java:188)

		assertNull( building );

		tx.commit();
		s.close();
	}

	@Override
	protected Class[] getAnnotatedClasses() {
		return new Class[] {
				Building.class
		};
	}

	@Override
	protected void configure(Configuration cfg) {
		cfg.setProperty( "hibernate.cache.use_query_cache", "true" );
	}
}
