package com.atlassian.bitbucket.internal.importer.github;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.internal.importer.*;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.test.ClasspathResourceRule;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.httpclient.api.Response;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.validation.Validator;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Map;

import static com.atlassian.bitbucket.internal.importer.ExternalRepositoryServiceTestHelper.*;
import static com.atlassian.bitbucket.internal.importer.ImporterConstants.PROPERTY_GITHUB_API_URL;
import static com.atlassian.bitbucket.internal.importer.ImporterConstants.PROPERTY_GITHUB_API_URL_DEFAULT;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class GitHubRepositoryServiceTest {

    @ClassRule
    public static final ClasspathResourceRule resources = new ClasspathResourceRule();
    private static final String GITHUB_ORGS_URI = "https://api.github.com/user/orgs?per_page=10&page=1";
    private static final String GITHUB_LINK = "<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel=\"next\",\n" +
            "  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel=\"last\"";
    private static final String GITHUB_USER_URI = "https://api.github.com/users/";
    private static final String REPOSITORIES_JSON_FILENAME = "/github/repository_response.json";
    private static final String TEAMS_JSON_FILENAME = "/github/team_response.json";
    private static final String USER_JSON_FILENAME = "/github/user_response.json";

    private Credential credential;
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private ApplicationPropertiesService propertiesService;
    private Response repositoryResponse;
    private String teamJson;
    private Response teamResponse;
    private Response userResponse;
    @Mock
    private Validator validator;

    @Before
    public void setUp() throws Exception {
        when(propertiesService.getPluginProperty(PROPERTY_GITHUB_API_URL, PROPERTY_GITHUB_API_URL_DEFAULT)).thenReturn(PROPERTY_GITHUB_API_URL_DEFAULT);
        when(validator.validate(any())).thenReturn(Collections.emptySet());
        credential = new Credential.Builder("github_user", "github_password")
                .build();
        repositoryResponse = getResponse(new String(Files.readAllBytes(resources.get(REPOSITORIES_JSON_FILENAME).toPath())));
        teamJson = new String(Files.readAllBytes(resources.get(TEAMS_JSON_FILENAME).toPath()));
        teamResponse = getResponse(teamJson);
        userResponse = getResponse(new String(Files.readAllBytes(resources.get(USER_JSON_FILENAME).toPath())));
    }

    @Test
    public void testGetMissingOAuthScopes() throws Exception {
        assertOAuthScopes("read:org, repo", new OAuthScope[]{});
        assertOAuthScopes("write:org, repo", new OAuthScope[]{});
        assertOAuthScopes("admin:org, repo", new OAuthScope[]{});
        assertOAuthScopes("repo", OAuthScope.OWNER);
        assertOAuthScopes("random", OAuthScope.OWNER, OAuthScope.REPOSITORY);
        assertOAuthScopes(null, OAuthScope.OWNER, OAuthScope.REPOSITORY);
        assertOAuthScopes("", OAuthScope.OWNER, OAuthScope.REPOSITORY);
        assertOAuthScopes("a, b, c", OAuthScope.OWNER, OAuthScope.REPOSITORY);
    }

    @Test
    public void testGetUser() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService(GITHUB_USER_URI + credential.getUsername(), userResponse);
        ExternalRequest request = new ExternalRequest.Builder(credential, gitHub()).build();
        assertUser(service.getUser(request), "octocat", "monalisa octocat", "https://github.com/images/error/octocat_happy.gif");
    }

    @Test
    public void testGetOwnersParsing() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService(GITHUB_ORGS_URI, teamResponse);
        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest.Builder(credential, gitHub()).build();

        Page<ExternalOwner> ownersPage = service.searchOwners(searchRequest, PageUtils.newRequest(0, 10));
        validateDefaultOwnersPage(ownersPage);
    }

    @Test
    public void testGetRepositoriesParsing() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService("https://api.github.com/search/repositories?" +
                "q=user:github_user+fork:true+&per_page=10&page=1", repositoryResponse);
        ExternalRepositorySearchRequest request = new ExternalRepositorySearchRequest.Builder(credential, gitHub())
                .build();

        Page<ExternalRepository> repositoriesPage = service.searchRepositories(request, PageUtils.newRequest(0, 10));
        validateDefaultRepositoryPage(repositoriesPage);
    }

    @Test
    public void testGetRepositoriesPagesProperly() throws Exception {
        String url = "https://api.github.com/search/repositories?" +
                "q=user:github_user+" +
                "fork:true+&" +
                "per_page=5&" +
                "page=3";
        GitHubRepositoryService service = setupGitHubRepoService(url, repositoryResponse);
        ExternalRepositorySearchRequest request = new ExternalRepositorySearchRequest.Builder(credential, gitHub())
                .build();
        Page<ExternalRepository> externalRepositoryPage = service.searchRepositories(request, PageUtils.newRequest(10, 5));
        validateDefaultRepositoryPage(externalRepositoryPage);
    }

    @Test
    public void testGetRepositoriesForGitHubEnterprise() throws Exception {
        String base = "https://github.somecompany.com";
        String url = base + "/api/v3/search/repositories?" +
                "q=atl+in:name,description+" +
                "user:importer+" +
                "fork:true+&" +
                "per_page=10&" +
                "page=1";
        Credential credential = new Credential.Builder("github_user", "github_password")
                .build();
        GitHubRepositoryService service = setupGitHubRepoService(url, repositoryResponse);
        ExternalRepositorySearchRequest request = new ExternalRepositorySearchRequest.Builder(credential, gitHub(base))
                .filter("atl")
                .ownerName("importer")
                .build();
        Page<ExternalRepository> externalRepositoryPage = service.searchRepositories(request, PageUtils.newRequest(0, 10));
        validateDefaultRepositoryPage(externalRepositoryPage);
    }

    @Test
    public void testSearchOwnersForGitHubEnterprise() throws Exception {
        String base = "https://github.somecompany.com";
        String url = base + "/api/v3/user/orgs?" +
                "per_page=10&" +
                "page=1";
        String userUrl = base + "/api/v3/user";
        Credential credential = new Credential.Builder("github_user", "github_password")
                .build();
        GitHubRepositoryService service = setupGitHubRepoService(ImmutableMap.of(url, teamResponse,
                userUrl, userResponse));
        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest.Builder(credential, gitHub(base))
                .build();

        Page<ExternalOwner> externalOwnerPage = service.searchOwners(searchRequest, PageUtils.newRequest(0, 10));
        validateDefaultOwnersPage(externalOwnerPage);
    }

    @Test(expected = ExternalRequestFailedException.class)
    public void testSearchOwnersWithEmptyResponse() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService(GITHUB_ORGS_URI, (String) null);
        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest.Builder(credential, gitHub())
                .build();

        service.searchOwners(searchRequest, PageUtils.newRequest(0, 10));
    }

    @Test(expected = ExternalRequestFailedException.class)
    public void testSearchOwnersWithMalformedResponse() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService(GITHUB_ORGS_URI, "Some Response");
        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest.Builder(credential, gitHub())
                .build();

        service.searchOwners(searchRequest, PageUtils.newRequest(0, 10));
    }

    @Test(expected = ExternalRequestFailedException.class)
    public void testSearchOwnersWithWrongJSONResponse() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService(GITHUB_ORGS_URI, (String) null);
        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest.Builder(credential, gitHub())
                .build();

        service.searchOwners(searchRequest, PageUtils.newRequest(0, 10));
    }

    @Test
    public void testSearchOwnersPageShouldNotBeLastIfNextLinkIsPresent() throws Exception {
        GitHubRepositoryService service = setupGitHubRepoService(ImmutableMap.of(GITHUB_ORGS_URI, getResponse(teamJson, GITHUB_LINK),
                GITHUB_USER_URI, userResponse));
        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest.Builder(credential, gitHub())
                .build();

        Page<ExternalOwner> ownerPage = service.searchOwners(searchRequest, PageUtils.newRequest(0, 10));
        assertFalse(ownerPage.getIsLastPage());
    }

    private void assertOAuthScopes(String headerValue, OAuthScope... oAuthScopes) throws IOException {
        GitHubRepositoryService service = setupGitHubRepoService(ImmutableMap.of(
                GITHUB_USER_URI + credential.getUsername(), getResponse("", "X-OAuth-Scopes", headerValue)));
        ExternalRequest request = new ExternalRequest.Builder(credential, gitHub()).build();
        if (oAuthScopes.length == 0) {
            assertTrue(Iterables.isEmpty(service.getMissingOAuthScopes(request)));
        } else {
            assertThat(service.getMissingOAuthScopes(request), IsIterableContainingInAnyOrder.containsInAnyOrder(oAuthScopes));
        }
    }

    private ExternalSource gitHub() {
        return gitHub(null);
    }

    private ExternalSource gitHub(String url) {
        return new ExternalSource.Builder(SourceType.GITHUB)
                .url(url)
                .build();
    }

    private GitHubRepositoryService setupGitHubRepoService(String url, String response) throws IOException {
        return setupGitHubRepoService(url, getResponse(response));
    }

    private GitHubRepositoryService setupGitHubRepoService(String url, Response response) throws IOException {
        return setupGitHubRepoService(ImmutableMap.of(url, response));
    }

    private GitHubRepositoryService setupGitHubRepoService(Map<String, Response> responseMap) throws IOException {
        StubRequestExecutor requestExecutor = new StubRequestExecutor(responseMap);
        return new GitHubRepositoryService(i18nService, propertiesService, requestExecutor, validator);
    }

    private void validateDefaultOwnersPage(Page<ExternalOwner> ownersPage) {
        assertTrue(ownersPage.getIsLastPage());

        Iterable<ExternalOwner> owners = ownersPage.getValues();
        assertEquals(2, Iterables.size(owners));

        assertOwner(Iterables.get(owners, 0), "atlassian", "A great-great organization", "https://atlassian.com/images/error/charlie.gif");
        assertOwner(Iterables.get(owners, 1), "github", "A great organization", "https://github.com/images/error/octocat_happy.gif");
    }

    private void validateDefaultRepositoryPage(Page<ExternalRepository> repositoriesPage) {
        assertTrue(repositoriesPage.getIsLastPage());

        Iterable<ExternalRepository> repositories = repositoriesPage.getValues();
        assertEquals(2, Iterables.size(repositories));

        assertRepository(Iterables.get(repositories, 0), "Tetris",
                "A C implementation of Tetris using Pennsim through LC4", "https://github.com/dtrupenn/Tetris.git");
        assertRepository(Iterables.get(repositories, 1), "drumoyne",
                "A Java implementation of Tetris", "https://github.com/dtrupenn/drumoyne.git");
    }
}