package com.eactive.testmaster.client.service;

import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.dto.ApiRequestKeyValueDTO;
import com.eactive.testmaster.client.dto.ApiResponse;
import com.eactive.testmaster.common.exception.ServerNotFoundException;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.server.entity.ServerOption;
import com.eactive.testmaster.server.repository.ServerRepository;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class NettyTcpApiClient implements ApiClient {

    private static final Logger logger = LoggerFactory.getLogger(NettyTcpApiClient.class);

    private final ServerRepository serverRepository;

    public NettyTcpApiClient(ServerRepository serverRepository) {
        this.serverRepository = serverRepository;
    }

    public String fillPadding(int dataLength, String value) {
        StringBuilder sb = new StringBuilder(value);
        while (sb.length() < dataLength) {
            sb.insert(0, ' '); // Adds space at the beginning of the string
        }
        return sb.toString();
    }

    public CompletableFuture<ApiResponse> handleRequest(ApiRequestDTO apiRequest) {
        Server server = serverRepository.findById(apiRequest.getServer()).orElseThrow(() -> new ServerNotFoundException("Server not found"));
        String host = server.getHostname();
        int port = server.getPort();

        CompletableFuture<ApiResponse> responseFuture = new CompletableFuture<>();
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            StringBuilder message = new StringBuilder();

            for (int i = 0; i < server.getServerOptions().size(); i++) {
                ServerOption serverOption = server.getServerOptions().get(i);
                if (serverOption.isEnabled()) {
                    ApiRequestKeyValueDTO header = apiRequest.getHeaders().stream().filter(h -> h.getKey().equals(serverOption.getOptionKey())).findFirst().orElse(null);
                    if (header != null) {
                        String value = header.getValue();
                        message.append(fillPadding(serverOption.getLength(), value));
                    }
                }
            }
            message.append(apiRequest.getLayout().buildMessage());
            String response = sendMessage(host, port, group, message.toString());

            ApiResponse apiResponse = new ApiResponse();
            apiResponse.setBody(response);
            responseFuture.complete(apiResponse);
        } catch (Exception e) {
            Thread.currentThread().interrupt();
            ApiResponse response = new ApiResponse();
            response.setBody("An error occurred: " + e.getMessage());
            return CompletableFuture.completedFuture(response);
        } finally {
            group.shutdownGracefully();
        }
        return responseFuture;
    }

    public String sendMessage(String host, int port, EventLoopGroup group, final String message) throws InterruptedException {
        logger.debug(message);
        final BlockingQueue<String> answer = new ArrayBlockingQueue<>(1);
        Bootstrap b = new Bootstrap();
        b.group(group)
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new SimpleChannelInboundHandler<String>() {
                        @Override
                        protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                            answer.offer(msg);
                            ctx.close();
                        }

                        @Override
                        public void channelActive(ChannelHandlerContext ctx) {
                            ctx.writeAndFlush(message);
                            // Send message on activation, consider thread safety
                        }
                    });
                }
            });

        // Connect to the server
        ChannelFuture f = b.connect(host, port).sync();
        f.channel().closeFuture().sync();

        return answer.take(); // This will block until a message is available
    }
}
