package net.joelinn.quartz;

import org.junit.Test;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;

import java.util.*;

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assert.*;

/**
 * Joe Linn
 * 7/15/2014
 */
public class StoreJobTest extends BaseTest{

    @Test
    public void storeJob() throws Exception {
        JobDetail testJob = getJobDetail();

        jobStore.storeJob(testJob, false);

        // ensure that the job was stored properly
        String jobHashKey = schema.jobHashKey(testJob.getKey());
        Map<String, String> jobMap = jedis.hgetAll(jobHashKey);

        assertNotNull(jobMap);
        assertThat(jobMap, hasKey("name"));
        assertEquals("testJob", jobMap.get("name"));

        Map<String, String> jobData = jedis.hgetAll(schema.jobDataMapHashKey(testJob.getKey()));
        assertNotNull(jobData);
        assertThat(jobData, hasKey("timeout"));
        assertEquals("42", jobData.get("timeout"));

        // ensure that job data which is not included in the current map is removed from Redis
        testJob.getJobDataMap().remove("timeout");
        testJob.getJobDataMap().put("foo", "bar");
        jobStore.storeJob(testJob, true);
        jobData = jedis.hgetAll(schema.jobDataMapHashKey(testJob.getKey()));
        assertNotNull(jobData);
        assertThat(jobData, not(hasKey("timeout")));
        assertThat(jobData, hasKey("foo"));
    }

    @Test(expected = ObjectAlreadyExistsException.class)
    public void storeJobNoReplace() throws Exception {
        jobStore.storeJob(getJobDetail(), false);
        jobStore.storeJob(getJobDetail(), false);
    }

    @Test
    public void storeJobWithReplace() throws Exception {
        jobStore.storeJob(getJobDetail(), true);
        jobStore.storeJob(getJobDetail(), true);
    }

    @Test
    public void retrieveJob() throws Exception {
        JobDetail testJob = getJobDetail();
        jobStore.storeJob(testJob, false);

        // retrieve the job
        JobDetail retrievedJob = jobStore.retrieveJob(testJob.getKey());

        assertEquals(testJob.getJobClass(), retrievedJob.getJobClass());
        assertEquals(testJob.getDescription(), retrievedJob.getDescription());
        JobDataMap retrievedJobJobDataMap = retrievedJob.getJobDataMap();
        for (String key : testJob.getJobDataMap().keySet()) {
            assertThat(retrievedJobJobDataMap, hasKey(key));
            assertEquals(String.valueOf(testJob.getJobDataMap().get(key)), retrievedJobJobDataMap.get(key));
        }
    }

    @Test
    public void retrieveNonExistentJob() throws Exception {
        assertThat(jobStore.retrieveJob(new JobKey("foo", "bar")), nullValue());
    }

    @Test
    public void removeJob() throws Exception {
        // attempt to remove a non-existent job
        assertFalse(jobStore.removeJob(JobKey.jobKey("foo", "bar")));

        // create and store a job with multiple triggers
        JobDetail job = getJobDetail("job1", "jobGroup1");
        CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
        CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
        Set<Trigger> triggersSet = new HashSet<>();
        triggersSet.add(trigger1);
        triggersSet.add(trigger2);
        Map<JobDetail, Set<? extends Trigger>> jobsAndTriggers = new HashMap<>();
        jobsAndTriggers.put(job, triggersSet);
        jobStore.storeJobsAndTriggers(jobsAndTriggers, false);

        assertTrue(jobStore.removeJob(job.getKey()));

        // ensure that the job and all of its triggers were removed
        assertThat(jobStore.retrieveJob(job.getKey()), nullValue());
        assertThat(jobStore.retrieveTrigger(trigger1.getKey()), nullValue());
        assertThat(jobStore.retrieveTrigger(trigger2.getKey()), nullValue());
        assertThat(jedis.get(schema.triggerHashKey(trigger1.getKey())), nullValue());
    }

    @Test
    public void removeJobs() throws Exception {
        // create and store some jobs with triggers
        Map<JobDetail, Set<? extends Trigger>> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
        jobStore.storeJobsAndTriggers(jobsAndTriggers, false);

        List<JobKey> removeKeys = new ArrayList<>(2);
        for (JobDetail jobDetail : new ArrayList<>(jobsAndTriggers.keySet()).subList(0, 1)) {
            removeKeys.add(jobDetail.getKey());
        }

        assertTrue(jobStore.removeJobs(removeKeys));

        // ensure that only the proper jobs were removed
        for (JobKey removeKey : removeKeys) {
            assertThat(jobStore.retrieveJob(removeKey), nullValue());
        }
        for (JobDetail jobDetail : new ArrayList<>(jobsAndTriggers.keySet()).subList(1, 3)) {
            assertThat(jobStore.retrieveJob(jobDetail.getKey()), not(nullValue()));
        }
    }

    @Test
    public void getNumberOfJobs() throws Exception {
        jobStore.storeJob(getJobDetail("job1", "group1"), false);
        jobStore.storeJob(getJobDetail("job2", "group1"), false);
        jobStore.storeJob(getJobDetail("job3", "group2"), false);

        int numberOfJobs = jobStore.getNumberOfJobs();

        assertEquals(3, numberOfJobs);
    }

    @Test
    public void getJobKeys() throws Exception {
        jobStore.storeJob(getJobDetail("job1", "group1"), false);
        jobStore.storeJob(getJobDetail("job2", "group1"), false);
        jobStore.storeJob(getJobDetail("job3", "group2"), false);

        Set<JobKey> jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupEquals("group1"));

        assertThat(jobKeys, hasSize(2));
        assertThat(jobKeys, containsInAnyOrder(new JobKey("job1", "group1"), new JobKey("job2", "group1")));

        jobStore.storeJob(getJobDetail("job4", "awesomegroup1"), false);

        jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupContains("group"));

        assertThat(jobKeys, hasSize(4));
        assertThat(jobKeys, containsInAnyOrder(new JobKey("job1", "group1"), new JobKey("job2", "group1"),
                new JobKey("job4", "awesomegroup1"), new JobKey("job3", "group2")));

        jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupStartsWith("awe"));

        assertThat(jobKeys, hasSize(1));
        assertThat(jobKeys, containsInAnyOrder(new JobKey("job4", "awesomegroup1")));

        jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupEndsWith("1"));

        assertThat(jobKeys, hasSize(3));
        assertThat(jobKeys, containsInAnyOrder(new JobKey("job1", "group1"), new JobKey("job2", "group1"),
                new JobKey("job4", "awesomegroup1")));
    }

    @Test
    public void getJobGroupNames() throws Exception {
        List<String> jobGroupNames = jobStore.getJobGroupNames();

        assertThat(jobGroupNames, not(nullValue()));
        assertThat(jobGroupNames, hasSize(0));

        jobStore.storeJob(getJobDetail("job1", "group1"), false);
        jobStore.storeJob(getJobDetail("job2", "group1"), false);
        jobStore.storeJob(getJobDetail("job3", "group2"), false);

        jobGroupNames = jobStore.getJobGroupNames();

        assertThat(jobGroupNames, hasSize(2));
        assertThat(jobGroupNames, containsInAnyOrder("group1", "group2"));
    }

    @Test
    public void pauseJob() throws Exception {
        // create and store a job with multiple triggers
        JobDetail job = getJobDetail("job1", "jobGroup1");
        CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
        CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
        Set<Trigger> triggersSet = new HashSet<>();
        triggersSet.add(trigger1);
        triggersSet.add(trigger2);
        Map<JobDetail, Set<? extends Trigger>> jobsAndTriggers = new HashMap<>();
        jobsAndTriggers.put(job, triggersSet);
        jobStore.storeJobsAndTriggers(jobsAndTriggers, false);

        // pause the job
        jobStore.pauseJob(job.getKey());

        // ensure that the job's triggers were paused
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
    }

    @Test
    public void pauseJobsEquals() throws Exception {
        // create and store some jobs with triggers
        Map<JobDetail, Set<? extends Trigger>> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
        jobStore.storeJobsAndTriggers(jobsAndTriggers, false);

        // pause jobs from one of the groups
        String pausedGroupName = new ArrayList<>(jobsAndTriggers.keySet()).get(0).getKey().getGroup();
        jobStore.pauseJobs(GroupMatcher.jobGroupEquals(pausedGroupName));

        // ensure that the appropriate triggers have been paused
        for (Map.Entry<JobDetail, Set<? extends Trigger>> entry : jobsAndTriggers.entrySet()) {
            for (Trigger trigger : entry.getValue()) {
                if(entry.getKey().getKey().getGroup().equals(pausedGroupName)){
                    Trigger.TriggerState triggerState = jobStore.getTriggerState(trigger.getKey());
                    assertEquals(Trigger.TriggerState.PAUSED, triggerState);
                }
                else{
                    assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
                }
            }
        }
    }

    @Test
    public void pauseJobsStartsWith() throws Exception {
        JobDetail job1 = getJobDetail("job1", "jobGroup1");
        storeJobAndTriggers(job1, getCronTrigger("trigger1", "triggerGroup1", job1.getKey()), getCronTrigger("trigger2", "triggerGroup1", job1.getKey()));
        JobDetail job2 = getJobDetail("job2", "yobGroup1");
        CronTriggerImpl trigger3 = getCronTrigger("trigger3", "triggerGroup3", job2.getKey());
        CronTriggerImpl trigger4 = getCronTrigger("trigger4", "triggerGroup4", job2.getKey());
        storeJobAndTriggers(job2, trigger3, trigger4);

        // pause jobs with groups beginning with "yob"
        Collection<String> pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupStartsWith("yob"));
        assertThat(pausedJobs, hasSize(1));
        assertThat(pausedJobs, containsInAnyOrder("yobGroup1"));

        // ensure that the job was added to the paused jobs set
        assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job2.getKey())));

        // ensure that the job's triggers have been paused
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger3.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger4.getKey()));
    }

    @Test
    public void pauseJobsEndsWith() throws Exception {
        JobDetail job1 = getJobDetail("job1", "jobGroup1");
        CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job1.getKey());
        CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job1.getKey());
        storeJobAndTriggers(job1, trigger1, trigger2);
        JobDetail job2 = getJobDetail("job2", "yobGroup2");
        CronTriggerImpl trigger3 = getCronTrigger("trigger3", "triggerGroup3", job2.getKey());
        CronTriggerImpl trigger4 = getCronTrigger("trigger4", "triggerGroup4", job2.getKey());
        storeJobAndTriggers(job2, trigger3, trigger4);

        // pause job groups ending with "1"
        Collection<String> pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupEndsWith("1"));
        assertThat(pausedJobs, hasSize(1));
        assertThat(pausedJobs, containsInAnyOrder("jobGroup1"));

        // ensure that the job was added to the paused jobs set
        assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job1.getKey())));

        // ensure that the job's triggers have been paused
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
    }

    @Test
    public void pauseJobsContains() throws Exception {
        JobDetail job1 = getJobDetail("job1", "jobGroup1");
        CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job1.getKey());
        CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job1.getKey());
        storeJobAndTriggers(job1, trigger1, trigger2);
        JobDetail job2 = getJobDetail("job2", "yobGroup2");
        CronTriggerImpl trigger3 = getCronTrigger("trigger3", "triggerGroup3", job2.getKey());
        CronTriggerImpl trigger4 = getCronTrigger("trigger4", "triggerGroup4", job2.getKey());
        storeJobAndTriggers(job2, trigger3, trigger4);

        // Pause job groups containing "foo". Should result in no jobs being paused.
        Collection<String> pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupContains("foo"));
        assertThat(pausedJobs, hasSize(0));

        // pause jobs containing "Group"
        pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupContains("Group"));
        assertThat(pausedJobs, hasSize(2));
        assertThat(pausedJobs, containsInAnyOrder("jobGroup1", "yobGroup2"));

        // ensure that both jobs were added to the paused jobs set
        assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job1.getKey())));
        assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job2.getKey())));

        // ensure that all triggers were paused
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger3.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger4.getKey()));
    }

    @Test
    public void resumeJob() throws Exception {
        // create and store a job with multiple triggers
        JobDetail job = getJobDetail("job1", "jobGroup1");
        CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
        CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
        storeJobAndTriggers(job, trigger1, trigger2);

        // pause the job
        jobStore.pauseJob(job.getKey());

        // ensure that the job's triggers have been paused
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
        assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));

        // resume the job
        jobStore.resumeJob(job.getKey());

        // ensure that the triggers have been resumed
        assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger1.getKey()));
        assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger2.getKey()));
    }

    @Test
    public void resumeJobsEquals() throws Exception {
        // attempt to resume jobs for a non-existent job group
        Collection<String> resumedJobGroups = jobStore.resumeJobs(GroupMatcher.jobGroupEquals("foobar"));
        assertThat(resumedJobGroups, hasSize(0));

        // store some jobs with triggers
        Map<JobDetail, Set<? extends Trigger>> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
        jobStore.storeJobsAndTriggers(jobsAndTriggers, false);

        // pause one of the job groups
        String pausedGroupName = new ArrayList<>(jobsAndTriggers.keySet()).get(0).getKey().getGroup();
        jobStore.pauseJobs(GroupMatcher.jobGroupEquals(pausedGroupName));

        // ensure that the appropriate triggers have been paused
        for (Map.Entry<JobDetail, Set<? extends Trigger>> entry : jobsAndTriggers.entrySet()) {
            for (Trigger trigger : entry.getValue()) {
                if(entry.getKey().getKey().getGroup().equals(pausedGroupName)){
                    Trigger.TriggerState triggerState = jobStore.getTriggerState(trigger.getKey());
                    assertEquals(Trigger.TriggerState.PAUSED, triggerState);
                }
                else{
                    assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
                }
            }
        }

        // resume the paused jobs
        resumedJobGroups = jobStore.resumeJobs(GroupMatcher.jobGroupEquals(pausedGroupName));

        assertThat(resumedJobGroups, hasSize(1));
        assertEquals(pausedGroupName, new ArrayList<>(resumedJobGroups).get(0));

        for (Trigger trigger : new ArrayList<>(jobsAndTriggers.entrySet()).get(0).getValue()) {
            assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
        }
    }

    @Test
    public void resumeJobsEndsWith() throws Exception {
        Map<JobDetail, Set<? extends Trigger>> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
        jobStore.storeJobsAndTriggers(jobsAndTriggers, false);

        // pause one of the job groups
        String pausedGroupName = new ArrayList<>(jobsAndTriggers.keySet()).get(0).getKey().getGroup();
        String substring = pausedGroupName.substring(pausedGroupName.length() - 1, pausedGroupName.length());
        Collection<String> pausedGroups = jobStore.pauseJobs(GroupMatcher.jobGroupEndsWith(substring));

        assertThat(pausedGroups, hasSize(1));
        assertThat(pausedGroups, containsInAnyOrder(pausedGroupName));

        // resume the paused jobs
        Collection<String> resumedGroups = jobStore.resumeJobs(GroupMatcher.jobGroupEndsWith(substring));

        assertThat(resumedGroups, hasSize(1));
        assertThat(resumedGroups, containsInAnyOrder(pausedGroupName));

        for (Trigger trigger : new ArrayList<>(jobsAndTriggers.entrySet()).get(0).getValue()) {
            assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
        }
    }
}