package com.atlassian.stash.internal.maintenance.backup;

import com.atlassian.event.api.EventPublisher;
import com.atlassian.johnson.Johnson;
import com.atlassian.bitbucket.event.backup.BackupCanceledEvent;
import com.atlassian.bitbucket.event.backup.BackupFailedEvent;
import com.atlassian.bitbucket.event.backup.BackupStartedEvent;
import com.atlassian.bitbucket.event.backup.BackupSucceededEvent;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.backup.BackupException;
import com.atlassian.stash.internal.backup.CanceledBackupException;
import com.atlassian.stash.internal.maintenance.latch.LatchableService;
import com.atlassian.stash.internal.db.DatabaseLatch;
import com.atlassian.stash.internal.db.DatabaseManager;
import com.atlassian.stash.internal.maintenance.*;
import com.atlassian.stash.internal.scm.InternalScmService;
import com.atlassian.stash.internal.scm.ScmLatch;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public class AbstractBackupTaskTest {

    @Mock
    private DatabaseLatch databaseLatch;
    @Mock
    private DatabaseManager databaseManager;
    @Mock
    private EventPublisher eventPublisher;
    @Spy
    private I18nService i18nService = new SimpleI18nService(SimpleI18nService.Mode.RETURN_KEYS);
    @Mock
    private MaintenanceModePhase maintenanceModePhase;
    @Mock
    private ScmLatch scmLatch;
    @Mock
    private InternalScmService scmService;
    @Mock(answer = Answers.RETURNS_MOCKS)
    private MaintenanceTaskFactory taskFactory;

    private AbstractBackupTask abstractBackupTask;

    @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() {
        BackupPhase backupPhase = mock(BackupPhase.class);

        BackupPhase.Builder backupPhaseBuilder = mock(BackupPhase.Builder.class);
        when(backupPhaseBuilder.build()).thenReturn(backupPhase);
        when(backupPhaseBuilder.add(any(MaintenanceTask.class), anyInt())).thenReturn(backupPhaseBuilder);

        when(databaseManager.getCurrentLatch()).thenReturn(databaseLatch);

        when(scmService.getCurrentLatch()).thenReturn(scmLatch);

        when(taskFactory.backupPhaseBuilder(any(BackupState.class))).thenReturn(backupPhaseBuilder);

        abstractBackupTask = new AbstractBackupTask(databaseManager, eventPublisher, i18nService,
                taskFactory, new LatchableService[]{databaseManager, scmService}) {

            @Override
            protected MaintenanceTask createDelegateBackupTask(BackupClientPlaceholderStep backupClientPlaceholderStep,
                                                               MaintenanceTaskFactory taskFactory,
                                                               BackupState backupState,
                                                               TaskMaintenanceEvent maintenanceEvent) {
                return maintenanceModePhase;
            }
        };
    }

    @Test
    public void testClientProgressCallbackAvailable() {
        // verify that a clientProgressCallback is provided
        assertNotNull(abstractBackupTask.getClientProgressCallback());
    }

    @Test
    public void testRunExitsInError() throws Exception {
        doThrow(new RuntimeException("faking an error from the nested task")).when(maintenanceModePhase).run();

        try {
            abstractBackupTask.run();
            fail("BackupTask.run should not have completed normally");
        } catch (BackupException e) {
            // expected
            verify(eventPublisher).publish(isA(BackupStartedEvent.class));
            verify(databaseLatch).unlatch();
            verify(scmLatch).unlatch();
            verify(eventPublisher).publish(isA(BackupFailedEvent.class));

            assertFalse(e instanceof CanceledBackupException);
        }
    }

    @Test
    public void testRunIsCanceled() throws Exception {
        doThrow(new CanceledBackupException(mock(KeyedMessage.class))).when(maintenanceModePhase).run();

        try {
            abstractBackupTask.run();
            fail("BackupTask.run should not have completed normally");
        } catch (CanceledBackupException e) {
            // expected
            verify(eventPublisher).publish(isA(BackupStartedEvent.class));
            verify(databaseLatch).unlatch();
            verify(scmLatch).unlatch();
            verify(eventPublisher).publish(isA(BackupCanceledEvent.class));
        }
    }

    @Test
    public void testShouldNotifyOfBackupStartAndSuccess() throws Exception {
        abstractBackupTask.run();
        verify(eventPublisher).publish(isA(BackupStartedEvent.class));
        verify(eventPublisher).publish(isA(BackupSucceededEvent.class));
    }
}
