package app.mealsmadeeasy.api.job;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;

import java.util.Map;
import java.util.Optional;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class JobServiceTests {

    @Mock
    private JobRepository jobRepository;

    @Mock
    private ApplicationContext applicationContext;

    @Spy
    private ObjectMapper objectMapper;

    @InjectMocks
    private JobService jobService;

    @Test
    public void whenRunOneJob_callsClaimNext() {
        when(this.jobRepository.claimNext(anyString())).thenReturn(Optional.empty());
        this.jobService.runOneJob();
        verify(this.jobRepository).claimNext(anyString());
    }

    @Test
    public void findsJobHandlerAndSavesJobInfo() {
        final Job testJob = new Job();
        testJob.setJobKey("TEST_JOB_KEY");

        @SuppressWarnings("unchecked")
        final JobHandler<Object> testJobHandler = mock(JobHandler.class);
        when(testJobHandler.getPayloadType()).thenReturn(Object.class);
        when(testJobHandler.getJobKey()).thenReturn("TEST_JOB_KEY");

        when(this.jobRepository.claimNext(anyString())).thenReturn(Optional.of(testJob));
        when(this.applicationContext.getBeansOfType(JobHandler.class)).thenReturn(
                Map.of("testJobHandler", testJobHandler)
        );

        this.jobService.runOneJob();

        verify(testJobHandler).handle(testJob, null);
        verify(this.jobRepository).save(testJob);

        assertThat(testJob.getState(), is(Job.State.DONE));
        assertThat(testJob.getModified(), is(notNullValue()));
        assertThat(testJob.getLockedBy(), is(nullValue()));
        assertThat(testJob.getLockedAt(), is(nullValue()));
    }

    @Test
    public void retryJobAfterThrowingHandler() {
        final Job testJob = new Job();
        testJob.setAttempts(0);
        testJob.setMaxAttempts(3);
        testJob.setJobKey("TEST_JOB_KEY");

        final JobHandler<Object> throwingHandler = mock(JobHandler.class);
        when(throwingHandler.getPayloadType()).thenReturn(Object.class);
        when(throwingHandler.getJobKey()).thenReturn("TEST_JOB_KEY");
        doThrow(new RuntimeException("TO THROW")).when(throwingHandler).handle(testJob, null);

        when(this.jobRepository.claimNext(anyString())).thenReturn(Optional.of(testJob));
        when(this.applicationContext.getBeansOfType(JobHandler.class)).thenReturn(
                Map.of("testJobHandler", throwingHandler)
        );

        this.jobService.runOneJob();

        verify(throwingHandler).handle(testJob, null);
        verify(this.jobRepository).save(testJob);

        assertThat(testJob.getState(), is(Job.State.QUEUED));
        assertThat(testJob.getAttempts(), is(1));
        assertThat(testJob.getRunAfter(), is(notNullValue()));
        assertThat(testJob.getLastError(), is(notNullValue()));
        assertThat(testJob.getModified(), is(notNullValue()));
        assertThat(testJob.getLockedBy(), is(nullValue()));
        assertThat(testJob.getLockedAt(), is(nullValue()));
    }

}
