package com.atlassian.stash.internal.web.markup;

import com.atlassian.bitbucket.AuthorisationException;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.nav.DummyNavBuilder;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestService;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.markup.renderer.RenderContext;
import com.atlassian.markup.renderer.RenderTransform;
import com.atlassian.markup.renderer.Replacement;
import com.atlassian.stash.internal.project.InternalNormalProject;
import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.stash.internal.pull.InternalPullRequest;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.beans.DirectFieldAccessor;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;

@RunWith(Parameterized.class)
public class PullRequestMarkupRendererTest {

    @SuppressWarnings("ThrowableInstanceNeverThrown")
    private static final AuthorisationException AUTHORISATION_EXCEPTION = new AuthorisationException(new KeyedMessage("", "", ""));

    private PullRequestService pullRequestService;
    private RepositoryService repositoryService;
    private PullRequestMarkupRenderer renderer;

    @Parameterized.Parameters(name = "markup = \"{0}\"")
    public static Collection<Object[]> testCases() {

        PullRequestLink sameRepoLink = new PullRequestLink("SCRIPT", "repo", 1, 37);
        PullRequestLink sameProjectLink = new PullRequestLink("SCRIPT", "2accessible", 3, 42);
        PullRequestLink otherProjectLink = new PullRequestLink("OTHER", "99other", 4, 55);
        PullRequestLink personalRepoLink = new PullRequestLink("~USER", "my_repo", 5, 66);
        String[] emptyMatches = new String[]{};
        PullRequestLink[] emptyLinks = new PullRequestLink[]{};

        return Arrays.asList(new Object[][]{
                {"#37", new String[]{"#37"}, new PullRequestLink[]{sameRepoLink}},
                {"This is fixed in #37, which was awesome.", new String[]{"#37"}, new PullRequestLink[]{sameRepoLink}},
                {"repo#37--2accessible#42", new String[]{"repo#37", "2accessible#42"}, new PullRequestLink[]{sameRepoLink, sameProjectLink}},
                {"PR other/99other#55, foo.", new String[]{"PR other/99other#55"}, new PullRequestLink[]{otherProjectLink}},
                {"script/repo#37", new String[]{"script/repo#37"}, new PullRequestLink[]{sameRepoLink}},
                {"SCRIPT/repo#37", new String[]{"SCRIPT/repo#37"}, new PullRequestLink[]{sameRepoLink}},
                {"SCRIPT/REPO#37", new String[]{"SCRIPT/REPO#37"}, new PullRequestLink[]{sameRepoLink}},
                {"link to pull request SCRIPT/REPO#37, ok?", new String[]{"pull request SCRIPT/REPO#37"}, new PullRequestLink[]{sameRepoLink}},
                {"Also could be this one (PR #37)", new String[]{"PR #37"}, new PullRequestLink[]{sameRepoLink}},
                {"This is in my personal project PR ~user/my_repo#66, ok?", new String[]{"PR ~user/my_repo#66"}, new PullRequestLink[]{personalRepoLink}},
                {"http://foo/other/99other#55, foo.", emptyMatches, emptyLinks},
                {"[foobar#37](#38)", emptyMatches, emptyLinks},
                {"foo#38)", emptyMatches, emptyLinks},
                {"37#38)", emptyMatches, emptyLinks},
                {"<a href='foo#38'>foo</a>)", emptyMatches, emptyLinks},
                {" ", emptyMatches, emptyLinks},
                {"", emptyMatches, emptyLinks}
        });
    }

    private final String[] matches;
    private final String markup;
    private final PullRequestLink[] pullRequestLinks;

    public PullRequestMarkupRendererTest(String markup, String[] matches, PullRequestLink[] pullRequestLinks) {
        this.markup = markup;
        this.pullRequestLinks = pullRequestLinks;
        this.matches = matches;
    }

    @Before
    public void setup() throws Exception {
        NavBuilder navBuilder = DummyNavBuilder.create();
        pullRequestService = mock(PullRequestService.class);
        repositoryService = mock(RepositoryService.class);

        InternalProject project = new InternalNormalProject.Builder()
                .id(1)
                .key("SCRIPT")
                .name("<script>alert(1);</script>")
                .build();

        InternalProject inaccessibleProject = new InternalNormalProject.Builder()
                .id(2)
                .key("INACC")
                .name("Inaccessible")
                .build();

        InternalProject otherProject = new InternalNormalProject.Builder()
                .id(3)
                .key("OTHER")
                .name("Other")
                .build();

        InternalProject personalProject = new InternalNormalProject.Builder()
                .id(4)
                .key("~USER")
                .name("User's personal project")
                .build();

        when(repositoryService.getBySlug(anyString(), anyString())).thenReturn(null);
        when(repositoryService.getBySlug(eq(inaccessibleProject.getKey()), anyString())).thenThrow(AUTHORISATION_EXCEPTION);

        InternalRepository repo = mockRepository(project, 1, "repo", true);
        InternalRepository accessibleRepo2 = mockRepository(project, 3, "2accessible", true);
        InternalRepository otherRepo = mockRepository(otherProject, 4, "99other", true);
        InternalRepository personalRepo = mockRepository(personalProject, 5, "my_repo", true);
        mockRepository(project, 2, "inaccessible", false);

        mockPullRequest(repo, 37L);
        mockPullRequest(accessibleRepo2, 42L);
        mockPullRequest(otherRepo, 55L);
        mockPullRequest(personalRepo, 66L);

        renderer = new PullRequestMarkupRenderer(navBuilder, pullRequestService, repositoryService);
        new DirectFieldAccessor(renderer).setPropertyValue("lookupTimeout", 2000);
    }

    @Test
    public void testRender() throws Exception {
        RenderTransform transform = renderer.process(markup, new RenderContext("https://example.com/stash", ImmutableMap.<String, Object>of("repositoryId", 1)));

        List<Replacement> replacements = MarkupTestUtils.sort(transform.getReplacements());
        assertThat("mismatch on: " + markup, replacements, hasSize(pullRequestLinks.length));
        for (int i = 0; i < pullRequestLinks.length; i++) {
            PullRequestLink pullRequestLink = pullRequestLinks[i];
            Replacement replacement = replacements.get(i);
            assertThat(replacement.getValue().toString(), allOf(
                    startsWith("<a"),
                    containsString("\"https://example.com/stash/repo(" + pullRequestLink.projectKey + "/" +
                            pullRequestLink.repoSlug + "[" + pullRequestLink.repoId + "]).pullRequest(" +
                            pullRequestLink.pullRequestId + ").buildRelNoContext()\""),
                    containsString(">" + this.matches[i] + "<"),
                    containsString("class=\"pull-request\""),
                    not(containsString("<script>alert(1);</script>")),
                    containsString("&lt;script&gt;alert(1);&lt;/script&gt;"),
                    endsWith("</a>")));
        }
    }

    private static class PullRequestLink {
        String projectKey;
        String repoSlug;
        int repoId;
        long pullRequestId;

        public PullRequestLink(String projectKey, String repoSlug, int repoId, long pullRequestId) {
            this.projectKey = projectKey;
            this.repoSlug = repoSlug;
            this.repoId = repoId;
            this.pullRequestId = pullRequestId;
        }
    }

    private InternalRepository mockRepository(InternalProject project, int id, String name, boolean accessible) {
        InternalRepository repo = new InternalRepository.Builder().id(id)
                .name(name).hierarchyId("42").project(project).build();

        if (accessible) {
            when(repositoryService.getById(repo.getId())).thenReturn(repo);
            when(repositoryService.getBySlug(
                    argThat(equalToIgnoringCase(project.getKey())),
                    argThat(equalToIgnoringCase(repo.getSlug()))
            )).thenReturn(repo);
        } else {
            when(repositoryService.getById(id)).thenThrow(AUTHORISATION_EXCEPTION);
            when(repositoryService.getBySlug(
                    argThat(equalToIgnoringCase(project.getKey())),
                    argThat(equalToIgnoringCase(repo.getSlug()))
            )).thenThrow(AUTHORISATION_EXCEPTION);
        }

        return repo;
    }

    private void mockPullRequest(InternalRepository repo, long pullRequestId) {
        PullRequest pullRequest = mock(InternalPullRequest.class, RETURNS_DEEP_STUBS);
        when(pullRequest.getId()).thenReturn(pullRequestId);
        when(pullRequest.getToRef().getRepository()).thenReturn(repo);
        when(pullRequest.getTitle()).thenReturn("<script>alert(1);</script>");

        when(pullRequestService.getById(repo.getId(), pullRequest.getId())).thenReturn(pullRequest);
    }

}
