package com.atlassian.stash.internal.scm.git.protocol.ssh;

import com.atlassian.bitbucket.help.HelpPathService;
import com.atlassian.bitbucket.hook.HookService;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.mockito.MockitoUtils;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.NoSuchRepositoryException;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.request.RequestManager;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.ssh.ExitCodeCallback;
import com.atlassian.bitbucket.scm.ssh.SshScmRequest;
import com.atlassian.bitbucket.scm.throttle.ThrottledScmRequestFactory;
import com.atlassian.bitbucket.throttle.ThrottleService;
import com.atlassian.bitbucket.user.DummySecurityService;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.stash.internal.scm.git.GitWriteTracker;
import com.atlassian.stash.internal.scm.git.InternalGitScmConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Optional;

import static java.util.Optional.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DefaultGitSshScmRequestHandlerTest {

    @Mock
    private GitCommandBuilderFactory builderFactory;
    @Mock
    private InternalGitScmConfig config;
    @Mock
    private EventPublisher eventPublisher;
    @Mock
    private HelpPathService helpPathService;
    @Mock
    private HookService hookService;
    @Spy
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private Project project;
    @Mock
    private Repository repository;
    @Mock
    private RepositoryService repositoryService;
    @Mock
    private RequestManager requestManager;
    @Spy
    private SecurityService securityService = new DummySecurityService();
    @Mock
    private ThrottledScmRequestFactory throttledFactory;
    @Mock
    private ThrottleService throttleService;
    @Mock
    private GitWriteTracker writeTracker;
    @Mock
    private InputStream in;
    @Mock
    private OutputStream out;
    @Mock
    private OutputStream err;
    @Mock
    private ExitCodeCallback exitCodeCallback;

    @InjectMocks
    private DefaultGitSshScmRequestHandler requestHandler;

    @Before
    public void setUp() {
        when(throttledFactory.throttled(any(SshScmRequest.class))).thenAnswer(MockitoUtils.returnFirst());
        when(repository.getProject()).thenReturn(project);
        when(repositoryService.getBySlug(null, "stash", "stash")).thenReturn(repository);
    }

    @Test
    public void testNamespacedRepo() {
        when(project.getNamespace()).thenReturn("namespace");
        when(repositoryService.getBySlug("namespace", "stash", "stash")).thenReturn(repository);
        SshScmRequest request = assertSupported("git-upload-pack 'namespace/stash/stash.git'");
        assertTrue(request instanceof SshUploadPack);

        //SshUploadPack manages throttling internally and should not be decorated through throttledFactory
        verify(throttledFactory, never()).throttled(any(SshScmRequest.class));
    }

    @Test
    public void testNonGitCommand() {
        assertUnsupported("whoami");
    }

    @Test
    public void testReceivePack() {
        SshScmRequest request = assertSupported("git-receive-pack 'stash/stash.git'");
        assertTrue(request instanceof SshReceivePack);

        verify(throttledFactory).throttled(any(SshScmRequest.class));
    }

    @Test
    public void testUploadArchive() {
        SshScmRequest request = assertSupported("git-upload-archive 'stash/stash.git'");
        assertTrue(request instanceof SshUploadArchive);

        verify(throttledFactory).throttled(any(SshScmRequest.class));
    }

    @Test
    public void testUploadPack() {
        SshScmRequest request = assertSupported("git-upload-pack 'stash/stash.git'");
        assertTrue(request instanceof SshUploadPack);

        //SshUploadPack manages throttling internally and should not be decorated through throttledFactory
        verify(throttledFactory, never()).throttled(any(SshScmRequest.class));
    }

    @Test(expected = NoSuchRepositoryException.class)
    public void testUploadPackInvalidArgument() {
        requestHandler.create("git-upload-pack 'stash.git'", in, out, err, exitCodeCallback);
    }

    @Test
    public void testUnsupportedGitCommand() {
        assertUnsupported("git-pack-objects 'stash/stash.git'");
    }

    @Test(expected = NoSuchRepositoryException.class)
    public void testRepositoryNotFound() {
        requestHandler.create("git-upload-pack 'invalidproject/invalidrepository.git'", in, out, err, exitCodeCallback);
    }

    @Test(expected = NoSuchRepositoryException.class)
    public void testNamespacedRepositoryNotFound() {
        requestHandler.create("git-upload-pack 'invalidnamespace/invalidproject/invalidrepository.git'",
                in, out, err, exitCodeCallback);
    }

    @Test(expected = NoSuchRepositoryException.class)
    public void testUnexpectedRepositoryPathNotFound() {
        requestHandler.create("git-upload-pack 'four/levels/deep/repo.git'", in, out, err, exitCodeCallback);
    }

    private SshScmRequest assertSupported(String command) {
        assertThat(requestHandler.supports(command), is(true));
        Optional<SshScmRequest> result = requestHandler.create(command, in, out, err, exitCodeCallback);
        assertThat(result, not(empty()));
        return result.get();
    }

    private void assertUnsupported(String command) {
        assertThat(requestHandler.supports(command), is(false));
        assertThat(requestHandler.create(command, in, out, err, exitCodeCallback), is(empty()));
    }
}