package com.atlassian.bitbucket.internal.scm.git.lfs.mirror;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.internal.scm.git.lfs.model.OperationType;
import com.atlassian.bitbucket.internal.scm.git.lfs.model.ResponseAction;
import com.atlassian.bitbucket.internal.scm.git.lfs.rest.model.RestBatchRequest;
import com.atlassian.bitbucket.json.JsonRenderer;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamAccount;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServer;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType;
import com.atlassian.bitbucket.mirroring.mirror.client.UpstreamClient;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.httpclient.api.Request;
import com.google.common.net.HttpHeaders;
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 org.osgi.framework.BundleContext;

import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DefaultUpstreamLfsClientTest {

    @Mock
    private BundleContext bundleContext;
    @Mock
    private I18nService i18nService;
    @Mock
    private JsonRenderer jsonRenderer;

    @InjectMocks
    private DefaultUpstreamLfsClient underTest;

    @Mock
    private Project project;
    @Mock
    private Repository repository;
    @Mock
    private RepositoryService repositoryService;
    @Mock
    private UpstreamAccount upstreamAccount;
    @Mock
    private UpstreamServer upstreamServer;
    @Mock
    private UpstreamClient upstreamClient;
    @Mock
    private Request.Builder builder;

    private static final String PROJECT_KEY = "PROJ";
    private static final String REPOSITORY_SLUG = "repo_slug";
    private static final String TEAM_NAME = "team_name";
    private static final String BATCH_CLOUD_URL_FORMAT = "/%s/%s.git/info/lfs/objects/batch";
    private static final String BATCH_SERVER_URL_FORMAT = "/scm/%s/%s.git/info/lfs/objects/batch";

    @Before
    public void setUp() {
        when(repository.getSlug()).thenReturn(REPOSITORY_SLUG);
        when(repository.getProject()).thenReturn(project);
        when(project.getKey()).thenReturn(PROJECT_KEY);
        when(upstreamServer.getType()).thenReturn(UpstreamServerType.BITBUCKET_CLOUD);
        when(upstreamServer.getAccount()).thenReturn(Optional.of(upstreamAccount));
        when(upstreamAccount.getName()).thenReturn(TEAM_NAME);
        when(jsonRenderer.render(anyObject(), anyMap()))
                .thenAnswer(invocation -> invocation.getArgumentAt(0, RestBatchRequest.class).toString());
    }

    @Test
    public void testBatchURLReturnsServerURL() throws Exception {
        when(upstreamServer.getType()).thenReturn(UpstreamServerType.BITBUCKET_SERVER);

        String batchURL = DefaultUpstreamLfsClient.batchUrl(repository, upstreamServer);

        assertEquals(batchURL, String.format(BATCH_SERVER_URL_FORMAT, PROJECT_KEY, REPOSITORY_SLUG));
    }

    @Test
    public void testBatchURLReturnsCloudURL() throws Exception {
        String batchURL = DefaultUpstreamLfsClient.batchUrl(repository, upstreamServer);

        assertEquals(batchURL, String.format(BATCH_CLOUD_URL_FORMAT, TEAM_NAME, REPOSITORY_SLUG));
    }

    @Test
    public void testRequestUpstreamActions() {
        when(upstreamClient.newScmHttpRequest(anyString())).thenReturn(builder);
        when(builder.setContentType(anyString())).thenReturn(builder);
        when(builder.setEntity(anyString())).thenReturn(builder);

        underTest.requestUpstreamActions(upstreamClient, upstreamServer, repository, "oid");

        verify(builder).setContentType("application/json");
        verify(builder).setEntity(contains("download"));
        verify(builder).post();
    }

    @Test
    public void testParseResponseAction() {
        String responseEntity = "{" +
                "   \"objects\": [" +
                "     {" +
                "       \"oid\": \"1111111\"," +
                "       \"size\": 123," +
                "       \"actions\": {" +
                "         \"verify\": {" +
                "           \"href\": \"https://some-upload.com\"," +
                "           \"header\": {" +
                "             \"Key\": \"value\"" +
                "           }" +
                "         }," +
                "         \"download\": {" +
                "           \"href\": \"https://some-upload.com\"," +
                "           \"header\": {" +
                "             \"Key\": \"value\"" +
                "           }" +
                "         }" +
                "       }" +
                "     }" +
                "   ]" +
                " }";
        ResponseAction responseAction = DefaultUpstreamLfsClient.parseResponseAction(responseEntity);
        assertEquals(1, responseAction.getHeaders().size());
        assertEquals("value", responseAction.getHeaders().get("Key"));
        assertEquals("https://some-upload.com", responseAction.getHref());
        assertEquals(OperationType.DOWNLOAD, responseAction.getType());
        assertFalse(responseAction.getHeaders().containsKey(HttpHeaders.AUTHORIZATION));
    }

    @Test(expected = RuntimeException.class)
    public void testParseEmptyObjectResponseActionThrows() {
        String responseEntity = "{ \"objects\": [] }";
        DefaultUpstreamLfsClient.parseResponseAction(responseEntity);
    }

    @Test(expected = RuntimeException.class)
    public void testParseInvalidResponseActionThrows() {
        String responseEntity = "{" +
                "   \"objects\": [" +
                "     {" +
                "       \"oid\": \"1111111\"," +
                "       \"size\": 123," +
                "       \"actions\": {" +
                "         \"upload\": {" +
                "           \"href\": \"https://some-upload.com\"," +
                "           \"header\": {" +
                "             \"Key\": \"value\"" +
                "           }" +
                "         }" +
                "       }" +
                "     }" +
                "   ]" +
                " }";
        DefaultUpstreamLfsClient.parseResponseAction(responseEntity);
    }

    @Test(expected = RuntimeException.class)
    public void testParseReturnsErrorResponseThrows() {
        String responseEntity = "{\n" +
                "  \"objects\": [\n" +
                "    {" +
                "      \"oid\": \"2222222\"," +
                "      \"size\": 123," +
                "      \"error\": {\n" +
                "          \"code\": \"404\",\n" +
                "          \"message\": \"Object does not exist\"\n" +
                "        }" +
                "    }\n" +
                "  ]\n" +
                "}";
        DefaultUpstreamLfsClient.parseResponseAction(responseEntity);
    }

    @Test(expected = RuntimeException.class)
    public void testParseFailsOnInvalidInput() {
        String responseEntity = "Invalid JSON causes IOException";
        DefaultUpstreamLfsClient.parseResponseAction(responseEntity);
    }
}