package com.atlassian.stash.internal.cluster;

import com.atlassian.stash.internal.maintenance.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.IOException;
import java.util.concurrent.ExecutionException;

import static com.atlassian.stash.internal.cluster.ClusterJoinCheckAction.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class MaintenanceClusterJoinCheckTest extends AbstractClusterJoinCheckTest {

    private MaintenanceTaskStatus acceptingStatus;
    private MaintenanceTaskStatus connectingStatus;
    @Mock
    private ClusterJoinRequest request;
    @Mock
    private MaintenanceTaskMonitor runningTask;

    @Test
    public void testNoLatestTask() throws Exception {
        CheckResult result = doJoin();

        assertEquals(CONNECT, result.acceptResult.getAction());
        assertEquals(CONNECT, result.connectResult.getAction());
    }

    @Test
    public void testLatestTaskCompleteOnBoth() throws Exception {
        acceptingStatus = mockLatestTask("task-id", MaintenanceTaskState.SUCCESSFUL);
        connectingStatus = mockLatestTask("other-task-id", MaintenanceTaskState.SUCCESSFUL);

        CheckResult result = doJoin();

        assertEquals(CONNECT, result.acceptResult.getAction());
        assertEquals(CONNECT, result.connectResult.getAction());
    }

    @Test
    public void testAcceptingSideInMaintenancePassivatesConnectingWhenNoLatestTask() throws Exception {
        acceptingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);

        CheckResult result = doJoin();

        assertEquals(PASSIVATE_OTHER_NODE, result.acceptResult.getAction());
        assertEquals(PASSIVATE_THIS_NODE, result.connectResult.getAction());
    }

    @Test
    public void testAcceptingSideInMaintenancePassivatesConnectingWhenLatestTaskCompleted() throws Exception {
        acceptingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);
        connectingStatus = mockLatestTask("other-task-id", MaintenanceTaskState.FAILED);

        CheckResult result = doJoin();

        assertEquals(PASSIVATE_OTHER_NODE, result.acceptResult.getAction());
        assertEquals(PASSIVATE_THIS_NODE, result.connectResult.getAction());
    }

    @Test
    public void testConnectingSideInMaintenancePassivatesAcceptingWhenNoLatestTask() throws Exception {
        connectingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);

        CheckResult result = doJoin();

        assertEquals(PASSIVATE_THIS_NODE, result.acceptResult.getAction());
        assertEquals(PASSIVATE_OTHER_NODE, result.connectResult.getAction());
    }

    @Test
    public void testConnectingSideInMaintenancePassivatesAcceptingWhenLatestTaskCompleted() throws Exception {
        acceptingStatus = mockLatestTask("other-task-id", MaintenanceTaskState.FAILED);
        connectingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);

        CheckResult result = doJoin();

        assertEquals(PASSIVATE_THIS_NODE, result.acceptResult.getAction());
        assertEquals(PASSIVATE_OTHER_NODE, result.connectResult.getAction());
    }

    @Test
    public void testOnUnknown() throws Exception {
        assertEquals(ClusterJoinCheckResult.OK, createCheck(acceptingStatus).onUnknown(mock(ClusterJoinRequest.class)));
    }

    @Test
    public void testSplitBrainResolutionAcceptingCompleted() throws Exception {
        acceptingStatus = mockLatestTask("task-id", MaintenanceTaskState.SUCCESSFUL);
        connectingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);

        CheckResult result = doJoin();

        assertEquals(CONNECT, result.acceptResult.getAction());
        assertEquals(CONNECT, result.connectResult.getAction());
    }

    @Test
    public void testSplitBrainResolutionConnectingFailed() throws Exception {
        acceptingStatus = mockLatestTask("task-id", MaintenanceTaskState.SUCCESSFUL);
        connectingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);

        CheckResult result = doJoin();

        assertEquals(CONNECT, result.acceptResult.getAction());
        assertEquals(CONNECT, result.connectResult.getAction());
    }

    @Test
    public void testSplitBrainResolutionStillRunning() throws Exception {
        acceptingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);
        connectingStatus = mockLatestTask("task-id", MaintenanceTaskState.RUNNING);

        CheckResult result = doJoin();

        assertEquals(CONNECT, result.acceptResult.getAction());
        assertEquals(CONNECT, result.connectResult.getAction());
    }

    private MaintenanceClusterJoinCheck createCheck(MaintenanceTaskStatus status) {
        MaintenanceTaskStatusSupplier maintenanceTaskStatusSupplier = new LocalMaintenanceTaskStatusSupplier();
        if (status != null) {
            maintenanceTaskStatusSupplier.set(status);
        }
        return new MaintenanceClusterJoinCheck(maintenanceTaskStatusSupplier);
    }

    private CheckResult doJoin() throws InterruptedException, ExecutionException, IOException {
        return executeJoinCheck(createCheck(connectingStatus), createCheck(acceptingStatus));
    }

    private MaintenanceTaskStatus mockLatestTask(String taskId, MaintenanceTaskState state) {
        MaintenanceTaskStatus latestTask = mock(MaintenanceTaskStatus.class);

        when(latestTask.getId()).thenReturn(taskId);
        when(latestTask.getState()).thenReturn(state);

        return latestTask;
    }
}