package com.atlassian.stash.internal.bootstrap;

import com.atlassian.bitbucket.server.ApplicationMode;
import com.atlassian.bitbucket.util.UrlUtils;
import com.atlassian.crowd.embedded.api.PasswordCredential;
import com.atlassian.crowd.model.user.UserTemplate;
import com.atlassian.stash.internal.config.RemoveSetupConfigurationRequest;
import com.atlassian.stash.internal.user.InternalNormalUser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.runners.MockitoJUnitRunner;

import java.net.URI;

import static com.atlassian.stash.internal.ApplicationConstants.*;
import static com.atlassian.stash.internal.bootstrap.BootstrapOperation.VALUE_CLOUD_MIRROR;
import static com.atlassian.stash.internal.bootstrap.BootstrapOperation.VALUE_SERVER_MIRROR;
import static com.atlassian.stash.internal.bootstrap.DevBootstrapOperation.LICENSE_CLUSTERED;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class BootstrapOperationTest extends BootstrapOperationTestBase {

    private static final String USER_NAME = "admin";
    private static final String USER_PASSWORD = "admin";
    private static final String USER_EMAIL = "a@a.com";
    private static final String USER_DISPLAY_NAME = "Adam";

    @Before
    public void setUp() throws Throwable {
        super.setUp();

        when(propertiesService.getMode()).thenReturn(ApplicationMode.DEFAULT);
        bootstrapOperation = new BootstrapOperation(directoryManager, propertiesService, userDao, applicationProperties,
                applicationManager, licenseHelper, configurationService, globalPermissionDao, applicationPropertyDao,
                licenseService);
    }

    @Test
    public void testIsNotSetUpAfterBootstrapWithoutUnattendedProps() throws Exception {
        mockInteractionsForNoExistingDirectory();

        mockLicenseHelper();

        bootstrapOperation.perform();

        verifyInteractionsForNoExistingDirectory();

        verifyStashUsersGroupPermsSet();

        // display name is defaulted to the product name
        verify(propertiesService).setDisplayName(PRODUCT_NAME);

        // nothing else should be set up
        verify(directoryManager, never()).addUser(anyLong(), any(UserTemplate.class), any(PasswordCredential.class));
        verify(configurationService, never()).removeSetupProperties(any(RemoveSetupConfigurationRequest.class));
        verify(propertiesService, never()).setBaseURL(any(URI.class));
        verify(propertiesService, never()).setSetup(anyBoolean());
    }

    @Test
    public void testIsSetUpAfterBootstrapWithAllRequiredUnattendedProps() throws Exception {
        mockInteractionsForNoExistingDirectory();

        when(applicationProperties.getProperty(eq(SETUP_LICENSE))).thenReturn(LICENSE_CLUSTERED);
        when(applicationProperties.getProperty(eq(SETUP_BASE_URL))).thenReturn(BASE_URL);
        when(applicationProperties.getProperty(eq(SETUP_DISPLAY_NAME))).thenReturn(APP_DISPLAY_NAME);
        when(applicationProperties.getProperty(eq(SETUP_USER_NAME))).thenReturn(USER_NAME);
        when(applicationProperties.getProperty(eq(SETUP_USER_PASSWORD))).thenReturn(USER_PASSWORD);
        when(applicationProperties.getProperty(eq(SETUP_USER_DISPLAY_NAME))).thenReturn(USER_DISPLAY_NAME);
        when(applicationProperties.getProperty(eq(SETUP_USER_EMAIL_ADDRESS))).thenReturn(USER_EMAIL);

        InternalNormalUser createdUser = new InternalNormalUser.Builder().name(USER_NAME).build();
        when(userDao.findByName(eq(USER_NAME))).thenReturn(createdUser);

        mockLicenseHelper();

        bootstrapOperation.perform();

        verifyInteractionsForNoExistingDirectory();

        verifyStashUsersGroupPermsSet();

        verifyUserCreated(USER_NAME, USER_NAME, USER_NAME, USER_DISPLAY_NAME, USER_EMAIL, USER_PASSWORD, times(1));
        verifySysAdminPermissionGranted(createdUser, times(1));

        // remove prop from config
        ArgumentCaptor<RemoveSetupConfigurationRequest> requestCaptor = captureRemoveConfigRequest();

        verify(configurationService).removeSetupProperties(requestCaptor.capture());
        assertTrue(requestCaptor.getValue().isRemoveSysAdmin());
        assertTrue(requestCaptor.getValue().isRemoveLicense());
        assertTrue(requestCaptor.getValue().isRemoveBaseUrl());
        assertTrue(requestCaptor.getValue().isRemoveDisplayName());

        verify(propertiesService).setBaseURL(eq(UrlUtils.uncheckedCreateURI(BASE_URL)));
        verify(propertiesService).setDisplayName(eq(APP_DISPLAY_NAME));
        verify(propertiesService).setSetup(true);
    }

    @Test
    public void testIsSetUpAfterBootstrapInMirrorServerModeWithAllRequiredUnattendedProps() throws Exception {
        assertMirrorBootstrap(true, APP_DISPLAY_NAME, "https://mirror.example.com", VALUE_SERVER_MIRROR);
    }

    @Test
    public void testIsNotSetUpAfterBootstrapInServerMirrorModeWhenPrimaryUrlNotProvided() throws Exception {
        assertMirrorBootstrap(false, APP_DISPLAY_NAME, null, VALUE_SERVER_MIRROR);
    }

    @Test
    public void testIsNotSetUpAfterBootstrapInServerMirrorModeDisplayNameNotProvided() throws Exception {
        assertMirrorBootstrap(false, null, "https://mirror.example.com", VALUE_SERVER_MIRROR);
    }

    @Test
    public void testIsSetUpAfterBootstrapInMirrorCloudModeWithAllRequiredUnattendedProps() throws Exception {
        assertMirrorBootstrap(true, APP_DISPLAY_NAME, null, VALUE_CLOUD_MIRROR);
    }

    @Test
    public void testIsNotSetUpAfterBootstrapInCloudMirrorModeDisplayNameNotProvided() throws Exception {
        assertMirrorBootstrap(false, null, "https://mirror.example.com", VALUE_CLOUD_MIRROR);
    }

    @Test
    public void testIsNotSetUpWithBadUpstreamType() throws Exception {
        assertMirrorBootstrap(false, null, "https://mirror.example.com", "randomBadType");
    }

    @Test
    public void testIsNotSetUpAfterBootstrapWithMissingSetupProperty() throws Exception {
        mockInteractionsForExistingDirectory();

        when(applicationProperties.getProperty(eq(SETUP_LICENSE))).thenReturn(LICENSE_CLUSTERED);
        when(applicationProperties.getProperty(eq(SETUP_BASE_URL))).thenReturn(BASE_URL);
        when(applicationProperties.getProperty(eq(SETUP_DISPLAY_NAME))).thenReturn(APP_DISPLAY_NAME);
        // no username provided
        when(applicationProperties.getProperty(eq(SETUP_USER_PASSWORD))).thenReturn(USER_PASSWORD);
        when(applicationProperties.getProperty(eq(SETUP_USER_DISPLAY_NAME))).thenReturn(USER_DISPLAY_NAME);
        when(applicationProperties.getProperty(eq(SETUP_USER_EMAIL_ADDRESS))).thenReturn(USER_EMAIL);

        mockLicenseHelper();

        bootstrapOperation.perform();

        // The application and directory should not have been created
        verifyInteractionsForExistingDirectory();

        // The sysadmin account should not be created because the username was not provided
        verify(directoryManager, never()).addUser(anyLong(), any(UserTemplate.class), any(PasswordCredential.class));

        // The license, base url, and display name should be created
        verify(licenseHelper).set(eq(LICENSE_CLUSTERED));
        verify(propertiesService).setBaseURL(eq(UrlUtils.uncheckedCreateURI(BASE_URL)));
        verify(propertiesService).setDisplayName(eq(APP_DISPLAY_NAME));

        // The application is not setup
        verify(propertiesService, never()).setSetup(true);

        ArgumentCaptor<RemoveSetupConfigurationRequest> requestCaptor = captureRemoveConfigRequest();
        verify(configurationService).removeSetupProperties(requestCaptor.capture());
        assertTrue(requestCaptor.getValue().isRemoveLicense());
        assertFalse(requestCaptor.getValue().isRemoveSysAdmin());
        assertTrue(requestCaptor.getValue().isRemoveBaseUrl());
        assertTrue(requestCaptor.getValue().isRemoveDisplayName());
    }

    @Test
    public void testIsNotSetUpAfterBootstrapWithFailingLicense() throws Exception {
        mockInteractionsForExistingDirectory();

        String garbageLicense = "garbage-license";
        when(applicationProperties.getProperty(eq(SETUP_LICENSE))).thenReturn(garbageLicense);
        when(licenseHelper.decode(garbageLicense)).thenThrow(new IllegalArgumentException());

        bootstrapOperation.perform();

        verifyInteractionsForExistingDirectory();

        verifyNoMoreInteractions(userDao, directoryManager, globalPermissionDao, configurationService);
    }

    @Test
    public void testIsNotSetUpAfterBootstrapWithLicenseForAnotherProduct() throws Exception {
        mockInteractionsForExistingDirectory();

        String jiraLicense = "jira-license";
        when(applicationProperties.getProperty(eq(SETUP_LICENSE))).thenReturn(jiraLicense);
        when(licenseHelper.decode(jiraLicense)).thenReturn(null);

        bootstrapOperation.perform();

        verifyInteractionsForExistingDirectory();

        verifyNoMoreInteractions(userDao, directoryManager, globalPermissionDao, configurationService);
    }

    @Test
    public void testIsNotSetUpAfterBootstrapWithInvalidBaseUrl() throws Exception {
        mockInteractionsForExistingDirectory();

        when(applicationProperties.getProperty(eq(SETUP_LICENSE))).thenReturn(LICENSE_CLUSTERED);
        when(applicationProperties.getProperty(eq(SETUP_BASE_URL))).thenReturn("this is not a URI");

        mockLicenseHelper();

        bootstrapOperation.perform();

        verifyInteractionsForExistingDirectory();

        // The application is not setup
        verify(propertiesService, never()).setSetup(true);

        // License is read, applied and removed
        ArgumentCaptor<RemoveSetupConfigurationRequest> requestCaptor = captureRemoveConfigRequest();
        verify(configurationService).removeSetupProperties(requestCaptor.capture());
        assertTrue(requestCaptor.getValue().isRemoveLicense());
    }

    private void assertMirrorBootstrap(boolean setup, String displayName, String mirrorUrl, String mirrorMode) throws Exception {
        when(propertiesService.getMode()).thenReturn(ApplicationMode.MIRROR);
        bootstrapOperation = new BootstrapOperation(directoryManager, propertiesService, userDao, applicationProperties,
                applicationManager, licenseHelper, configurationService, globalPermissionDao, applicationPropertyDao,
                licenseService);

        mockInteractionsForNoExistingDirectory();

        when(applicationProperties.getProperty(eq(SETUP_LICENSE))).thenReturn(LICENSE_CLUSTERED);
        when(applicationProperties.getProperty(eq(SETUP_BASE_URL))).thenReturn(BASE_URL);
        when(applicationProperties.getProperty(eq(SETUP_DISPLAY_NAME))).thenReturn(displayName);
        when(applicationProperties.getProperty(eq(SETUP_USER_NAME))).thenReturn(USER_NAME);
        when(applicationProperties.getProperty(eq(SETUP_USER_PASSWORD))).thenReturn(USER_PASSWORD);
        when(applicationProperties.getProperty(eq(SETUP_USER_DISPLAY_NAME))).thenReturn(USER_DISPLAY_NAME);
        when(applicationProperties.getProperty(eq(SETUP_USER_EMAIL_ADDRESS))).thenReturn(USER_EMAIL);
        when(applicationProperties.getProperty(eq(BootstrapOperation.KEY_MIRROR_UPSTREAM))).thenReturn(mirrorUrl);
        when(applicationProperties.getProperty(eq(BootstrapOperation.KEY_MIRROR_TYPE))).thenReturn(mirrorMode);

        mockLicenseHelper();

        bootstrapOperation.perform();

        verifyInteractionsForNoExistingDirectory();

        // sys admins can't be set for mirrors
        verify(directoryManager, never()).addUser(anyLong(), any(UserTemplate.class), any(PasswordCredential.class));

        // remove prop from config
        ArgumentCaptor<RemoveSetupConfigurationRequest> requestCaptor = captureRemoveConfigRequest();
        verify(configurationService).removeSetupProperties(requestCaptor.capture());
        // sys admins andlicenses can't be set for mirrors
        assertFalse(requestCaptor.getValue().isRemoveSysAdmin());
        assertFalse(requestCaptor.getValue().isRemoveLicense());
        // everything else can be set
        assertTrue(requestCaptor.getValue().isRemoveBaseUrl());
        verify(licenseService).setUnvalidated(BootstrapOperation.LICENSE_MIRROR);
        verify(propertiesService).setBaseURL(eq(UrlUtils.uncheckedCreateURI(BASE_URL)));

        if (displayName == null) {
            verify(propertiesService, never()).setDisplayName(anyString());
            assertFalse(requestCaptor.getValue().isRemoveDisplayName());
        } else {
            assertTrue(requestCaptor.getValue().isRemoveDisplayName());
            verify(propertiesService).setDisplayName(eq(displayName));
        }

        if (setup) {
            verify(propertiesService).setSetup(true);
        } else {
            verify(propertiesService, never()).setSetup(anyBoolean());
        }
    }
}