package com.commercetools.pspadapter.payone;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobDetail;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.commercetools.pspadapter.payone.ScheduledJobFactory.DELAY_START_IN_SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ScheduledJobFactoryTest {

    @Mock
    private IntegrationService integrationService;

    private ScheduledJobFactory scheduledJobFactory;

    @Before
    public void setUp() throws Exception {
        scheduledJobFactory = new ScheduledJobFactory();
    }

    @After
    public void tearDown() throws Exception {
        scheduledJobFactory.shutdown(false);
    }

    @Test(timeout=70000)
    public void jobIsExecuted4TimesInMinute() throws Exception {
        when(integrationService.getTenantFactories()).thenReturn(Collections.emptyList());

        // schedule to execute every 15 seconds and wait 60 seconds
        JobDetail scheduledJob = scheduledJobFactory.createScheduledJob(
                CronScheduleBuilder.cronSchedule("0/15 * * * * ? *"),
                ScheduledJobShortTimeframe.class,
                integrationService);

        Thread.sleep(62000); // +2 second gap to avoid the test fail on short time lags

        // verify the job called integrationService.getTenantFactories() 4 or 5 times in 1 minute
        verify(integrationService, atLeast(4)).getTenantFactories();

        // 5 times is possible when the job is started at 14, 29, 44 or 59 seconds,
        // then 2 seconds gap above will make one additional overlap on 15, 30, 45 or 00 seconds
        verify(integrationService, atMost(5)).getTenantFactories();

        scheduledJobFactory.deleteScheduledJob(scheduledJob.getKey());
    }

    @Test(timeout=10000)
    public void setAllScheduledItemsStartedListener_executed() throws Exception {
        JobDetail scheduledJob1 = scheduledJobFactory.createScheduledJob(
                CronScheduleBuilder.cronSchedule("0/15 * * * * ? *"),
                ScheduledJobShortTimeframe.class,
                integrationService);

        JobDetail scheduledJob2 = scheduledJobFactory.createScheduledJob(
                CronScheduleBuilder.cronSchedule("5 0 0/1 * * ? *"),
                ScheduledJobLongTimeframe.class,
                integrationService);

        // because value in the closure should be final, we use this mutable wrapper
        final AtomicBoolean allJobsRun = new AtomicBoolean(false);

        scheduledJobFactory.setAllScheduledItemsStartedListener(() -> {
            // when all jobs are run - set allJobsRun to true and notify the waiting thread.
            synchronized (this) {
                allJobsRun.set(true);
                this.notify();
            }
        });

        // wait at most DELAY_START_IN_SECONDS to start 2 scheduled jobs
        while (!allJobsRun.get()) {
            // +1 second to avoid lags effect
            synchronized (this) {
                this.wait((DELAY_START_IN_SECONDS + 1) * 1000);
            }
        }

        assertThat(allJobsRun.get()).isTrue();

        scheduledJobFactory.deleteScheduledJob(scheduledJob1.getKey());
        scheduledJobFactory.deleteScheduledJob(scheduledJob2.getKey());
    }

}


@@ -283,16 +283,73 @@
 bDetail;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0Aimport
@@ -804,16 +804,105 @@
 ctory;%0A%0A
+    private static Logger LOG = LoggerFactory.getLogger(ScheduledJobFactoryTest.class);%0A%0A
     @Bef
@@ -1143,9 +1143,9 @@
 out=
-7
+4
 0000
@@ -1179,22 +1179,24 @@
 uted
-4
+3
 TimesIn
-Minute
+30Second
 () t
@@ -1343,17 +1343,17 @@
  every 1
-5
+0
  seconds
@@ -1362,17 +1362,23 @@
 nd wait 
-6
+about 3
 0 second
@@ -1497,33 +1497,33 @@
 ronSchedule(%220/1
-5
+0
  * * * * ? *%22),%0A
@@ -1602,32 +1602,95 @@
 ationService);%0A%0A
+        LOG.info(%22Wait 30 seconds to allow the job execute%22);%0A%0A
         Thread.s
@@ -1698,9 +1698,9 @@
 eep(
-6
+3
 2000
@@ -1762,16 +1762,56 @@
 e lags%0A%0A
+        LOG.info(%22Waited 30 seconds%22);%0A%0A
         
@@ -1875,22 +1875,22 @@
 ories() 
-4
+3
  or 
-5
+4
  times i
@@ -1895,16 +1895,18 @@
  in 
-1 minute
+30 seconds
 %0A   
@@ -1945,17 +1945,17 @@
 atLeast(
-4
+3
 )).getTe
@@ -1983,17 +1983,17 @@
      // 
-5
+4
  times i
@@ -2034,21 +2034,20 @@
  at 
-14, 29, 44 or
+09, 19, ...,
  59 
@@ -2127,27 +2127,34 @@
 rlap
- on 15, 30, 45 or 0
+ped call at 40, 50, ..., 3
 0 se
@@ -2201,17 +2201,17 @@
  atMost(
-5
+4
 )).getTe
@@ -3335,46 +3335,93 @@
     
-// wait at most DELAY_START_IN_SECONDS
+final int waitTimeout = DELAY_START_IN_SECONDS + 1;%0A        LOG.info(%22Wait at most %7B%7D
  to 
@@ -3442,16 +3442,32 @@
 led jobs
+%22, waitTimeout);
 %0A       
@@ -3606,34 +3606,19 @@
 it((
-DELAY_START_IN_SECONDS + 1
+waitTimeout
 ) * 

add logs and make scheduled jobs factory tests shorter