package org.infinispan.partitionhandling;

import jakarta.transaction.Transaction;
import jakarta.transaction.TransactionManager;

import org.infinispan.Cache;
import org.infinispan.commands.tx.RollbackCommand;
import org.infinispan.commands.tx.TransactionBoundaryCommand;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.testng.annotations.Test;

/**
 * It tests multiple scenarios where a split can happen during a transaction.
 *
 * @author Pedro Ruivo
 * @since 8.0
 */
@Test(groups = "functional", testName = "partitionhandling.PessimisticTxPartitionAndMergeDuringRollbackTest")
public class PessimisticTxPartitionAndMergeDuringRollbackTest extends BasePessimisticTxPartitionAndMergeTest {

   private static final Log log = LogFactory.getLog(PessimisticTxPartitionAndMergeDuringRollbackTest.class);

   public void testDegradedPartitionWithDiscard() throws Exception {
      doTest(SplitMode.BOTH_DEGRADED, true, true);
   }

   public void testDegradedPartition() throws Exception {
      doTest(SplitMode.BOTH_DEGRADED, true, false);
   }

   @Test(groups = "unstable", description = "https://issues.jboss.org/browse/ISPN-8232")
   public void testOriginatorIsolatedPartitionWithDiscard() throws Exception {
      doTest(SplitMode.ORIGINATOR_ISOLATED, true, true);
   }

   public void testOriginatorIsolatedPartition() throws Exception {
      doTest(SplitMode.ORIGINATOR_ISOLATED, true, false);
   }

   @Test(groups = "unstable", description = "https://issues.jboss.org/browse/ISPN-8232")
   public void testPrimaryOwnerIsolatedPartitionWithDiscard() throws Exception {
      doTest(SplitMode.PRIMARY_OWNER_ISOLATED, true, true);
   }

   public void testPrimaryOwnerIsolatedPartition() throws Exception {
      doTest(SplitMode.PRIMARY_OWNER_ISOLATED, true, false);
   }

   public void testSplitBeforeRollback() throws Exception {
      //split happens before the commit() or rollback(). Locks are acquired
      waitForClusterToForm(PESSIMISTIC_TX_CACHE_NAME);

      final KeyInfo keyInfo = createKeys(PESSIMISTIC_TX_CACHE_NAME);
      final Cache<Object, String> originator = cache(0, PESSIMISTIC_TX_CACHE_NAME);

      final TransactionManager transactionManager = originator.getAdvancedCache().getTransactionManager();
      transactionManager.begin();
      keyInfo.putFinalValue(originator);
      final Transaction transaction = transactionManager.suspend();

      SplitMode.BOTH_DEGRADED.split(this);

      transactionManager.resume(transaction);
      transactionManager.rollback();

      assertEventuallyNotLocked(cache(1, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey1());
      assertLocked(cache(2, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey2());

      mergeCluster(PESSIMISTIC_TX_CACHE_NAME);
      finalAsserts(PESSIMISTIC_TX_CACHE_NAME, keyInfo, INITIAL_VALUE);
   }

   @Override
   protected void checkLocksDuringPartition(SplitMode splitMode, KeyInfo keyInfo, boolean discard) {
      if (splitMode == SplitMode.ORIGINATOR_ISOLATED) {
         if (discard) {
            //rollback is never received.
            assertLocked(cache(1, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey1());
         } else {
            //rollback can be delivered
            assertEventuallyNotLocked(cache(1, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey1());
         }
      } else {
         //key is unlocked because the rollback is always received in cache1
         assertEventuallyNotLocked(cache(1, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey1());
      }
      if (discard) {
         //rollback never received, so key is locked until the merge occurs.
         assertLocked(cache(2, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey2());
      } else {
         //rollback received, so key is unlocked
         assertEventuallyNotLocked(cache(2, PESSIMISTIC_TX_CACHE_NAME), keyInfo.getKey2());
      }
   }

   @Override
   protected boolean forceRollback() {
      return true;
   }

   @Override
   protected Class<? extends TransactionBoundaryCommand> getCommandClass() {
      return RollbackCommand.class;
   }

   @Override
   protected Log getLog() {
      return log;
   }
}
