package com.jrestless.core.interceptor;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Base64;
import java.util.Random;

import javax.ws.rs.ext.WriterInterceptorContext;

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

public class ConditionalBase64WriteInterceptorTest {

	private final NeverBase64WriteInterceptor neverBase64WriteInterceptor = spy(new NeverBase64WriteInterceptor());
	private final AlwaysBase64WriteInterceptor alwaysBase64WriteInterceptor = spy(new AlwaysBase64WriteInterceptor());

	@Test
	public void testWrapsOutputStreamNever() throws IOException {

		WriterInterceptorContext context = mock(WriterInterceptorContext.class);
		OutputStream os = mock(OutputStream.class);
		when(context.getOutputStream()).thenReturn(os);

		neverBase64WriteInterceptor.aroundWriteTo(context);

		verify(neverBase64WriteInterceptor).isBase64(context);

		verify(context).proceed();
		verifyNoMoreInteractions(context);
	}

	@Test
	public void testWrapsOutputStreamAlways() throws IOException {

		WriterInterceptorContext context = mock(WriterInterceptorContext.class);
		OutputStream os = mock(OutputStream.class);
		when(context.getOutputStream()).thenReturn(os);

		ArgumentCaptor<OutputStream> updatedOsCapture = ArgumentCaptor.forClass(OutputStream.class);

		alwaysBase64WriteInterceptor.aroundWriteTo(context);

		verify(alwaysBase64WriteInterceptor).isBase64(context);

		verifyZeroInteractions(os);

		verify(context).setOutputStream(updatedOsCapture.capture());
		verify(context).proceed();
		verify(context).getOutputStream();
		verifyNoMoreInteractions(context);
		OutputStream updatedOs = updatedOsCapture.getValue();

		// just make sure we have some wrapper
		assertNotSame(os, updatedOs);
		updatedOs.close();
		verify(os).close();
	}

	@Test
	public void testDoesNotWrapOutputStreamWithBase64UrlEncoder() throws IOException {
		// a URL encoder would give "KUra8-qaMAL-Kpv0_5pR6zm8_d4="
		final String base64Bytes = "KUra8+qaMAL+Kpv0/5pR6zm8/d4=";
		testBase64Encoding(Base64.getDecoder().decode(base64Bytes), base64Bytes);
	}

	@Test
	public void testDoesNotWrapOutputStreamWithBase64MimeEncoder() throws IOException {
		/*
		 * a mime encoder is usually limited in size and would add newlines when the line limit is hit
		 * => let's generate a large string or rather byte array
		 */
		final byte[] bytes = new byte[200];
		new Random().nextBytes(bytes);
		final String base64Bytes = Base64.getEncoder().encodeToString(bytes);
		testBase64Encoding(Base64.getDecoder().decode(base64Bytes), base64Bytes);
	}

	@Test
	public void testBase64Encoding() throws IOException {
		final byte[] byteToEncode = "test".getBytes();
		testBase64Encoding(byteToEncode, new String(Base64.getEncoder().encode(byteToEncode)));
	}

	private void testBase64Encoding(byte[] bytes, String expectedBase64) throws IOException {

		WriterInterceptorContext context = mock(WriterInterceptorContext.class);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		when(context.getOutputStream()).thenReturn(baos);

		ArgumentCaptor<OutputStream> updatesOsCapture = ArgumentCaptor.forClass(OutputStream.class);

		alwaysBase64WriteInterceptor.aroundWriteTo(context);

		verify(context).setOutputStream(updatesOsCapture.capture());
		OutputStream updatedOs = updatesOsCapture.getValue();

		updatedOs.write(bytes);
		updatedOs.close();
		assertEquals(expectedBase64, baos.toString());
	}


	private static class NeverBase64WriteInterceptor extends ConditionalBase64WriteInterceptor {
		@Override
		protected boolean isBase64(WriterInterceptorContext context) {
			return false;
		}
	}

	private static class AlwaysBase64WriteInterceptor extends ConditionalBase64WriteInterceptor {
		@Override
		protected boolean isBase64(WriterInterceptorContext context) {
			return true;
		}
	}
}