package org.mockserver.httpclient.netty;

import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockserver.echo.http.EchoServer;
import org.mockserver.httpclient.NettyHttpClient;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.model.HttpResponse;
import org.mockserver.model.MediaType;
import org.mockserver.scheduler.Scheduler;
import org.mockserver.socket.PortFactory;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpHeaderValues.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThrows;
import static org.mockserver.configuration.Configuration.configuration;
import static org.mockserver.model.Header.header;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import static org.mockserver.model.StringBody.exact;
import static org.mockserver.stop.Stop.stopQuietly;

public class NettyHttpClientErrorHandlingTest {

    private static EventLoopGroup clientEventLoopGroup;
    private final MockServerLogger mockServerLogger = new MockServerLogger();

    @BeforeClass
    public static void startEventLoopGroup() {
        clientEventLoopGroup = new NioEventLoopGroup(3, new Scheduler.SchedulerThreadFactory(NettyHttpClientErrorHandlingTest.class.getSimpleName() + "-eventLoop"));
    }

    @AfterClass
    public static void stopEventLoopGroup() {
        clientEventLoopGroup.shutdownGracefully(0, 0, MILLISECONDS).syncUninterruptibly();
    }

    @Test
    public void shouldThrowSocketCommunicationExceptionForConnectException() {
        // given
        int freePort = PortFactory.findFreePort();

        // when
        Exception exception = assertThrows(Exception.class, () -> new NettyHttpClient(configuration(), mockServerLogger, clientEventLoopGroup, null, false)
            .sendRequest(request().withHeader(HOST.toString(), "127.0.0.1:" + freePort))
            .get(10, TimeUnit.SECONDS));

        // then
        assertThat(exception.getMessage(), anyOf(
            containsString("Connection refused: /127.0.0.1:" + freePort),
            containsString("Connection refused: no further information: /127.0.0.1:" + freePort),
            containsString("Channel closed before valid response")
        ));
    }

    @Test
    public void shouldHandleConnectionClosure() throws Exception {
        // given
        EchoServer echoServer = new EchoServer(true, EchoServer.Error.CLOSE_CONNECTION);

        try {
            // when
            Exception exception = assertThrows(Exception.class, () -> new NettyHttpClient(configuration(), mockServerLogger, clientEventLoopGroup, null, false).sendRequest(request().withSecure(true).withHeader(HOST.toString(), "127.0.0.1:" + echoServer.getPort()))
                .get(10, TimeUnit.SECONDS));

            // then
            assertThat(exception.getMessage(), anyOf(
                containsString("Connection reset"),
                containsString("Channel set as inactive before valid response has been received"),
                containsString("Channel handler removed before valid response has been received")
            ));
        } finally {
            stopQuietly(echoServer);
        }
    }

    @Test
    public void shouldHandleSmallerContentLengthHeader() throws Exception {
        // given
        EchoServer echoServer = new EchoServer(true, EchoServer.Error.SMALLER_CONTENT_LENGTH);

        try {
            // when
            InetSocketAddress socket = new InetSocketAddress("127.0.0.1", echoServer.getPort());
            HttpResponse httpResponse = new NettyHttpClient(configuration(), mockServerLogger, clientEventLoopGroup, null, false)
                .sendRequest(
                    request()
                        .withHeader(CONTENT_TYPE.toString(), MediaType.TEXT_PLAIN.toString())
                        .withBody(exact("this is an example body"))
                        .withSecure(true),
                    socket
                )
                .get(10, TimeUnit.SECONDS);

            // then
            assertThat(httpResponse, is(
                response()
                    .withStatusCode(200)
                    .withReasonPhrase("OK")
                    .withHeader(CONTENT_TYPE.toString(), "text/plain")
                    .withHeader(header(ACCEPT_ENCODING.toString(), GZIP + "," + DEFLATE))
                    .withHeader(header(CONNECTION.toString(), KEEP_ALIVE.toString()))
                    .withHeader(header(CONTENT_LENGTH.toString(), "this is an example body".length() / 2))
                    .withBody(exact("this is an ", MediaType.TEXT_PLAIN))
            ));
        } finally {
            stopQuietly(echoServer);
        }
    }

}
