package com.atlassian.bitbucket.mesh.git.hook;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Collections;

import static org.junit.Assert.*;

@RunWith(MockitoJUnitRunner.class)
public class GitHookInputParserTest {

    private static final String POO = "\uD83D\uDCA9";

    @Test
    public void testReadContextIdAsHookRequestId() throws Exception {
        GitHookInputParser input = new GitHookInputParserBuilder()
                .hookRequestId("cafebabe")
                .build();
        HookContextId contextId = input.readContextId();
        assertEquals("cafebabe", contextId.getHookRequestId());
    }

    @Test
    public void testReadContextIdAsRequestAndRepositoryId() throws Exception {
        GitHookInputParser input = new GitHookInputParserBuilder()
                .requestId("cafebabe", "repo-id")
                .build();
        HookContextId contextId = input.readContextId();
        assertEquals("cafebabe", contextId.getExecutionContextId());
        assertEquals("repo-id", contextId.getRepositoryId());
    }

    @Test
    public void testReadContextIdOutOfOrder() {
        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
            GitHookInputParser input = new GitHookInputParserBuilder()
                    .hookType("git-update")
                    .build();
            input.readContextId();
        });
        assertEquals("Unexpected chunk type: HOOK_TYPE", thrown.getMessage());
    }

    @Test
    public void testReadRequest() throws Exception {
        HookRequest request = new GitHookInputParserBuilder()
                .hookType("hook1")
                .arguments("foo", "bar")
                .environment("foo", "bar")
                .stdin("one")
                .stdin("two")
                .build()
                .readRequest();
        assertNotNull(request);
        assertEquals("hook1", request.getHookName());
        assertEquals(ImmutableList.of("foo", "bar"), request.getArguments());
        assertEquals(ImmutableMap.of("foo", "bar"), request.getEnvironment());
        assertEquals("onetwo", request.getStdin().read());

        request = new GitHookInputParserBuilder()
                .hookType("hook2")
                .stdin("one")
                .stdin("two")
                .build()
                .readRequest();
        assertNotNull(request);
        assertEquals("hook2", request.getHookName());
        assertEquals(Collections.emptyList(), request.getArguments());
        assertEquals("onetwo", request.getStdin().read());

        request = new GitHookInputParserBuilder()
                .hookType("hook3")
                .build()
                .readRequest();
        assertNotNull(request);
        assertEquals("hook3", request.getHookName());
        assertEquals(Collections.emptyList(), request.getArguments());
        assertEquals("", request.getStdin().read());

        request = new GitHookInputParserBuilder()
                .hookType("hook4")
                .arguments("bar")
                .build()
                .readRequest();
        assertNotNull(request);
        assertEquals("hook4", request.getHookName());
        assertEquals(ImmutableList.of("bar"), request.getArguments());
        assertEquals("", request.getStdin().read());
    }

    @Test
    public void testReadRequestOutOfOrder() {
        IllegalStateException thrown = assertThrows(IllegalStateException.class,
                () -> new GitHookInputParserBuilder()
                        .hookRequestId("cafebabe")
                        .build()
                        .readRequest());
        assertEquals("Unexpected chunk type: HOOK_REQUEST_ID", thrown.getMessage());
    }

    @Test
    public void testReadUnicode() throws IOException {
        HookRequest request = new GitHookInputParserBuilder()
                .hookType("unicode")
                .environment("poo", POO)
                .stdin(POO)
                .build()
                .readRequest();
        assertEquals(POO, request.getStdin().read());
        assertEquals(POO, request.getEnvironment().get("poo"));
    }

    @Test
    public void testReadsFromSlowChannel() throws Exception {
        HookRequest request = new GitHookInputParserBuilder()
                .hookType("hook1")
                .arguments("foo", "bar")
                .environment("foo", "bar")
                .stdin("one")
                .stdin("two")
                .build(Long.MAX_VALUE, bytes -> new SlowpokeChannel(Channels.newChannel(new ByteArrayInputStream(bytes))))
                .readRequest();
        assertNotNull(request);
        assertEquals("hook1", request.getHookName());
        assertEquals(ImmutableList.of("foo", "bar"), request.getArguments());
        assertEquals(ImmutableMap.of("foo", "bar"), request.getEnvironment());
        assertEquals("onetwo", request.getStdin().read());
    }

    private static class SlowpokeChannel implements ReadableByteChannel {

        private final ReadableByteChannel delegate;
        private final ByteBuffer temp;

        SlowpokeChannel(ReadableByteChannel delegate) {
            this.delegate = delegate;
            this.temp = ByteBuffer.allocate(1);
        }

        @Override
        public void close() throws IOException {
            delegate.close();
        }

        @Override
        public boolean isOpen() {
            return delegate.isOpen();
        }

        @Override
        public int read(ByteBuffer dst) throws IOException {
            int read = delegate.read(temp);
            temp.flip();
            dst.put(temp);
            temp.compact();
            return read;
        }
    }
}
