package org.testfun.jee;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.testfun.jee.runner.PersistenceXml;
import org.testfun.jee.runner.SingletonDataSource;
import static org.hamcrest.CoreMatchers.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.sql.*;
import java.util.List;

import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.*;

@RunWith(EjbWithMockitoRunner.class)
public class EjbWithMockitoRunnerTransactionTest {

    private static boolean rowAdded;

    @PersistenceContext
    private EntityManager entityManager;

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @BeforeClass
    public static void createTables() throws SQLException {
        dropTables();
        try (
                Connection connection = DriverManager.getConnection(PersistenceXml.getInstnace().getConnectionUrl());
                Statement statement = connection.createStatement()
        ) {
            statement.execute("CREATE TABLE duplicates (id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE)");
        }

        rowAdded = false;
    }

    @AfterClass
    public static void dropTables() throws SQLException {
        // Must rollback the connection used by the entity manager in order to avoid locks due to inserts done before (recall transactions are never committed).
        SingletonDataSource.getDataSource().getConnection().rollback();

        try (
                Connection connection = DriverManager.getConnection(PersistenceXml.getInstnace().getConnectionUrl());
                Statement statement = connection.createStatement()
        ) {
            statement.execute("DROP TABLE IF EXISTS duplicates");
        }
    }

    @Test
    public void test1() {
        firstAddRowNextVerify();
    }

    @Test
    public void test2() {
        firstAddRowNextVerify();
    }

    @Test
    public void dbThrowDuplicateKey() {
        entityManager.persist(new Duplicates("kuki"));

        try {
            entityManager.persist(new Duplicates("kuki"));
        } catch (Throwable t) {
            assertThat(ExceptionUtils.getRootCauseMessage(t), containsString("unique constraint or index violation"));
        }
    }

    @Test
    public void preUpdate() {
        Duplicates entity = new Duplicates("kuki");
        entityManager.persist(entity);

        assertNull(entity.getDuplicateName());

        entity.setName("puki");
        entityManager.flush();

        assertEquals(entity.getName(), entity.getDuplicateName());
    }

    /**
     * This method is used for testing that a transaction is rolled back at the end of a test.
     * Because the rollback occurs after all @After methods are invoked, the test cannot be done using @After.
     * Also, there are no guarantees as for the order test methods are called.
     * This method solves the problem by performing "insert" on the first time it is called and "verify" on the second
     * time.
     */
    private void firstAddRowNextVerify() {
        if (rowAdded) {
            // If a row was already added by a previous run, verify it was rolled back.
            List rows = entityManager.createQuery("FROM Duplicates AS dups").getResultList();
            assertEquals(0, rows.size());
        }

        else {
            // If no row was added so far (its the first call to this method) than add a row.
            Duplicates row = new Duplicates("kuki");
            entityManager.persist(row);
            assertThat(row.getId(), not(0));

            assertSame(row, entityManager.find(Duplicates.class, row.getId()));

            rowAdded = true;
        }
    }

}
