/*
 * Copyright (c) 2018, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root or
 * https://opensource.org/licenses/BSD-3-Clause
 */

package com.salesforce.dockerfileimageupdate.subcommands.impl;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.gson.JsonElement;
import com.salesforce.dockerfileimageupdate.utils.Constants;
import com.salesforce.dockerfileimageupdate.utils.DockerfileGitHubUtil;
import net.sourceforge.argparse4j.inf.Namespace;
import org.kohsuke.github.*;
import org.mockito.Mockito;
import org.testng.annotations.Test;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;

/**
 * Created by minho.park on 7/19/16.
 */
public class AllTest {
    @Test
    public void testForkRepositoriesFound() throws Exception {
        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);

        GHRepository contentRepo1 = mock(GHRepository.class);
        GHRepository contentRepo2 = mock(GHRepository.class);
        GHRepository contentRepo3 = mock(GHRepository.class);

        GHContent content1 = mock(GHContent.class);
        when(content1.getOwner()).thenReturn(contentRepo1);
        GHContent content2 = mock(GHContent.class);
        when(content2.getOwner()).thenReturn(contentRepo2);
        GHContent content3 = mock(GHContent.class);
        when(content3.getOwner()).thenReturn(contentRepo3);

        PagedSearchIterable<GHContent> contentsWithImage = mock(PagedSearchIterable.class);

        PagedIterator<GHContent> contentsWithImageIterator = mock(PagedIterator.class);
        when(contentsWithImageIterator.hasNext()).thenReturn(true, true, true, false);
        when(contentsWithImageIterator.next()).thenReturn(content1, content2, content3, null);
        when(contentsWithImage.iterator()).thenReturn(contentsWithImageIterator);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.forkRepositoriesFound(ArrayListMultimap.create(), ArrayListMultimap.create(), contentsWithImage, "image");

        Mockito.verify(dockerfileGitHubUtil, times(3)).getOrCreateFork(any());
    }

    @Test
    public void testForkRepositoriesFound_unableToforkRepo() throws Exception {
        /**
         * Suppose we have multiple dockerfiles that need to updated in a repo and we fail to fork such repo,
         * we should not add those repos to pathToDockerfilesInParentRepo.
         */
        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);

        GHRepository contentRepo1 = mock(GHRepository.class);
        when(contentRepo1.getFullName()).thenReturn("1");

        GHRepository contentRepo2 = mock(GHRepository.class);
        // Say we have multiple dockerfiles to be updated in repo "1"
        when(contentRepo2.getFullName()).thenReturn("1");

        GHRepository contentRepo3 = mock(GHRepository.class);
        when(contentRepo3.getFullName()).thenReturn("2");

        GHContent content1 = mock(GHContent.class);
        when(content1.getOwner()).thenReturn(contentRepo1);
        when(content1.getPath()).thenReturn("1"); // path to 1st dockerfile in repo "1"

        GHContent content2 = mock(GHContent.class);
        when(content2.getOwner()).thenReturn(contentRepo2);
        when(content2.getPath()).thenReturn("2"); // path to 2st dockerfile in repo "1"

        GHContent content3 = mock(GHContent.class);
        when(content3.getOwner()).thenReturn(contentRepo3);
        when(content3.getPath()).thenReturn("3");

        PagedSearchIterable<GHContent> contentsWithImage = mock(PagedSearchIterable.class);

        PagedIterator<GHContent> contentsWithImageIterator = mock(PagedIterator.class);
        when(contentsWithImageIterator.hasNext()).thenReturn(true, true, true, false);
        when(contentsWithImageIterator.next()).thenReturn(content1, content2, content3, null);
        when(contentsWithImage.iterator()).thenReturn(contentsWithImageIterator);
        when(dockerfileGitHubUtil.getOrCreateFork(contentRepo1)).thenReturn(null); // repo1 is unforkable
        when(dockerfileGitHubUtil.getOrCreateFork(contentRepo2)).thenReturn(null); // repo1 is unforkable
        when(dockerfileGitHubUtil.getOrCreateFork(contentRepo3)).thenReturn(new GHRepository());

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        Multimap<String, String> pathToDockerfilesInParentRepo = ArrayListMultimap.create();
        Multimap<String, String> imagesFoundInParentRepo = ArrayListMultimap.create();
        all.forkRepositoriesFound(pathToDockerfilesInParentRepo, imagesFoundInParentRepo, contentsWithImage, "image");

        // Since repo "1" is unforkable, we only added repo "2" to pathToDockerfilesInParentRepo
        assertEquals(pathToDockerfilesInParentRepo.size(), 1);
        assertEquals(imagesFoundInParentRepo.size(), 1);
        Mockito.verify(dockerfileGitHubUtil, times(3)).getOrCreateFork(any());
    }

    @Test
    public void testChangeDockerfiles_returnIfNotFork() throws Exception {
        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        GHRepository currUserRepo = mock(GHRepository.class);
        when(currUserRepo.isFork()).thenReturn(false);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(null,
                null,
                null,
                null,
                currUserRepo,
                new ArrayList<>());

        Mockito.verify(dockerfileGitHubUtil, times(0)).getRepo(anyString());
    }

    @Test
    public void testChangeDockerfiles_returnIfNotDesiredParent() throws Exception {

        GHRepository currUserRepo = mock(GHRepository.class);
        when(currUserRepo.isFork()).thenReturn(true);
        when(currUserRepo.getFullName()).thenReturn("forkedrepo5");

        GHRepository forkedRepo = mock(GHRepository.class);
        GHRepository parentRepo = mock(GHRepository.class);
        when(parentRepo.getFullName()).thenReturn("repo5");
        when(forkedRepo.getParent()).thenReturn(parentRepo);
        when(forkedRepo.getDefaultBranch()).thenReturn("branch");

        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        when(dockerfileGitHubUtil.getRepo(currUserRepo.getFullName())).thenReturn(forkedRepo);

        Multimap<String, String> pathToDockerfilesInParentRepo = HashMultimap.create();
        pathToDockerfilesInParentRepo.put("repo1", "df1");
        pathToDockerfilesInParentRepo.put("repo2", "df2");
        pathToDockerfilesInParentRepo.put("repo3", "df3");
        pathToDockerfilesInParentRepo.put("repo4", "df4");

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(null,
                pathToDockerfilesInParentRepo,
                null,
                null,
                currUserRepo,
                new ArrayList<>());

        Mockito.verify(dockerfileGitHubUtil, times(1)).getRepo(eq(currUserRepo.getFullName()));
        Mockito.verify(dockerfileGitHubUtil, times(0))
                .tryRetrievingContent(eq(forkedRepo), anyString(), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(0))
                .modifyOnGithub(any(), anyString(), anyString(), anyString(), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(0))
                .createPullReq(eq(parentRepo), anyString(), eq(forkedRepo), anyString());
    }

    @Test
    public void testChangeDockerfiles_returnWhenForkedRepoNotFound() throws Exception {

        GHRepository currUserRepo = mock(GHRepository.class);
        when(currUserRepo.isFork()).thenReturn(true);

        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        when(dockerfileGitHubUtil.getRepo(currUserRepo.getFullName())).thenThrow(FileNotFoundException.class);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(null, null, null, null,
                currUserRepo, new ArrayList<>());

        Mockito.verify(dockerfileGitHubUtil, times(1)).getRepo(anyString());
        Mockito.verify(dockerfileGitHubUtil, times(0)).tryRetrievingContent(any(),
                anyString(), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(0))
                .modifyOnGithub(any(), anyString(), anyString(), anyString(), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(0)).createPullReq(any(), anyString(), any(), anyString());

    }

    @Test
    public void testChangeDockerfiles_pullRequestCreation() throws Exception {
        Map<String, Object> nsMap = ImmutableMap.of(Constants.IMG,
                "image", Constants.TAG,
                "tag", Constants.STORE,
                "store");
        Namespace ns = new Namespace(nsMap);

        Multimap<String, String> pathToDockerfilesInParentRepo = ArrayListMultimap.create();
        pathToDockerfilesInParentRepo.put("repo1", "df1");
        pathToDockerfilesInParentRepo.put("repo2", "df2");
        pathToDockerfilesInParentRepo.put("repo3", "df3");
        pathToDockerfilesInParentRepo.put("repo4", "df4");

        Multimap<String, String> imagesFoundInParentRepo = ArrayListMultimap.create();
        imagesFoundInParentRepo.put("repo1", "image1");
        imagesFoundInParentRepo.put("repo2", "image2");
        imagesFoundInParentRepo.put("repo3", "image3");
        imagesFoundInParentRepo.put("repo4", "image4");

        Map<String, String> imageToTagMap = ImmutableMap.of(
                "image1", "tag1",
                "image2", "tag2",
                "image3", "tag3",
                "image4", "tag4");

        GHRepository currUserRepo = mock(GHRepository.class);
        when(currUserRepo.isFork()).thenReturn(true);
        when(currUserRepo.getFullName()).thenReturn("forkedrepo");

        GHRepository forkedRepo = mock(GHRepository.class);
        GHRepository parentRepo = mock(GHRepository.class);
        when(parentRepo.getFullName()).thenReturn("repo2");
        when(forkedRepo.getParent()).thenReturn(parentRepo);
        when(forkedRepo.getDefaultBranch()).thenReturn("branch");

        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        when(dockerfileGitHubUtil.getRepo(currUserRepo.getFullName())).thenReturn(forkedRepo);
        GHContent forkedRepoContent = mock(GHContent.class);
        when(dockerfileGitHubUtil.tryRetrievingContent(forkedRepo, "df2", forkedRepo.getDefaultBranch())).thenReturn(forkedRepoContent);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(ns, pathToDockerfilesInParentRepo, imagesFoundInParentRepo, imageToTagMap,
                currUserRepo, new ArrayList<>());

        Mockito.verify(dockerfileGitHubUtil, times(1)).getRepo(anyString());
        Mockito.verify(dockerfileGitHubUtil, times(1)).tryRetrievingContent(eq(forkedRepo),
                eq("df2"), eq("branch"));
        Mockito.verify(dockerfileGitHubUtil, times(1))
                .modifyOnGithub(any(), eq("branch"), eq("image2"), eq("tag2"), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(1)).createPullReq(eq(parentRepo),
                eq("branch"), eq(forkedRepo), anyString());
    }

    @Test
    public void testOnePullRequestForMultipleDockerfilesInSameRepo() throws Exception {
        Map<String, Object> nsMap = ImmutableMap.of(Constants.IMG,
                "image", Constants.TAG,
                "tag", Constants.STORE,
                "store");
        Namespace ns = new Namespace(nsMap);

        Multimap<String, String> pathToDockerfilesInParentRepo = ArrayListMultimap.create();
        pathToDockerfilesInParentRepo.put("repo1", "df11");
        pathToDockerfilesInParentRepo.put("repo1", "df12");
        pathToDockerfilesInParentRepo.put("repo3", "df3");
        pathToDockerfilesInParentRepo.put("repo4", "df4");

        Multimap<String, String> imagesFoundInParentRepo = ArrayListMultimap.create();
        imagesFoundInParentRepo.put("repo1", "image11");
        imagesFoundInParentRepo.put("repo1", "image12");
        imagesFoundInParentRepo.put("repo3", "image3");
        imagesFoundInParentRepo.put("repo4", "image4");

        Map<String, String> imageToTagMap = ImmutableMap.of(
                "image11", "tag11",
                "image12", "tag12",
                "image3", "tag3",
                "image4", "tag4");

        GHRepository currUserRepo = mock(GHRepository.class);
        when(currUserRepo.isFork()).thenReturn(true);
        when(currUserRepo.getFullName()).thenReturn("forkedrepo");

        GHRepository forkedRepo = mock(GHRepository.class);
        GHRepository parentRepo = mock(GHRepository.class);
        when(parentRepo.getFullName()).thenReturn("repo1");
        when(forkedRepo.getParent()).thenReturn(parentRepo);
        when(forkedRepo.getDefaultBranch()).thenReturn("branch");

        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        when(dockerfileGitHubUtil.getRepo(currUserRepo.getFullName())).thenReturn(forkedRepo);
        GHContent forkedRepoContent1 = mock(GHContent.class);
        when(dockerfileGitHubUtil.tryRetrievingContent(eq(forkedRepo),
                eq("df11"), eq("branch"))).thenReturn(forkedRepoContent1);
        GHContent forkedRepoContent2 = mock(GHContent.class);
        when(dockerfileGitHubUtil.tryRetrievingContent(eq(forkedRepo),
                eq("df12"), eq("branch"))).thenReturn(forkedRepoContent2);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(ns, pathToDockerfilesInParentRepo, imagesFoundInParentRepo, imageToTagMap,
                currUserRepo, new ArrayList<>());

        // Get repo only once
        Mockito.verify(dockerfileGitHubUtil, times(1)).getRepo(anyString());

        // Both Dockerfiles retrieved from the same repo
        Mockito.verify(dockerfileGitHubUtil, times(1)).tryRetrievingContent(eq(forkedRepo),
                eq("df11"), eq("branch"));
        Mockito.verify(dockerfileGitHubUtil, times(1)).tryRetrievingContent(eq(forkedRepo),
                eq("df12"), eq("branch"));

        // Both Dockerfiles modified
        Mockito.verify(dockerfileGitHubUtil, times(1))
                .modifyOnGithub(any(), eq("branch"), eq("image11"), eq("tag11"), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(1))
                .modifyOnGithub(any(), eq("branch"), eq("image12"), eq("tag12"), anyString());

        // Only one PR created on the repo with changes to both Dockerfiles.
        Mockito.verify(dockerfileGitHubUtil, times(1)).createPullReq(eq(parentRepo),
                eq("branch"), eq(forkedRepo), anyString());
    }

    @Test
    public void testNoPullRequestForMissingDockerfile() throws Exception {
        Map<String, Object> nsMap = ImmutableMap.of(Constants.IMG,
                "image", Constants.TAG,
                "tag", Constants.STORE,
                "store");
        Namespace ns = new Namespace(nsMap);

        Multimap<String, String> pathToDockerfilesInParentRepo = ArrayListMultimap.create();
        pathToDockerfilesInParentRepo.put("repo1", "missing_df");

        Multimap<String, String> imagesFoundInParentRepo = ArrayListMultimap.create();
        imagesFoundInParentRepo.put("repo1", "test");

        Map<String, String> imageToTagMap = ImmutableMap.of(
                "image1", "tag1",
                "image2", "tag2");

        GHRepository currUserRepo = mock(GHRepository.class);
        when(currUserRepo.isFork()).thenReturn(true);
        when(currUserRepo.getFullName()).thenReturn("forkedrepo");

        GHRepository forkedRepo = mock(GHRepository.class);
        GHRepository parentRepo = mock(GHRepository.class);
        when(parentRepo.getFullName()).thenReturn("repo1");
        when(forkedRepo.getParent()).thenReturn(parentRepo);
        when(forkedRepo.getFullName()).thenReturn("forkedrepo");
        when(forkedRepo.getDefaultBranch()).thenReturn("branch");

        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        when(dockerfileGitHubUtil.getRepo(currUserRepo.getFullName())).thenReturn(forkedRepo);
        // Dockerfile not found anymore when trying to retrieve contents from the forked repo.
        when(dockerfileGitHubUtil.tryRetrievingContent(eq(forkedRepo),
                eq("missing_df"), eq("branch"))).thenReturn(null);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(ns, pathToDockerfilesInParentRepo, imagesFoundInParentRepo, imageToTagMap,
                currUserRepo, new ArrayList<>());

        // fetch repo
        Mockito.verify(dockerfileGitHubUtil, times(1)).getRepo(anyString());

        // trying to retrieve Dockerfile
        Mockito.verify(dockerfileGitHubUtil, times(1)).tryRetrievingContent(eq(forkedRepo),
                eq("missing_df"), eq("branch"));

        // missing Dockerfile, so skipping modify and create PR
        Mockito.verify(dockerfileGitHubUtil, times(0))
                .modifyOnGithub(any(), anyString(), anyString(), anyString(), anyString());
        Mockito.verify(dockerfileGitHubUtil, times(0)).createPullReq(eq(parentRepo),
                anyString(), eq(forkedRepo), anyString());
    }

    @Test
    public void testPullRequestToAForkIsUnSupported() throws Exception {

        GHRepository parentRepo = mock(GHRepository.class);
        // When the repo is a fork then skip it.
        when(parentRepo.isFork()).thenReturn(true);
        GHContent content = mock(GHContent.class);
        when(content.getOwner()).thenReturn(parentRepo);

        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        PagedSearchIterable<GHContent> contentsWithImage = mock(PagedSearchIterable.class);
        PagedIterator<GHContent> contentsWithImageIterator = mock(PagedIterator.class);
        when(contentsWithImageIterator.hasNext()).thenReturn(true, false);
        when(contentsWithImageIterator.next()).thenReturn(content, null);
        when(contentsWithImage.iterator()).thenReturn(contentsWithImageIterator);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.forkRepositoriesFound(ArrayListMultimap.create(), ArrayListMultimap.create(), contentsWithImage, "image");
        Mockito.verify(dockerfileGitHubUtil, times(0)).getOrCreateFork(any());
    }

    @Test
    public void testParseStoreToImagesMap() throws Exception {
        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        when(dockerfileGitHubUtil.getMyself()).thenReturn(mock(GHMyself.class));
        when(dockerfileGitHubUtil.getRepo(anyString())).thenReturn(mock(GHRepository.class));
        GHContent mockContent = mock(GHContent.class);
        ClassLoader classloader = Thread.currentThread().getContextClassLoader();
        when(mockContent.read()).thenReturn(classloader.getResourceAsStream("image-store-sample.json"));
        when(dockerfileGitHubUtil.tryRetrievingContent(any(GHRepository.class), anyString(), anyString())).thenReturn(mockContent);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        Set<Map.Entry<String, JsonElement>> imageSet = all.parseStoreToImagesMap("testStore");
        assertNotNull(imageSet);
    }

    @Test
    public void checkPullRequestNotMadeForArchived() throws Exception {
        final String repoName = "mock repo";
        Map<String, Object> nsMap = ImmutableMap.of(Constants.IMG,
                "image", Constants.TAG,
                "tag", Constants.STORE,
                "store");
        Namespace ns = new Namespace(nsMap);

        GHRepository parentRepo = mock(GHRepository.class);
        GHRepository forkRepo = mock(GHRepository.class);
        DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
        GHMyself myself = mock(GHMyself.class);

        when(parentRepo.isArchived()).thenReturn(true);

        when(forkRepo.getFullName()).thenReturn(repoName);
        when(parentRepo.getFullName()).thenReturn(repoName);
        when(dockerfileGitHubUtil.getRepo(eq(repoName))).thenReturn(forkRepo);
        when(forkRepo.isFork()).thenReturn(true);
        when(forkRepo.getParent()).thenReturn(parentRepo);
        when(dockerfileGitHubUtil.getMyself()).thenReturn(myself);

        Multimap<String, String> pathToDockerfilesInParentRepo = HashMultimap.create();
        pathToDockerfilesInParentRepo.put(repoName, null);

        All all = new All();
        all.loadDockerfileGithubUtil(dockerfileGitHubUtil);
        all.changeDockerfiles(ns, pathToDockerfilesInParentRepo, null, null, forkRepo, null);

        Mockito.verify(dockerfileGitHubUtil, Mockito.never())
                .createPullReq(Mockito.any(), anyString(), Mockito.any(), anyString());
        //Make sure we at least check if its archived
        Mockito.verify(parentRepo, Mockito.times(2)).isArchived();
    }
}