package org.codefilarete.stalactite.engine.configurer.builder;

import java.util.Comparator;
import java.util.Set;
import java.util.function.Function;

import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
import org.codefilarete.stalactite.dsl.idpolicy.IdentifierPolicy;
import org.codefilarete.stalactite.dsl.naming.TableNamingStrategy;
import org.codefilarete.stalactite.engine.configurer.AbstractIdentification;
import org.codefilarete.stalactite.engine.configurer.ToStringBuilder;
import org.codefilarete.stalactite.engine.model.AbstractVehicle;
import org.codefilarete.stalactite.id.Identifier;
import org.codefilarete.stalactite.id.PersistableIdentifier;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.function.Sequence;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.codefilarete.stalactite.dsl.MappingEase.entityBuilder;
import static org.codefilarete.tool.function.Functions.chain;

class PrimaryKeyStepTest {
	
	@Test
	void alreadyAssignedPolicy() {
		PrimaryKeyStep<AbstractVehicle, Identifier<Long>> testInstance = new PrimaryKeyStep<>();
		EntityMappingConfiguration<AbstractVehicle, Identifier<Long>> identifyingConfiguration = entityBuilder(AbstractVehicle.class, Identifier.LONG_TYPE)
				.mapKey(AbstractVehicle::getId, IdentifierPolicy.alreadyAssigned(o -> {}, o -> true))
				.withColumnNaming(accessorDefinition -> "myId")
				.getConfiguration();
		
		Table mainTable = new Table("AbstractVehicle");
		testInstance.addIdentifyingPrimarykey(
				AbstractIdentification.forSingleKey(identifyingConfiguration),
				new TableMappingStep<AbstractVehicle, Identifier<Long>>().mapEntityConfigurationToTable(identifyingConfiguration, mainTable, TableNamingStrategy.DEFAULT),
				new ColumnBinderRegistry(),
				identifyingConfiguration.getColumnNamingStrategy(),
				identifyingConfiguration.getUniqueConstraintNamingStrategy());
		
		Function<Column, String> columnPrinter = ToStringBuilder.of(", ",
				Column::getAbsoluteName,
				chain(Column::getJavaType, Reflections::toString));
		assertThat((Set<Column>) mainTable.getPrimaryKey().getColumns())
				// Objects are similar but not equals so we compare them through their footprint (truly comparing them is quite hard)
				.usingElementComparator(Comparator.comparing(columnPrinter))
				.containsExactly(mainTable.getColumn("myId"));
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isAutoGenerated()).isFalse();
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isNullable()).isFalse();
	}
	
	@Test
	void beforeInsertPolicy() {
		PrimaryKeyStep<AbstractVehicle, Identifier<Long>> testInstance = new PrimaryKeyStep<>();
		EntityMappingConfiguration<AbstractVehicle, Identifier<Long>> identifyingConfiguration = entityBuilder(AbstractVehicle.class, Identifier.LONG_TYPE)
				.mapKey(AbstractVehicle::getId, IdentifierPolicy.pooledHiLoSequence(new Sequence<Identifier<Long>>() {

					private long identifier = 0;

					@Override
					public Identifier<Long> next() {
						return new PersistableIdentifier<>(identifier++);
					}
				}))
				.withColumnNaming(accessorDefinition -> "myId")
				.getConfiguration();

		Table mainTable = new Table("AbstractVehicle");
		testInstance.addIdentifyingPrimarykey(
				AbstractIdentification.forSingleKey(identifyingConfiguration),
				new TableMappingStep<AbstractVehicle, Identifier<Long>>().mapEntityConfigurationToTable(identifyingConfiguration, mainTable, TableNamingStrategy.DEFAULT),
				new ColumnBinderRegistry(),
				identifyingConfiguration.getColumnNamingStrategy(),
				identifyingConfiguration.getUniqueConstraintNamingStrategy());
		
		Function<Column, String> columnPrinter = ToStringBuilder.of(", ",
				Column::getAbsoluteName,
				chain(Column::getJavaType, Reflections::toString));
		assertThat((Set<Column>) mainTable.getPrimaryKey().getColumns())
				// Objects are similar but not equals so we compare them through their footprint (truly comparing them is quite hard)
				.usingElementComparator(Comparator.comparing(columnPrinter))
				.containsExactly(mainTable.getColumn("myId"));
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isAutoGenerated()).isFalse();
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isNullable()).isFalse();
	}

	@Test
	void afterInsertPolicy() {
		PrimaryKeyStep<AbstractVehicle, Identifier<Long>> testInstance = new PrimaryKeyStep<>();
		EntityMappingConfiguration<AbstractVehicle, Identifier<Long>> identifyingConfiguration = entityBuilder(AbstractVehicle.class, Identifier.LONG_TYPE)
				.mapKey(AbstractVehicle::getId, IdentifierPolicy.databaseAutoIncrement())
				.withColumnNaming(accessorDefinition -> "myId")
				.getConfiguration();

		Table mainTable = new Table("AbstractVehicle");
		testInstance.addIdentifyingPrimarykey(
				AbstractIdentification.forSingleKey(identifyingConfiguration),
				new TableMappingStep<AbstractVehicle, Identifier<Long>>().mapEntityConfigurationToTable(identifyingConfiguration, mainTable, TableNamingStrategy.DEFAULT),
				new ColumnBinderRegistry(),
				identifyingConfiguration.getColumnNamingStrategy(),
				identifyingConfiguration.getUniqueConstraintNamingStrategy());

		Function<Column, String> columnPrinter = ToStringBuilder.of(", ",
				Column::getAbsoluteName,
				chain(Column::getJavaType, Reflections::toString));
		assertThat((Set<Column>) mainTable.getPrimaryKey().getColumns())
				// Objects are similar but not equals so we compare them through their footprint (truly comparing them is quite hard)
				.usingElementComparator(Comparator.comparing(columnPrinter))
				.containsExactly(mainTable.getColumn("myId"));
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isAutoGenerated()).isTrue();
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isNullable()).isFalse();
	}
}
