package io.activej.http.stream;

import io.activej.bytebuf.ByteBuf;
import io.activej.bytebuf.ByteBufPool;
import io.activej.csp.supplier.ChannelSuppliers;
import io.activej.http.TestUtils.AssertingConsumer;
import io.activej.test.rules.ByteBufRule;
import io.activej.test.rules.EventloopRule;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.zip.GZIPOutputStream;

import static io.activej.http.GzipProcessorUtils.toGzip;
import static io.activej.promise.TestUtils.await;

public final class BufsConsumerGzipDeflaterTest {

	@ClassRule
	public static final EventloopRule eventloopRule = new EventloopRule();

	@ClassRule
	public static final ByteBufRule byteBufRule = new ByteBufRule();

	private final AssertingConsumer consumer = new AssertingConsumer();
	private final BufsConsumerGzipDeflater gzip = BufsConsumerGzipDeflater.create();
	private final ArrayList<ByteBuf> list = new ArrayList<>();

	@Before
	public void setUp() {
		consumer.reset();
		gzip.getOutput().set(consumer);
	}

	@Test
	public void testCompress() {
		String[] strings = {"Hello ", "World", "!"};
		List<byte[]> arrays = new ArrayList<>();
		for (String s : strings) {
			arrays.add(s.getBytes());
		}
		byte[] expected = compressWithGzipOutputStream(arrays.toArray(new byte[0][0]));
		consumer.setExpectedByteArray(expected);

		for (String string : strings) {
			ByteBuf buf = ByteBufPool.allocate(string.length());
			buf.put(string.getBytes());
			list.add(buf);
		}

		doTest();
	}

	// GzipParseUtils compatibility tests
	@Test
	public void testCompressSingleBuf() {
		byte[] data = new byte[17000];
		ThreadLocalRandom.current().nextBytes(data);
		ByteBuf buf = ByteBufPool.allocate(data.length);
		ByteBuf buf2 = ByteBufPool.allocate(data.length);
		buf.put(data);
		buf2.put(data);
		list.add(buf2);
		ByteBuf expected = toGzip(buf);
		consumer.setExpectedBuf(expected);
		doTest();
	}

	@Test
	public void testCompressMultipleBufs() {
		byte[] data = new byte[100_000];
		ThreadLocalRandom.current().nextBytes(data);
		ByteBuf buf1 = ByteBufPool.allocate(data.length);
		ByteBuf buf2 = ByteBufPool.allocate(data.length);
		buf1.put(data);
		buf2.put(data);
		ByteBuf gzipped = toGzip(buf1);
		consumer.setExpectedBuf(gzipped);
		int bufSize = 100;
		for (int i = 0; i < data.length; i += bufSize) {
			list.add(buf2.slice(bufSize));
			buf2.moveHead(bufSize);
		}
		buf2.recycle();

		doTest();
	}

	@Test
	public void testCompressWithEmptyBufs() {
		byte[] data1 = new byte[100];
		byte[] data2 = new byte[100];
		byte[] data3 = new byte[100];

		ThreadLocalRandom.current().nextBytes(data1);
		ThreadLocalRandom.current().nextBytes(data2);
		ThreadLocalRandom.current().nextBytes(data3);

		ByteBuf buf1 = ByteBuf.wrapForReading(data1);
		ByteBuf buf2 = ByteBuf.wrapForReading(new byte[0]);
		ByteBuf buf3 = ByteBuf.wrapForReading(data2);
		ByteBuf buf4 = ByteBuf.wrapForReading(new byte[0]);
		ByteBuf buf5 = ByteBuf.wrapForReading(data3);
		ByteBuf buf6 = ByteBuf.wrapForReading(new byte[0]);

		ByteBuf fullBuf = ByteBufPool.allocate(500);
		fullBuf = ByteBufPool.append(fullBuf, buf1.slice());
		fullBuf = ByteBufPool.append(fullBuf, buf2.slice());
		fullBuf = ByteBufPool.append(fullBuf, buf3.slice());
		fullBuf = ByteBufPool.append(fullBuf, buf4.slice());
		fullBuf = ByteBufPool.append(fullBuf, buf5.slice());
		fullBuf = ByteBufPool.append(fullBuf, buf6.slice());

		ByteBuf gzipped = toGzip(fullBuf);
		consumer.setExpectedBuf(gzipped);

		list.addAll(List.of(buf1, buf2, buf3, buf4, buf5, buf6));

		doTest();
	}

	private void doTest() {
		gzip.getInput().set(ChannelSuppliers.ofList(list));
		await(gzip.getProcessCompletion());
	}

	private byte[] compressWithGzipOutputStream(byte[]... arrays) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try (GZIPOutputStream gzip = new GZIPOutputStream(baos, true)) {
			for (int i = 0; i < arrays.length; i++) {
				gzip.write(arrays[i], 0, arrays[i].length);
				if (i == arrays.length - 1) {
					gzip.finish();
				}
			}
			return baos.toByteArray();
		} catch (IOException e) {
			throw new AssertionError(e);
		}
	}
}
