package com.github.arteam.jdit;

import com.github.arteam.jdit.annotations.JditProperties;
import com.github.arteam.jdit.maintenance.DatabaseMaintenance;
import com.github.arteam.jdit.maintenance.DatabaseMaintenanceFactory;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

/**
 * <p>
 * Tests runner which:
 * <ul>
 * <li>Injects DBI related tested instances to the tests.
 * <p>Supports {@link Handle}, {@link Jdbi}, SQLObject and DBI DAO.
 * </li>
 * <li>Injects data to the DB from a script for a specific
 * method or a test</li>
 * <li>Sweeps data from the DB after every tests, so every
 * test starts with an empty schema</li>
 * </ul>
 */
public class DBIRunner extends BlockJUnit4ClassRunner {

    private DatabaseMaintenance databaseMaintenance;
    private TestObjectsInjector injector;
    private DataSetInjector dataSetInjector;
    private Class<?> klass;

    public DBIRunner(Class<?> klass) throws InitializationError {
        super(klass);
        this.klass = klass;
    }

    @Override
    protected Object createTest() throws Exception {
        Object test = super.createTest();
        // Now we can inject tested instances to the current test
        injector.injectTestedInstances(test);
        return test;
    }

    @Override
    protected Statement classBlock(RunNotifier notifier) {
        final Statement statement = super.classBlock(notifier);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                // Open a new handle for every test
                // It affords to avoid creating a static state which makes tests more independent
                JditProperties jditProperties = klass.getAnnotation(JditProperties.class);
                Jdbi dbi = jditProperties != null ? DBIContextFactory.getDBI(jditProperties.value()) : DBIContextFactory.getDBI();
                try (Handle handle = dbi.open()) {
                    injector = new TestObjectsInjector(dbi, handle);
                    databaseMaintenance = DatabaseMaintenanceFactory.create(handle);
                    dataSetInjector = new DataSetInjector(new DataMigration(handle));
                    statement.evaluate();
                }
            }
        };
    }

    @Override
    protected Statement methodBlock(final FrameworkMethod method) {
        final Statement statement = super.methodBlock(method);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    dataSetInjector.injectData(method.getMethod());
                    statement.evaluate();
                } finally {
                    // Sweep event if there is an error during injecting data
                    databaseMaintenance.sweepData();
                }
            }
        };
    }
}