package io.smartcat.migration;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import org.cassandraunit.CQLDataLoader;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;

import io.smartcat.migration.MigrationEngine.Migrator;
import io.smartcat.migration.migrations.schema.AddBookGenreFieldMigration;
import io.smartcat.migration.migrations.schema.AddBookISBNFieldMigration;

public class MigratorTest extends BaseTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(MigratorTest.class);

    private static final String CONTACT_POINT = "localhost";
    private static final int PORT = 9142;
    private static final String KEYSPACE = "migration_test_books";
    private static final String CQL = "books.cql";

    private static Session session;
    private static Cluster cluster;

    private CassandraVersioner versioner;
    private Migrator migrator;

    private CassandraMetadataAnalyzer metadataAnalyzer;

    @BeforeClass
    public static void init() throws Exception {
        LOGGER.info("Starting embedded cassandra server");
        EmbeddedCassandraServerHelper.startEmbeddedCassandra("another-cassandra.yaml");

        LOGGER.info("Connect to embedded db");
        cluster = Cluster.builder().addContactPoints(CONTACT_POINT).withPort(PORT).build();
        session = cluster.connect();
    }

    @Before
    public void setUp() throws Exception {
        LOGGER.info("Initialize keyspace");
        final CQLDataLoader cqlDataLoader = new CQLDataLoader(session);
        cqlDataLoader.load(new ClassPathCQLDataSet(CQL, false, true, KEYSPACE));

        versioner = new CassandraVersioner(session);
        migrator = MigrationEngine.withSession(session);
        metadataAnalyzer = new CassandraMetadataAnalyzer(session);
    }

    @AfterClass
    public static void tearDown() {
        if (cluster != null) {
            cluster.close();
            cluster = null;
        }
    }

    @Test
    public void executeOneMigration() throws Exception {
        assertTableDoesntContainsColumns("books", "genre");

        final MigrationResources resources = new MigrationResources();
        resources.addMigration(new AddBookGenreFieldMigration(1));

        migrator.migrate(resources);

        assertTableContainsColumns("books", "genre");
    }

    @Test
    public void executeTwoMigrations() throws Exception {
        assertTableDoesntContainsColumns("books", "genre", "isbn");

        final MigrationResources resources = new MigrationResources();
        resources.addMigration(new AddBookGenreFieldMigration(1));
        resources.addMigration(new AddBookISBNFieldMigration(2));

        migrator.migrate(resources);

        assertTableContainsColumns("books", "genre", "isbn");
    }

    @Test
    public void updateVersionAfterMigration() throws Exception {
        int versionBeforeMigration = getCurrentVersion();

        Migration migration = new AddBookGenreFieldMigration(1);
        final MigrationResources resources = new MigrationResources();
        resources.addMigration(migration);

        migrator.migrate(resources);

        // verify
        assertThat(getCurrentVersion(), not(is(versionBeforeMigration)));
        assertThat(getCurrentVersion(), is(migration.getVersion()));
    }

    @Test
    public void skipMigrationWithVersionOlderThanCurrentSchemaVersion() throws Exception {
        Migration migrationWithNewerVersion = new AddBookGenreFieldMigration(2);
        Migration migrationWithOlderVersion = new AddBookISBNFieldMigration(1);

        final MigrationResources resources = new MigrationResources();
        resources.addMigration(migrationWithNewerVersion);
        resources.addMigration(migrationWithOlderVersion);

        migrator.migrate(resources);

        // verify
        assertThat(getCurrentVersion(), is(migrationWithNewerVersion.getVersion()));
        assertTableDoesntContainsColumns("books", "isbn");
    }

    @Test
    public void skipMigrationWithSameVersionThanCurrentSchemaVersion() throws Exception {
        int versionBeforeMigration = getCurrentVersion();

        final MigrationResources resources = new MigrationResources();
        resources.addMigration(new AddBookGenreFieldMigration(versionBeforeMigration));

        migrator.migrate(resources);

        // verify
        assertThat(getCurrentVersion(), is(versionBeforeMigration));
    }

    private int getCurrentVersion() {
        return versioner.getCurrentVersion(MigrationType.SCHEMA);
    }

    private void assertTableDoesntContainsColumns(String table, String... columns) {
        for (String column : columns) {
            assertFalse(metadataAnalyzer.columnExistInTable(column, table));
        }
    }

    private void assertTableContainsColumns(String table, String... columns) {
        for (String column : columns) {
            assertTrue(metadataAnalyzer.columnExistInTable(column, table));
        }
    }

}
