package com.github.dockerjava.netty;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DefaultDockerClientConfig.Builder;
import com.github.dockerjava.core.DockerClientBuilder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.util.CharsetUtil;
import org.junit.Test;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static org.junit.Assert.assertEquals;

public class NettyDockerCmdExecFactoryConfigTest {

    @Test
    public void testNettyDockerCmdExecFactoryConfigWithApiVersion() throws Exception {
        int dockerPort = getFreePort();

        NettyDockerCmdExecFactory factory = new NettyDockerCmdExecFactory();
        Builder configBuilder = new DefaultDockerClientConfig.Builder()
            .withDockerTlsVerify(false)
            .withDockerHost("tcp://localhost:" + dockerPort)
            .withApiVersion("1.23");

        DockerClient client = DockerClientBuilder.getInstance(configBuilder)
                .withDockerCmdExecFactory(factory)
                .build();

        FakeDockerServer server = new FakeDockerServer(dockerPort);
        server.start();
        try {
            client.versionCmd().exec();

            List<HttpRequest> requests = server.getRequests();

            assertEquals(1, requests.size());
            assertEquals("/v1.23/version", requests.get(0).uri());
        } finally {
            server.stop();
        }
    }

    @Test
    public void testNettyDockerCmdExecFactoryConfigWithoutApiVersion() throws Exception {
        int dockerPort = getFreePort();

        NettyDockerCmdExecFactory factory = new NettyDockerCmdExecFactory();
        Builder configBuilder = new DefaultDockerClientConfig.Builder()
            .withDockerTlsVerify(false)
            .withDockerHost("tcp://localhost:" + dockerPort);

        DockerClient client = DockerClientBuilder.getInstance(configBuilder)
            .withDockerCmdExecFactory(factory)
            .build();

        FakeDockerServer server = new FakeDockerServer(dockerPort);
        server.start();
        try {
            client.versionCmd().exec();

            List<HttpRequest> requests = server.getRequests();

            assertEquals(1, requests.size());
            assertEquals("/version", requests.get(0).uri());
        } finally {
            server.stop();
        }
    }

    private int getFreePort() throws IOException {
        ServerSocket socket = new ServerSocket(0);
        int freePort = socket.getLocalPort();
        socket.close();
        return freePort;
    }

    private class FakeDockerServer {
        private final int port;
        private final NioEventLoopGroup parent;
        private final NioEventLoopGroup child;
        private final List<HttpRequest> requests = new ArrayList<>();
        private Channel channel;

        private FakeDockerServer(int port) {
            this.port = port;
            this.parent = new NioEventLoopGroup();
            this.child = new NioEventLoopGroup();
        }

        private void start() throws Exception {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parent, child)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast("codec", new HttpServerCodec());
                        pipeline.addLast("httpHandler", new SimpleChannelInboundHandler<Object>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext context, Object message) throws Exception {
                                if (message instanceof HttpRequest) {
                                    // Keep track of processed requests
                                    HttpRequest request = (HttpRequest) message;
                                    requests.add(request);
                                }

                                if (message instanceof HttpContent) {
                                    // Write an empty JSON response back to the client
                                    FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.copiedBuffer("{}", CharsetUtil.UTF_8));
                                    response.headers().set(CONTENT_TYPE, "application/json; charset=UTF-8");
                                    response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
                                    context.writeAndFlush(response);
                                }
                            }
                        });
                    }
                });

            channel = bootstrap.bind(port).syncUninterruptibly().channel();
        }

        private void stop() throws Exception {
            parent.shutdownGracefully();
            child.shutdownGracefully();
            channel.closeFuture().sync();
        }

        private List<HttpRequest> getRequests() {
            return requests;
        }
    }
}
