package com.atlassian.stash.internal.jira;

import com.atlassian.applinks.api.ApplicationLink;
import com.atlassian.applinks.api.application.jira.JiraApplicationType;
import com.atlassian.applinks.host.spi.HostApplication;
import com.atlassian.applinks.spi.Manifest;
import com.atlassian.applinks.spi.application.IdentifiableType;
import com.atlassian.applinks.spi.application.TypeId;
import com.atlassian.applinks.spi.auth.AuthenticationConfigurationException;
import com.atlassian.applinks.spi.auth.AuthenticationScenario;
import com.atlassian.applinks.spi.link.ApplicationLinkDetails;
import com.atlassian.applinks.spi.link.MutatingApplicationLinkService;
import com.atlassian.applinks.spi.manifest.ManifestNotFoundException;
import com.atlassian.applinks.spi.manifest.ManifestRetriever;
import com.atlassian.applinks.spi.util.TypeAccessor;
import com.atlassian.crowd.embedded.api.CrowdDirectoryService;
import com.atlassian.crowd.manager.application.ApplicationManager;
import com.atlassian.crowd.manager.directory.DirectoryManager;
import com.atlassian.bitbucket.AbstractServiceTest;
import com.atlassian.bitbucket.help.HelpPathService;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.jira.JiraSetupException;
import com.atlassian.bitbucket.jira.JiraSetupService;
import com.atlassian.bitbucket.jira.NotAJiraServerException;
import com.atlassian.bitbucket.permission.PermissionAdminService;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import java.net.URI;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class JiraSetupServiceImplTest extends AbstractServiceTest {

    private static final String JIRA_TYPE_ID = "jira";
    private static final String CONFLUENCE_TYPE_ID = "confluence";

    @Mock
    MutatingApplicationLinkService applicationLinkService;
    @Mock
    TypeAccessor typeAccessor;
    @Mock
    ManifestRetriever manifestRetriever;
    @Mock
    CrowdDirectoryService crowdDirectoryService;
    @Mock
    DirectoryManager directoryManager;
    @Mock
    ApplicationManager applicationManager;
    @Mock
    I18nService i18nService;
    @Mock
    HelpPathService helpPathService;
    @Mock
    PermissionAdminService permissionAdminService;
    @Mock
    HostApplication hostApplication;
    @Mock
    Manifest manifest;
    @Mock
    JiraIdentifiableApplicationType jiraApplicationType;
    @Mock
    ApplicationLink applicationLink;

    private interface JiraIdentifiableApplicationType extends JiraApplicationType, IdentifiableType {}

    JiraSetupServiceImpl setupService;
    URI uri;

    public JiraSetupServiceImplTest() {
        super(JiraSetupServiceImpl.class, JiraSetupService.class);
    }

    @Before
    public void setUp() throws Exception {
        setupService = new JiraSetupServiceImpl(applicationLinkService, typeAccessor, hostApplication,
                manifestRetriever, crowdDirectoryService, directoryManager, permissionAdminService, applicationManager,
                i18nService);

        // some useful test objects that are used in multiple test methods
        uri = new URI("https://jira.atlassian.com");
        when(jiraApplicationType.getId()).thenReturn(new TypeId(JIRA_TYPE_ID));

        // make I18nService just return the key it was invoked with for testing purposes
        final Answer<String> getTextReturnsI18nKey = new Answer<String>() {
            @Override
            public String answer(final InvocationOnMock invocation) throws Throwable {
                return (String) invocation.getArguments()[0]; // first arg is i18n key
            }
        };
        // when mocking a var-arg method you seem to need to have one with the var-arg Object[] matcher and one without
        when(i18nService.getMessage(Matchers.<String>any())).thenAnswer(getTextReturnsI18nKey);
        when(i18nService.getMessage(Matchers.<String>any(), Matchers.<Object[]>any())).thenAnswer(getTextReturnsI18nKey);
    }

    @Test
    public void testCheckServerIsEmbeddedCrowdJiraNoManifestFound() throws Exception {
        // happens when the remote server is unavailable or not an application in which UAL is deployed
        when(manifestRetriever.getManifest(uri)).thenThrow(new ManifestNotFoundException("No JIRA server here!"));
        try {
            setupService.checkServerIsJiraWithEmbeddedCrowd(uri);
            fail("Should have thrown NotAJiraServerException");
        } catch (NotAJiraServerException e) {
            assertEquals("bitbucket.web.jira.setup.notajiraserver", e.getMessage());
        }
    }

    @Test
    public void testCheckServerIsEmbeddedCrowdJiraJiraAppTypeNotInstalled() throws Exception {
        // happens if the built-in JiraApplicationType failed to load or has been disabled
        when(manifestRetriever.getManifest(uri)).thenReturn(manifest);
        when(typeAccessor.getApplicationType(JiraApplicationType.class)).thenReturn(null);
        try {
            setupService.checkServerIsJiraWithEmbeddedCrowd(uri);
            fail("Should have thrown NotAJiraServerException");
        } catch (JiraSetupException e) {
            assertEquals("bitbucket.web.jira.setup.localjiraapplicationtypemissing", e.getMessage());
        }
    }

    @Test
    public void testCheckServerIsEmbeddedCrowdJiraServerIsNotJira() throws Exception {
        // happens if the remote server has UAL deployed, but isn't JIRA (e.g. the user is connecting to Confluence, Bamboo, etc.)
        when(manifestRetriever.getManifest(uri)).thenReturn(manifest);
        when(typeAccessor.getApplicationType(JiraApplicationType.class)).thenReturn(jiraApplicationType);
        when(manifest.getTypeId()).thenReturn(new TypeId(CONFLUENCE_TYPE_ID));
        try {
            setupService.checkServerIsJiraWithEmbeddedCrowd(uri);
            fail("Should have thrown NotAJiraServerException");
        } catch (NotAJiraServerException e) {
            assertEquals("bitbucket.web.jira.setup.wrongappurl", e.getMessage());
        }
    }

    @Test
    public void testCheckServerIsEmbeddedCrowdJiraServerHappyPath() throws Exception {
        when(manifestRetriever.getManifest(uri)).thenReturn(manifest);
        when(typeAccessor.getApplicationType(JiraApplicationType.class)).thenReturn(jiraApplicationType);
        when(manifest.getTypeId()).thenReturn(new TypeId("jira"));
        setupService.checkServerIsJiraWithEmbeddedCrowd(uri);
        // sweet! no exceptions thrown - server is JIRA 4.3+
    }

    @Test
    @Ignore
    public void testCheckAdminCredentialsIncorrectOrNotAJiraSysAdmin() throws Exception {
        // user is not a sys-admin or their password was incorrect
        fail();
    }

    @Test
    @Ignore
    public void testCheckAdminCredentialsResponseException() throws Exception {
        // the request itself failed
        fail();
    }

    @Test
    @Ignore
    public void testCheckAdminCredentialsHappyPath() throws Exception {
        fail();
        // credentials are good and the user is a sys-admin!
    }

    @Test
    @Ignore
    public void testCreateReciprocatedApplicationLinkMissingApplicationType() throws Exception {
        fail();
        // stash application type is not installed in JIRA
    }

    @Test
    @Ignore
    public void testCreateReciprocatedApplicationLinkJiraCanNotSeeStash() throws Exception {
        // baseURL provided to JIRA to contact Stash is incorrect
        fail();
    }

    @Test
    @Ignore
    public void testCreateReciprocatedApplicationLinkOtherReciprocalActionException() throws Exception {
        // JIRA couldn't create the link back to Stash for some other reason
        fail();
    }

    @Test
    @Ignore
    public void testCreateReciprocatedApplicationLinkJiraAppTypeNotInstalled() throws Exception {
        // happens if the built-in JiraApplicationType failed to load or has been disabled
        fail();
    }

    @Test
    @Ignore
    public void testCreateReciprocatedApplicationLinkManifestNotFoundAfterCreate() throws Exception {
        // happens if JIRA appears to create the link to Stash correctly, but then goes down before Stash can link to it
        fail();
    }

    @Test
    @Ignore
    public void testCreateReciprocatedApplicationLinkHappyPath() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testRemoteErrorMessageStartsWith() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testAuthenticateApplicationLinkAuthenticationConfigurationFailed() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testAuthenticateApplicationLinkHappyPath() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testCreateStashApplicationInCrowdCrowdResultNotOk() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testCreateStashApplicationInCrowdHappyPath() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testSetupJiraDirectoryDirectoryNameInUse() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testSetupJiraDirectoryApplicationOrDirectoryNotFound() throws Exception {
        fail();
    }

    @Test
    @Ignore
    public void testGenerateLinkName() throws Exception {
        fail();
    }

    @Test(expected = JiraSetupException.class)
    public void testApplicationLinkCleanup() throws Exception {
        URI remoteRpcUrl = new URI("http://remote");
        URI localRpcUrl = new URI("http://local");
        String username = "username";
        String password = "password";

        ApplicationLinkDetails linkDetails = ApplicationLinkDetails.builder()
                .rpcUrl(remoteRpcUrl)
                .displayUrl(remoteRpcUrl) // just set to the RPC URL
                .isPrimary(true)
                .name(remoteRpcUrl.getHost() + " JIRA")
                .build();

        when(typeAccessor.getApplicationType(JiraApplicationType.class)).thenReturn(jiraApplicationType);
        when(applicationLinkService.createApplicationLink(same(jiraApplicationType), refEq(linkDetails)))
                .thenReturn(applicationLink);
        when(manifestRetriever.getManifest(remoteRpcUrl)).thenReturn(manifest);

        TypeId typeId = new TypeId("TYPE_ID");
        when(manifest.getTypeId()).thenReturn(typeId);
        when(jiraApplicationType.getId()).thenReturn(typeId);

        when(applicationLinkService.isAdminUserInRemoteApplication(remoteRpcUrl, username, password)).thenReturn(true);

        doThrow(new AuthenticationConfigurationException("FAILED")).when(applicationLinkService)
                .configureAuthenticationForApplicationLink(same(applicationLink), any(AuthenticationScenario.class),
                        eq(username), eq(password));

        try {
            setupService.createLink(remoteRpcUrl, localRpcUrl, username, password, true);
        } finally {
            verify(applicationLinkService).deleteApplicationLink(applicationLink);
        }
    }

}
