package org.infinispan.query.distributed;

import static org.testng.AssertJUnit.assertEquals;

import javax.transaction.TransactionManager;

import org.apache.lucene.search.MatchAllDocsQuery;
import org.hibernate.search.spi.SearchIntegrator;
import org.infinispan.Cache;
import org.infinispan.query.CacheQuery;
import org.infinispan.query.Search;
import org.infinispan.query.SearchManager;
import org.infinispan.query.helper.StaticTestingErrorHandler;
import org.infinispan.query.helper.TestableCluster;
import org.infinispan.query.indexmanager.InfinispanIndexManager;
import org.infinispan.query.test.Person;
import org.infinispan.test.AbstractInfinispanTest;
import org.testng.annotations.Test;

/**
 * Configures the Hibernate Search backend to use Infinispan custom commands as a backend
 * transport, and a consistent hash for Master election for each index.
 * The test changes the view several times while indexing and verifying index state.
 *
 * @author Sanne Grinovero
 */
@Test(groups = "functional", testName = "query.distributed.MultiNodeDistributedTest")
public class MultiNodeDistributedTest extends AbstractInfinispanTest {

   protected final TestableCluster<String,Person> cluster = new TestableCluster<>(getConfigurationResourceName());

   protected String getConfigurationResourceName() {
      return "dynamic-indexing-distribution.xml";
   }

   protected void storeOn(Cache<String, Person> cache, String key, Person person) throws Exception {
      TransactionManager transactionManager = cache.getAdvancedCache().getTransactionManager();
      if (transactionsEnabled()) transactionManager.begin();
      cache.put(key, person);
      if (transactionsEnabled()) transactionManager.commit();
   }

   @Test(groups = "unstable", description = "ISPN-8076")
   public void testIndexingWorkDistribution() throws Exception {
      try {
         cluster.startNewNode();
         cluster.startNewNode();
         assertIndexSize(0);
         //depending on test run, the index master selection might pick either cache.
         //We don't know which cache it picks, but we allow writing & searching on all.
         storeOn(cluster.getCache(0), "k1", new Person("K. Firt", "Is not a character from the matrix", 1));
         assertIndexSize(1);
         storeOn(cluster.getCache(1), "k2", new Person("K. Seycond", "Is a pilot", 1));
         assertIndexSize(2);
         storeOn(cluster.getCache(0), "k3", new Person("K. Theerd", "Forgot the fundamental laws", 1));
         assertIndexSize(3);
         storeOn(cluster.getCache(1), "k3", new Person("K. Overide", "Impersonating Mr. Theerd", 1));
         assertIndexSize(3);
         cluster.startNewNode();
         storeOn(cluster.getCache(2), "k4", new Person("K. Forth", "Dynamic Topology!", 1));
         assertIndexSize(4);
         cluster.startNewNode();
         assertIndexSize(4);
         killMasterNode();
         //After a node kill, a stale lock might not be immediately resolved.
         //Solicit resolution by issues at least three writes:
         storeOn(cluster.getCache(2), "k5", new Person("K. Vife", "Gets stuck in a buffer", 1));
         storeOn(cluster.getCache(2), "k6", new Person("K. Seix", "Fills the buffer", 1));
         storeOn(cluster.getCache(2), "k7", new Person("K. Vife", "Failover!", 1));
         assertIndexSize(7);
      }
      finally {
         cluster.killAll();
      }
   }

   protected void killMasterNode() {
      for (Cache<String,Person> cache : cluster.iterateAllCaches()) {
         if (isMasterNode(cache)) {
            cluster.killNode(cache);
            break;
         }
      }
   }

   private boolean isMasterNode(Cache<?,?> cache) {
      //Implicitly verifies the components are setup as configured by casting:
      SearchManager searchManager = Search.getSearchManager(cache);
      SearchIntegrator searchFactory = searchManager.unwrap(SearchIntegrator.class);
      InfinispanIndexManager indexManager = (InfinispanIndexManager) searchFactory.getIndexManager("person");
      return indexManager.isMasterLocal();
   }

   protected void assertIndexSize(int expectedIndexSize) {
      for (Cache cache : cluster.iterateAllCaches()) {
         StaticTestingErrorHandler.assertAllGood(cache);
         SearchManager searchManager = Search.getSearchManager(cache);
         CacheQuery<Person> query = searchManager.getQuery(new MatchAllDocsQuery(), Person.class);
         assertEquals(expectedIndexSize, query.list().size());
      }
   }

   protected boolean transactionsEnabled() {
      return false;
   }

}
