package com.atlassian.stash.internal.maintenance;

import com.atlassian.event.api.EventPublisher;
import com.atlassian.johnson.Johnson;
import com.atlassian.johnson.event.*;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.user.ApplicationUser;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultMaintenanceLockTest {
    private static final String TOKEN = "token";

    @Mock
    private EventPublisher eventPublisher;
    private I18nService i18nService = new SimpleI18nService(SimpleI18nService.Mode.RETURN_KEYS);
    private DefaultMaintenanceLock lock;
    @Mock
    private ApplicationUser owner;
    @Mock
    private Runnable listener1;
    @Mock
    private Runnable listener2;

    @BeforeClass
    public static void setUpJohnson() throws IOException {
        Johnson.initialize(new ClassPathResource("minimal-johnson-config.xml").getURL().getPath());
    }

    @AfterClass
    public static void tearDownJohnson() {
        Johnson.terminate();
    }

    @Before
    public void setUp() {
        lock = new DefaultMaintenanceLock(eventPublisher, i18nService, owner, TOKEN);
        lock.addListener(listener1);
        lock.addListener(listener2);
    }

    @Test
    public void testGetUnlockToken() {
        assertEquals(TOKEN, lock.getUnlockToken());
    }

    @Test
    public void testGetOwner() {
        assertSame(owner, lock.getOwner());
    }

    @Test
    public void testLockRaisesJohnsonEvent() {
        lock.lock();

        // verify that an AddEvent has been raised
        ArgumentCaptor<AddEvent> eventCaptor = ArgumentCaptor.forClass(AddEvent.class);
        verify(eventPublisher).publish(eventCaptor.capture());

        // verify the SystemMaintenanceEvent contained in the AddEvent
        verifySystemMaintenanceEvent(eventCaptor.getValue().getEvent());
    }

    @Test(expected = IllegalStateException.class)
    public void testLockWhileLocked() {
        lock.lock();
        lock.lock();
    }

    @Test
    public void testUnlockCorrectTokenWhenLocked() {
        lock.lock();

        reset(eventPublisher);
        lock.unlock(TOKEN);

        inOrder(eventPublisher, listener1, listener2);

        // verify that the RemoveEvent was published
        ArgumentCaptor<RemoveEvent> eventCaptor = ArgumentCaptor.forClass(RemoveEvent.class);
        verify(eventPublisher).publish(eventCaptor.capture());
        verifySystemMaintenanceEvent(eventCaptor.getValue().getEvent());

        // verify that the listeners have been called
        verify(listener1).run();
        verify(listener2).run();
    }

    @Test(expected = IllegalStateException.class)
    public void testUnlockCorrectTokenWhenNotLocked() {
        lock.unlock(TOKEN);
    }

    @Test(expected = IncorrectTokenMaintenanceException.class)
    public void testUnlockIncorrectToken() {
        lock.lock();
        lock.unlock("wrong");
    }

    private void verifySystemMaintenanceEvent(Event event) {
        assertTrue(event instanceof SystemMaintenanceEvent);
        SystemMaintenanceEvent maintenanceEvent = (SystemMaintenanceEvent) event;
        assertTrue(maintenanceEvent.isToken(TOKEN));
        assertEquals(EventType.get("performing-maintenance"), maintenanceEvent.getKey());
        assertEquals(EventLevel.get("system-maintenance"), maintenanceEvent.getLevel());
    }
}
