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

import com.atlassian.fugue.Either;
import com.atlassian.bitbucket.DataStoreException;
import com.atlassian.bitbucket.ServiceException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.attach.AttachmentService;
import com.atlassian.stash.internal.attach.AttachmentSupplier;
import com.atlassian.stash.internal.project.InternalProjectService;
import com.atlassian.stash.internal.web.util.DownloadHeaderHelper;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.repository.RefService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.permission.PermissionService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.DelegatingServletOutputStream;
import org.springframework.mock.web.MockHttpServletResponse;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import static com.atlassian.bitbucket.util.HttpHeaderAsserts.assertCachedForFourWeeks;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class AttachmentControllerTest {

    private final String PROJECT_KEY = "KEY";
    private final String REPOSITORY_SLUG = "slug";

    @Mock
    private AttachmentService attachmentService;
    @InjectMocks
    private AttachmentController controller;
    @Mock
    private DownloadHeaderHelper downloadHeaderHelper;
    @Spy
    @SuppressWarnings("unused")
    private I18nService i18nService = new SimpleI18nService();
    @Mock(answer = Answers.RETURNS_MOCKS)
    private NavBuilder navBuilder;
    @Mock
    private PermissionService permissionService;
    @Mock
    private InternalProjectService projectService;
    @Mock
    private RefService refService;
    @Mock
    private Repository repository;
    @Mock
    private RepositoryService repositoryService;

    @Before
    public void setup() {
        doReturn(repository).when(repositoryService).getBySlug(eq(PROJECT_KEY), eq(REPOSITORY_SLUG));
        when(repository.getId()).thenReturn(42);
    }

    @Test
    public void testDelete() {
        controller.delete(PROJECT_KEY, REPOSITORY_SLUG, "token", "image.png");

        verify(attachmentService).delete(same(repository), eq("token/image.png"));
        verify(repositoryService).getBySlug(eq(PROJECT_KEY), eq(REPOSITORY_SLUG));
    }

    @Test
    public void testRetrieve() throws IOException {
        HttpServletResponse response = mock(HttpServletResponse.class);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        setupMockingForRetrieve();
        when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(outputStream));

        controller.retrieve(PROJECT_KEY, REPOSITORY_SLUG, "token", "file.txt", null, response);

        verify(attachmentService).read(same(repository), eq("token/file.txt"));
        verify(repositoryService).getBySlug(eq(PROJECT_KEY), eq(REPOSITORY_SLUG));
        verify(response).getOutputStream();
        verify(downloadHeaderHelper).setDownloadHeaders(eq(response), eq("file.txt"), eq("text/plain"), isNull(String.class));
        verify(response).setHeader(eq("Content-Length"), eq("4"));
        verify(response).flushBuffer();

        assertEquals("Test", outputStream.toString());
    }

    @Test
    public void testShouldCacheForFourWeeks() throws Exception {
        HttpServletResponse response = new MockHttpServletResponse();
        setupMockingForRetrieve();

        controller.retrieve(PROJECT_KEY, REPOSITORY_SLUG, "token", "file.txt", null, response);

        assertCachedForFourWeeks(response);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSave() {
        String attachmentId = "abcde12345/id";
        DataStoreException exception = new DataStoreException(new KeyedMessage("key", "localized", "root"));

        AttachmentSupplier good = mock(AttachmentSupplier.class);
        AttachmentSupplier bad = mock(AttachmentSupplier.class);

        AttachmentForm form = mock(AttachmentForm.class);
        when(form.toSuppliers()).thenReturn(ImmutableList.of(good, bad));

        NavBuilder.Builder<?> navAttachment = mock(NavBuilder.Builder.class);
        when(navAttachment.buildAbsolute()).thenReturn("absoluteUrl");

        NavBuilder.RepoAttachments navAttachments = mock(NavBuilder.RepoAttachments.class);
        doReturn(navAttachment).when(navAttachments).attachment(eq(attachmentId));

        NavBuilder.Repo navRepo = mock(NavBuilder.Repo.class);
        when(navRepo.attachments()).thenReturn(navAttachments);

        when(attachmentService.saveAll(same(repository), isA(List.class)))
                .thenReturn(ImmutableList.of(
                        Either.<ServiceException, String>right(attachmentId),
                        Either.<ServiceException, String>left(exception)));
        when(navBuilder.repo(same(repository))).thenReturn(navRepo);

        ResponseEntity<Map<String, Object>> response = controller.save(PROJECT_KEY, REPOSITORY_SLUG, form);
        assertNotNull(response);
        assertSame(HttpStatus.CREATED, response.getStatusCode());

        Map<String, Object> body = response.getBody();
        List<Map<String, Object>> attachments = (List) body.get("attachments");
        assertEquals(2, attachments.size());

        Map<String, Object> goodAttachment = attachments.get(0);
        assertEquals(attachmentId, goodAttachment.get("id"));
        assertEquals("absoluteUrl", goodAttachment.get("url"));
        Map<String, Object> links = (Map<String, Object>) goodAttachment.get("links");
        assertEquals(ImmutableMap.of("href", "absoluteUrl"), links.get("self"));
        assertEquals(ImmutableMap.of("href", "attachment:42/abcde12345%2Fid"), links.get("attachment"));

        Map<String, Object> badAttachment = attachments.get(1);
        List<Map<String, Object>> errors = (List) badAttachment.get("errors");
        assertEquals(1, errors.size());

        Map<String, Object> error = errors.get(0);
        assertEquals("localized", error.get("message"));
        assertEquals(DataStoreException.class.getCanonicalName(), error.get("exceptionName"));
    }

    private void setupMockingForRetrieve() throws IOException {
        AttachmentSupplier supplier = mock(AttachmentSupplier.class);
        ByteArrayInputStream inputStream = new ByteArrayInputStream("Test".getBytes());

        when(supplier.open()).thenReturn(inputStream);
        when(supplier.getName()).thenReturn("file.txt");
        when(supplier.getSize()).thenReturn(4L);

        when(attachmentService.read(same(repository), eq("token/file.txt"))).thenReturn(supplier);
    }

}
