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

import com.atlassian.bitbucket.internal.importer.repository.RepositoryImportService;
import com.atlassian.bitbucket.internal.importer.repository.RepositoryImportTask;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

import static com.atlassian.bitbucket.internal.importer.repository.ImportTaskState.*;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class RepositoryImportFilterTest {

    private static final String PROJECT_KEY = "STASH";
    private static final String REPO_SLUG = "stash";
    private static final String REDIRECT_URL = "redirectUrl";

    @Mock
    private FilterChain chain;
    @Mock
    private NavBuilder navBuilder;
    @Mock
    private Repository repository;
    @Mock
    private RepositoryImportService repositoryImportService;
    @Mock
    private RepositoryService repositoryService;
    @Mock
    private HttpServletRequest request;
    @Mock
    private HttpServletResponse response;
    private RepositoryImportTask.Builder taskBuilder;

    @InjectMocks
    private RepositoryImportFilter filter;

    @Before
    public void setup() {
        // Setup ImportTask Builder
        taskBuilder = new RepositoryImportTask.Builder()
                .cloneUrl("")
                .created(new Date())
                .externalRepoName("")
                .lastUpdated(new Date())
                .repository(repository)
                .taskId(1);

        // Mock Project & Repo
        Project project = mock(Project.class);
        when(repository.getProject()).thenReturn(project);
        when(project.getKey()).thenReturn(PROJECT_KEY);
        when(repository.getSlug()).thenReturn(REPO_SLUG);
        when(repositoryService.getBySlug(PROJECT_KEY, REPO_SLUG)).thenReturn(repository);
        // Default path info for happy case. Other case tests will override
        when(request.getPathInfo()).thenReturn(String.format("/projects/%s/repos/%s/browse", PROJECT_KEY, REPO_SLUG));
        when(request.getMethod()).thenReturn("GET");

        // Mock NavBuilder calls
        NavBuilder.PluginServlets pluginServlets = mock(NavBuilder.PluginServlets.class);
        when(navBuilder.pluginServlets()).thenReturn(pluginServlets);
        NavBuilder.PathBuilder pathBuilder = mock(NavBuilder.PathBuilder.class);
        when(pluginServlets.path("import-in-process",
                "projects", repository.getProject().getKey(),
                "repos", repository.getSlug())).thenReturn(pathBuilder);
        when(pathBuilder.buildRelative()).thenReturn(REDIRECT_URL);
    }

    @Test
    public void testFilterChainsWhenPathInfoIsNull() throws Exception {
        when(request.getPathInfo()).thenReturn(null);

        filter.doFilter(request, response, chain);

        verifyChainForNoRepo();
    }

    @Test
    public void testFilterChainsWhenPathInfoIsEmpty() throws Exception {
        when(request.getPathInfo()).thenReturn("");

        filter.doFilter(request, response, chain);

        verifyChainForNoRepo();
    }

    @Test
    public void testFilterChainsWhenPathInfoIsSlash() throws Exception {
        when(request.getPathInfo()).thenReturn("/");

        filter.doFilter(request, response, chain);

        verifyChainForNoRepo();
    }

    @Test
    public void testFilterChainsWhenPathInfoNotRepoPath() throws Exception {
        when(request.getPathInfo()).thenReturn("/projects/STASH/somePath/somePath");

        filter.doFilter(request, response, chain);

        verifyChainForNoRepo();
    }

    @Test
    public void testFilterChainsWhenPathIsRepositoryThatDoesntExist() throws Exception {
        when(repositoryService.getBySlug(PROJECT_KEY, REPO_SLUG)).thenReturn(null);
        filter.doFilter(request, response, chain);

        verify(repositoryService).getBySlug(PROJECT_KEY, REPO_SLUG);
        verify(repositoryImportService, never()).getRepositoryImportTask(any());
        verifyChain();
    }

    @Test
    public void testFilterChainsWhenRepositoryHasNoImportTasks() throws Exception {
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(empty());

        filter.doFilter(request, response, chain);

        verifyChainForRepository();
    }

    @Test
    public void testFilterChainsWhenPathIsJustRepoPath() throws Exception {
        when(request.getPathInfo()).thenReturn("/projects/STASH/repos/stash/");
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(SUCCESS).build()));

        filter.doFilter(request, response, chain);

        verifyChainForRepository();
    }

    @Test
    public void testFilterChainsWhenRepositoryImportTaskIsFAILED() throws Exception {
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(FAILED).build()));

        filter.doFilter(request, response, chain);

        verifyChainForRepository();
    }

    @Test
    public void testFilterChainsWhenRepositoryImportTaskIsSUCCESS() throws Exception {
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(SUCCESS).build()));

        filter.doFilter(request, response, chain);

        verifyChainForRepository();
    }

    @Test
    public void testFilterChainsWhenRequestIsNotGet() throws Exception {
        when(request.getMethod()).thenReturn("DELETE");

        filter.doFilter(request, response, chain);

        verifyChainForNoRepo();
    }

    @Test
    public void testFilterRedirectsWhenRepositoryImportTaskIsQUEUED() throws Exception {
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(QUEUED).build()));

        filter.doFilter(request, response, chain);

        verifyRedirect();
    }

    @Test
    public void testFilterRedirectsWhenRepositoryImportTaskIsSTARTED() throws Exception {
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(STARTED).build()));

        filter.doFilter(request, response, chain);

        verifyRedirect();
    }

    @Test
    public void testFilterRedirectsWhenPathIsSettingsPath() throws Exception {
        when(request.getPathInfo()).thenReturn("/projects/STASH/repos/stash/settings");
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(STARTED).build()));

        filter.doFilter(request, response, chain);

        verifyRedirect();
    }

    @Test
    public void testFilterRedirectsWhenMethodIsGet() throws Exception {
        when(request.getMethod()).thenReturn("GET");
        when(repositoryImportService.getRepositoryImportTask(repository)).thenReturn(of(taskBuilder.state(STARTED).build()));

        filter.doFilter(request, response, chain);

        verifyRedirect();
    }

    private void verifyRedirect() throws IOException, ServletException {
        verify(response).sendRedirect(REDIRECT_URL);
        verify(chain, never()).doFilter(any(), any());
    }

    private void verifyChain() throws IOException, ServletException {
        verify(response, never()).sendRedirect(any());
        verify(chain).doFilter(request, response);
    }

    private void verifyChainForNoRepo() throws IOException, ServletException {
        verify(repositoryService, never()).getBySlug(any(), any());
        verify(repositoryImportService, never()).getRepositoryImportTask(any());
        verifyChain();
    }

    private void verifyChainForRepository() throws IOException, ServletException {
        verify(repositoryService).getBySlug(PROJECT_KEY, REPO_SLUG);
        verify(repositoryImportService).getRepositoryImportTask(repository);
        verifyChain();
    }
}