package com.atlassian.stash.internal.license;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.config.Clock;
import com.atlassian.stash.internal.server.InternalApplicationPropertiesService;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.*;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultGracePeriodHandlerTest {

    private static final long TIME = System.currentTimeMillis();

    @Mock
    private InternalApplicationPropertiesService applicationPropertiesService;
    @Mock
    private Clock clock;
    @InjectMocks
    private DefaultGracePeriodHandler gracePeriodHandler;
    @Spy
    private I18nService i18nService = new SimpleI18nService(SimpleI18nService.Mode.RETURN_KEYS);

    @Before
    public void setup() {
        when(clock.utcMillis()).thenReturn(createDateTime(TIME));
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(empty());
    }

    @Test
    public void testGetMessageNotInitialized() throws Exception {
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(empty());

        assertEquals(empty(), gracePeriodHandler.getMessage());
    }

    @Test
    public void testGetMessageNotStarted() throws Exception {
        assertEquals(empty(), gracePeriodHandler.getMessage());
    }

    @Test
    public void testGetMessageInTheFuture() throws Exception {
        long time = clock.utcMillis() + 100;
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        assertEquals("bitbucket.license.over.limit.grace.period.hours", gracePeriodHandler.getMessage()
                .get().getLocalisedMessage());
    }

    @Test
    public void testGetMessageHours() throws Exception {
        long time = clock.utcMillis() - 1;
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        assertEquals("bitbucket.license.over.limit.grace.period.hours", gracePeriodHandler.getMessage()
                .get().getLocalisedMessage());
    }

    @Test
    public void testGetMessageMinutes() throws Exception {
        long time = clock.utcMillis();
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        when(clock.utcMillis()).thenReturn(createDateTime(time + DefaultGracePeriodHandler.GRACE_PERIOD - MINUTES.toMillis(10)));

        assertEquals("bitbucket.license.over.limit.grace.period.minutes", gracePeriodHandler.getMessage()
                .get().getLocalisedMessage());
    }

    @Test
    public void testGetStartTimeNotStarted() throws Exception {
        assertEquals(empty(), gracePeriodHandler.getStartTime());
    }

    @Test
    public void testGetStartTimeInTheFuture() throws Exception {
        long time = clock.utcMillis() + 100L;
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        assertEquals((Long) time, gracePeriodHandler.getStartTime().get());
    }

    @Test
    public void testGetStartTime() throws Exception {
        long time = clock.utcMillis();
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        assertEquals((Long) time, gracePeriodHandler.getStartTime().get());
    }

    @Test
    public void testStartWhenNotInitialized() throws Exception {
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(empty());

        gracePeriodHandler.start();

        long time = clock.utcMillis();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService).setGracePeriodStartTime(time);
    }

    @Test
    public void testStartWhenNotStarted() throws Exception {
        gracePeriodHandler.start();

        long time = clock.utcMillis();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService).setGracePeriodStartTime(time);
    }

    @Test
    public void testStartWhenStarted() throws Exception {
        long time = clock.utcMillis();
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        gracePeriodHandler.start();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testStartWhenStartedButPassed() throws Exception {
        long time = clock.utcMillis();
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));
        when(clock.utcMillis()).thenReturn(createDateTime(time + DefaultGracePeriodHandler.GRACE_PERIOD));

        gracePeriodHandler.start();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        //If the grace period has passed, we should not restart it, we want to see a valid license first
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testStopWhenNotInitialized() throws Exception {
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(empty());

        gracePeriodHandler.stop();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testStopWhenNotStarted() throws Exception {
        gracePeriodHandler.stop();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testStopWhenRunning() throws Exception {
        //Have to prevent an admin disabling then re-enabling a user directory, therby going under the limit for a
        //moment to get a new grace period

        long time = clock.utcMillis();
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        gracePeriodHandler.stop();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testStopPassed() throws Exception {
        long time = clock.utcMillis();
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));
        when(clock.utcMillis()).thenReturn(createDateTime(time + DefaultGracePeriodHandler.GRACE_PERIOD));

        gracePeriodHandler.stop();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService).setGracePeriodStartTime(null);
    }

    @Test
    public void testStopStopped() throws Exception {
        gracePeriodHandler.stop();

        verify(applicationPropertiesService).getGracePeriodStartTime();
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testIsActiveNotInitialized() throws Exception {
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(empty());

        assertFalse(gracePeriodHandler.isActive());
    }

    @Test
    public void testIsActiveNotStarted() throws Exception {
        assertFalse(gracePeriodHandler.isActive());
    }

    @Test
    public void testIsActiveInTheFuture() throws Exception {
        long now = clock.utcMillis();
        long time = now + 100;
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        //Future -> tampering -> no grace period
        assertTrue(gracePeriodHandler.isActive());
        verify(applicationPropertiesService, never()).setGracePeriodStartTime(anyLong());
    }

    @Test
    public void testIsActiveInGracePeriod() throws Exception {
        long time = clock.utcMillis() - 1;
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));

        assertTrue(gracePeriodHandler.isActive());
    }

    @Test
    public void testIsActiveAfterGracePeriod() throws Exception {
        long time = clock.utcMillis() - 1;
        when(applicationPropertiesService.getGracePeriodStartTime()).thenReturn(of(time));
        when(clock.utcMillis()).thenReturn(createDateTime(time + 1 + DefaultGracePeriodHandler.GRACE_PERIOD));

        assertFalse(gracePeriodHandler.isActive());
    }

    private static long createDateTime(long time) {
        return new DateTime(time, DateTimeZone.UTC).getMillis();
    }
}