/*
 * 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.tool.schema;

import java.util.Collections;
import java.util.EnumSet;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.transaction.SystemException;
import javax.transaction.Transaction;

import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.resource.transaction.TransactionCoordinatorBuilder;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
import org.hibernate.tool.schema.SourceType;
import org.hibernate.tool.schema.TargetType;
import org.hibernate.tool.schema.spi.SchemaCreator;
import org.hibernate.tool.schema.spi.SchemaDropper;
import org.hibernate.tool.schema.spi.SchemaManagementTool;
import org.hibernate.tool.schema.spi.ScriptSourceInput;
import org.hibernate.tool.schema.spi.ScriptTargetOutput;
import org.hibernate.tool.schema.spi.SourceDescriptor;
import org.hibernate.tool.schema.spi.TargetDescriptor;

import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * @author Steve Ebersole
 */
public class SchemaToolTransactionHandlingTest extends BaseUnitTestCase {
	// for each case we want to run these tool delegates in a matrix of:
	//		1) JTA versus JDBC transaction handling
	//		2) existing transaction versus not
	//
	// cases:
	//		1) create-drop
	//		2) update
	//		3) validate
	//
	// so:
	//		1) create-drop
	//			1.1) JTA transaction handling
	//				1.1.1) inside an existing transaction
	//				1.1.2) outside any transaction
	//			1.1) JDBC transaction handling
	//				- there really cannot be an "existing transaction" case...

	@Test
	public void testDropCreateDropInExistingJtaTransaction() {
		// test for 1.1.1 - create-drop + JTA handling + existing

		// start a JTA transaction...
		try {
			TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
		}
		catch (Exception e) {
			throw new RuntimeException( "Unable to being JTA transaction prior to starting test", e );
		}

		// hold reference to Transaction object
		final Transaction jtaTransaction;
		try {
			jtaTransaction = TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getTransaction();
		}
		catch (SystemException e) {
			throw new RuntimeException( "Unable to access JTA Transaction prior to starting test", e );
		}

		// perform the test...
		try {
			final StandardServiceRegistry registry = buildJtaStandardServiceRegistry();
			final SchemaManagementTool smt = registry.getService( SchemaManagementTool.class );
			final SchemaDropper schemaDropper = smt.getSchemaDropper( Collections.emptyMap() );
			final SchemaCreator schemaCreator = smt.getSchemaCreator( Collections.emptyMap() );

			final Metadata mappings = buildMappings( registry );
			try {
				schemaDropper.doDrop(
						mappings,
						ExecutionOptionsTestImpl.INSTANCE,
						SourceDescriptorImpl.INSTANCE,
						TargetDescriptorImpl.INSTANCE
				);
				schemaCreator.doCreation(
						mappings,
						ExecutionOptionsTestImpl.INSTANCE,
						SourceDescriptorImpl.INSTANCE,
						TargetDescriptorImpl.INSTANCE
				);
			}
			finally {
				try {
					schemaDropper.doDrop(
							mappings,
							ExecutionOptionsTestImpl.INSTANCE,
							SourceDescriptorImpl.INSTANCE,
							TargetDescriptorImpl.INSTANCE
					);
				}
				catch (Exception ignore) {
					// ignore
				}
			}
		}
		finally {
			try {
				jtaTransaction.commit();
			}
			catch (Exception e) {
				// not much we can do...
			}
		}

	}
	@Test
	public void testValidateInExistingJtaTransaction() {
		// test for 1.1.1 - create-drop + JTA handling + existing

		// start a JTA transaction...
		try {
			TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
		}
		catch (Exception e) {
			throw new RuntimeException( "Unable to being JTA transaction prior to starting test", e );
		}

		// hold reference to Transaction object
		final Transaction jtaTransaction;
		try {
			jtaTransaction = TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getTransaction();
		}
		catch (SystemException e) {
			throw new RuntimeException( "Unable to access JTA Transaction prior to starting test", e );
		}

		// perform the test...
		try {
			final StandardServiceRegistry registry = buildJtaStandardServiceRegistry();
			final SchemaManagementTool smt = registry.getService( SchemaManagementTool.class );

			final Metadata mappings = buildMappings( registry );

			// first make the schema exist...
			try {
				smt.getSchemaCreator( Collections.emptyMap() ).doCreation(
						mappings,
						ExecutionOptionsTestImpl.INSTANCE,
						SourceDescriptorImpl.INSTANCE,
						TargetDescriptorImpl.INSTANCE
				);
			}
			catch (Exception e) {
				throw new RuntimeException( "Unable to create schema to validation tests", e );
			}

			try {
				smt.getSchemaValidator( Collections.emptyMap() ).doValidation(
						mappings,
						ExecutionOptionsTestImpl.INSTANCE
				);
			}
			finally {
				try {
					smt.getSchemaDropper( Collections.emptyMap() ).doDrop(
							mappings,
							ExecutionOptionsTestImpl.INSTANCE,
							SourceDescriptorImpl.INSTANCE,
							TargetDescriptorImpl.INSTANCE
					);
				}
				catch (Exception ignore) {
					// ignore
				}
			}
		}
		finally {
			try {
				jtaTransaction.commit();
			}
			catch (Exception e) {
				// not much we can do...
			}
		}

	}

	private Metadata buildMappings(StandardServiceRegistry registry) {
		return new MetadataSources( registry )
				.addAnnotatedClass( MyEntity.class )
				.buildMetadata();
	}

	protected StandardServiceRegistry buildJtaStandardServiceRegistry() {
		StandardServiceRegistry registry = TestingJtaBootstrap.prepare().build();
		assertThat( registry.getService( TransactionCoordinatorBuilder.class ), instanceOf( JtaTransactionCoordinatorBuilderImpl.class ) );
		return registry;
	}

	@Entity( name = "MyEntity" )
	@Table( name = "MyEntity" )
	public static class MyEntity {
		@Id
		public Integer id;
		public String name;
	}

	private static class SourceDescriptorImpl implements SourceDescriptor {
		/**
		 * Singleton access
		 */
		public static final SourceDescriptorImpl INSTANCE = new SourceDescriptorImpl();

		@Override
		public SourceType getSourceType() {
			return SourceType.METADATA;
		}

		@Override
		public ScriptSourceInput getScriptSourceInput() {
			return null;
		}
	}

	private static class TargetDescriptorImpl implements TargetDescriptor {
		/**
		 * Singleton access
		 */
		public static final TargetDescriptorImpl INSTANCE = new TargetDescriptorImpl();

		@Override
		public EnumSet<TargetType> getTargetTypes() {
			return EnumSet.of( TargetType.DATABASE );
		}

		@Override
		public ScriptTargetOutput getScriptTargetOutput() {
			return null;
		}
	}
}
