/*
 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

public class HttpConnectionHandler extends AbstractConnectionHandler {

    public final static int CONNECT_TIMEOUT = 30000;

    private static int bufferSize = 2048;

    public final static int READING_TIMEOUT = 3000; // in millis

    private String externalHttpProxyHost;

    private int externalHttpProxyPort = -1;

    private int delay = 0;

    public HttpConnectionHandler(Socket socket) {
        super(socket);
    }

    public void setDelay(int delay) {
        this.delay = delay;
    }

    public void setExternalHttpProxy(String externalHttpProxyHost, int externalHttpProxyPort) {
        this.externalHttpProxyHost = externalHttpProxyHost;
        this.externalHttpProxyPort = externalHttpProxyPort;
    }

    private boolean usingProxy() {
        return externalHttpProxyHost != null && externalHttpProxyPort > 0;
    }

    @Override
    protected void handleConnection(Socket socket) throws Exception {
        Utils.sleep(delay / 2);

        logger.fine("Handle client connection");

        InputStream clientInput = new BufferedInputStream(socket.getInputStream());
        OutputStream clientOutput = new BufferedOutputStream(socket.getOutputStream());

        HttpRequest httpRequest = HttpRequest.build(clientInput);
        logger.log(Level.FINE, "Got HTTP request: {0}", httpRequest);

        URI requestUri = new URI(httpRequest.getRequestUri());
        InetAddress ip = InetAddress.getByName(requestUri.getHost());
        int port = requestUri.getPort();

        if (!checkConnection(ip, port)) {
            throw new Exception("Decline connection to " + ip.toString() + ":" + port);
        }

        if (!checkData(httpRequest.getBytes())) {
            throw new Exception("Data declined");
        }

        URL url = requestUri.toURL();
        HttpURLConnection con;
        if (usingProxy()) {
            Proxy httpProxy = new Proxy(Proxy.Type.HTTP,
                    new InetSocketAddress(externalHttpProxyHost, externalHttpProxyPort));
            con = (HttpURLConnection) url.openConnection(httpProxy);
        } else {
            con = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
        }

        con.setConnectTimeout(CONNECT_TIMEOUT);
        con.setReadTimeout(CONNECT_TIMEOUT);

        // we need both reading and writing for the connection
        con.setDoOutput(true);
        con.setDoInput(true);

        for (Map.Entry<String, String> headerEntry : httpRequest.getHeaders().entrySet()) {
            if (headerEntry.getKey() == null)
                continue;
            logger.log(Level.FINE, "Set header: {0} = {1}",
                    new Object[] { headerEntry.getKey(), headerEntry.getValue() });
            con.setRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }

        con.setRequestMethod(httpRequest.getMethod().toString());

        HttpRequest.METHOD method = httpRequest.getMethod();
        logger.log(Level.FINE, "Write message body and send the request to remote host. Method: {0}. Body[{1}]: {2}",
                new Object[] { method, httpRequest.getBody().length, new String(httpRequest.getBody()) });
        if (method.equals(HttpRequest.METHOD.POST)) {
            // write message body and send the request to remote host
            OutputStream out = new BufferedOutputStream(con.getOutputStream());
            out.write(httpRequest.getBody());
            out.flush();
        } else {
            throw new Exception("Unsupported method: " + method);
        }

        String statusLine = con.getHeaderField(0);
        Map<String, List<String>> headers = con.getHeaderFields();
        HttpResponse httpResponse = HttpResponse.build(statusLine, headers, con.getInputStream());
        logger.log(Level.FINE, "Got HTTP response: {0}", httpResponse);

        if (!checkData(httpResponse.getBytes())) {
            throw new Exception("Data declined");
        }

        httpResponse.write(clientOutput);
        clientOutput.flush();

        Utils.sleep(delay / 2);
    }
}
