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.ForeignKeyNamingStrategy;
import org.codefilarete.stalactite.engine.configurer.ToStringBuilder;
import org.codefilarete.stalactite.engine.configurer.builder.InheritanceMappingStep.MappingPerTable;
import org.codefilarete.stalactite.engine.model.AbstractVehicle;
import org.codefilarete.stalactite.id.Identifier;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.ForeignKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.Arrays;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.junit.jupiter.api.Test;

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

class PrimaryKeyPropagationStepTest {
	
	@Test
	void propagatePrimarykey() {
		Table mainTable = new Table("AbstractVehicle");
		mainTable.addColumn("id", Identifier.LONG_TYPE)
				.nullable(false)
				.primaryKey()
				.autoGenerated();
		
		Table tableB = new Table("Vehicle");
		Table tableC = new Table("Car");
		PrimaryKeyPropagationStep.propagatePrimaryKey(mainTable.getPrimaryKey(), Arrays.asSet(tableB, tableC));
		
		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("id"));
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isAutoGenerated()).isTrue();
		assertThat(Iterables.first((Set<Column>) mainTable.getPrimaryKey().getColumns()).isNullable()).isFalse();
		assertThat((Set<Column>) tableB.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(tableB.getColumn("id"));
		assertThat(Iterables.first((Set<Column>) tableB.getPrimaryKey().getColumns()).isAutoGenerated()).isFalse();
		assertThat(Iterables.first((Set<Column>) tableB.getPrimaryKey().getColumns()).isNullable()).isFalse();
		assertThat((Set<Column>) tableC.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(tableC.getColumn("id"));
		assertThat(Iterables.first((Set<Column>) tableC.getPrimaryKey().getColumns()).isAutoGenerated()).isFalse();
		assertThat(Iterables.first((Set<Column>) tableC.getPrimaryKey().getColumns()).isNullable()).isFalse();
	}
	
	@Test
	void propagate() {
		EntityMappingConfiguration<AbstractVehicle, Identifier<Long>> identifyingConfiguration = entityBuilder(AbstractVehicle.class, Identifier.LONG_TYPE)
				.mapKey(AbstractVehicle::getId, IdentifierPolicy.databaseAutoIncrement())
				.getConfiguration();
		
		Table mainTable = new Table("AbstractVehicle");
		Table tableB = new Table("Vehicle");
		Table tableC = new Table("Car");
		
		mainTable.addColumn("id", Identifier.LONG_TYPE)
				.nullable(false)
				.primaryKey()
				.autoGenerated();
		PrimaryKeyPropagationStep<AbstractVehicle, Identifier<Long>> testInstance = new PrimaryKeyPropagationStep<>();
		MappingPerTable<AbstractVehicle> mappingPerTable = new MappingPerTable<AbstractVehicle>() {
			@Override
			KeepOrderSet<Table> giveTables() {
				return new KeepOrderSet<>(Arrays.asSet(tableC, tableB, mainTable));
			}
		};
		
		testInstance.propagate(mainTable.getPrimaryKey(), mappingPerTable, ForeignKeyNamingStrategy.DEFAULT);
		
		Function<Column, String> columnPrinter = ToStringBuilder.of(", ",
				Column::getAbsoluteName,
				chain(Column::getJavaType, Reflections::toString));
		Function<ForeignKey, String> fkPrinter = ToStringBuilder.of(", ",
				ForeignKey::getName,
				link(ForeignKey::getColumns, ToStringBuilder.asSeveral(columnPrinter)),
				link(ForeignKey::getTargetColumns, ToStringBuilder.asSeveral(columnPrinter)));
		assertThat(tableB.getForeignKeys())
				// Objects are similar but not equals so we compare them through their footprint (truly comparing them is quite hard)
				.usingElementComparator(Comparator.comparing(fkPrinter))
				.containsExactly(new ForeignKey("FK_Vehicle_id_AbstractVehicle_id", tableB.getColumn("id"), mainTable.getColumn("id")));
		assertThat(tableC.getForeignKeys())
				// Objects are similar but not equals so we compare them through their footprint (truly comparing them is quite hard)
				.usingElementComparator(Comparator.comparing(fkPrinter))
				.containsExactly(new ForeignKey("FK_Car_id_Vehicle_id", tableC.getColumn("id"), tableB.getColumn("id")));
	}
}
