package org.AutoHealerAndClusterSearch.SearchWebApp.WebServer;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.Executors;

public class WebServer implements Watcher
{
    static final Logger logger = LoggerFactory.getLogger(WebServer.class);
    private static final String TASK_ENDPOINT = "/search-query";

    private static final String COORDINATOR_ZNODE_PATH = "/coordinator_node";
    private final String zooKeeperServerAddress = "192.168.184.10";
    private static final int SESSION_TIMEOUT = 3000;
    private final int port;
    private HttpServer server;

    private ZooKeeper zooKeeper;

    private String CoordinatorAddress;

    private String Coordinator_Full_Name;

    public WebServer(int port) throws IOException, InterruptedException, KeeperException
    {
        this.port = port;
    }

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        int serverPort = 8080;
        if (args.length == 1)
        {
            serverPort = Integer.parseInt(args[0]);
        }

        WebServer webServer = new WebServer(serverPort);
        webServer.connectToZookeeper();
        webServer.startServer();

        System.out.println("Server is listening on port " + serverPort);
        logger.info("Server is listening on port " + serverPort);
        webServer.run();
        webServer.close();
    }

    public void connectToZookeeper() throws IOException, InterruptedException, KeeperException
    {
        this.zooKeeper = new ZooKeeper(zooKeeperServerAddress, SESSION_TIMEOUT, this);
        setCoordinatorAddress();
    }
    public void run() throws InterruptedException {
        synchronized (zooKeeper) {
            zooKeeper.wait();
        }
    }
    private void close() throws InterruptedException {
        this.zooKeeper.close();
    }
    private synchronized void setCoordinatorAddress() throws InterruptedException, KeeperException {
        String cord = zooKeeper.getChildren(COORDINATOR_ZNODE_PATH , this).get(0);
        Coordinator_Full_Name = COORDINATOR_ZNODE_PATH + "/" + cord;
        Stat stat = zooKeeper.exists(Coordinator_Full_Name , false);
        if (stat == null)
        {
            logger.error("Coordinator Not Found");
            throw new RuntimeException();
        }
        this.CoordinatorAddress = new String(zooKeeper.getData(Coordinator_Full_Name , false , stat)).split("@")[1];

    }
    public void startServer() {
        try
        {
            this.server = HttpServer.create(new InetSocketAddress(port), 0);
            logger.info("Server Started at : " + System.nanoTime());
        }
        catch (IOException e) {
            logger.error("Server failed to Start at : " + System.nanoTime());
            throw new RuntimeException(e);
        }

        HttpContext taskContext = server.createContext(TASK_ENDPOINT);

        taskContext.setHandler(this::handleTaskRequest);

        server.setExecutor(Executors.newFixedThreadPool(8));
        server.start();

    }

    private void handleTaskRequest(HttpExchange exchange) throws IOException
    {
        logger.info("Query Request at " + System.nanoTime());
        if (!exchange.getRequestMethod().equalsIgnoreCase("post"))
        {
            exchange.close();
            return;
        }

        Headers headers = exchange.getRequestHeaders();

        long startTime = System.nanoTime();

        byte[] requestBytes = exchange.getRequestBody().readAllBytes();
        byte[] responseBytes = prepareRsponse(requestBytes);

        long finishTime = System.nanoTime();

        String debugMessage = String.format("Operation took %d ns", finishTime - startTime);
        exchange.getResponseHeaders().put("X-Debug-Info", Arrays.asList(debugMessage));

        sendResponse(responseBytes, exchange);

    }

    private byte[] prepareRsponse(byte[] requestBytes)
    {
        String bodyString = new String(requestBytes);
        String[] stringTerms = bodyString.split(" ");

        /**
         *
         * must add code to send the stringTerms to the coordinator
         * using grpc and wait for response !!
         * */

        return String.format("Result of the multiplication is \n").getBytes();
    }

    private void sendResponse(byte[] responseBytes, HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(200, responseBytes.length);
        OutputStream outputStream = exchange.getResponseBody();
        outputStream.write(responseBytes);
        outputStream.flush();
        outputStream.close();
        exchange.close();
    }

    @Override
    public void process(WatchedEvent watchedEvent)
    {
        switch (watchedEvent.getType())
        {
            case NodeChildrenChanged:
            {
                try
                {
                    logger.warn("Coordinator Changed !!");
                    setCoordinatorAddress();
                }
                catch (InterruptedException | KeeperException e)
                {
                    logger.error("Could NOT Handle Node Children Changed Event!");
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
