package org.mockserver.mock.action.http;

import org.mockserver.configuration.Configuration;
import org.mockserver.httpclient.NettyHttpClient;
import org.mockserver.filters.HopByHopHeaderFilter;
import org.mockserver.log.model.LogEntry;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.model.Header;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
import org.mockserver.model.SocketAddress;
import org.slf4j.event.Level;

import javax.annotation.Nullable;
import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.mockserver.model.HttpResponse.badGatewayResponse;

/**
 * @author jamesdbloom
 */
@SuppressWarnings("FieldMayBeFinal")
public abstract class HttpForwardAction {

    protected final MockServerLogger mockServerLogger;
    protected final Configuration configuration;
    private final NettyHttpClient httpClient;
    private HopByHopHeaderFilter hopByHopHeaderFilter = new HopByHopHeaderFilter();

    HttpForwardAction(MockServerLogger mockServerLogger, Configuration configuration, NettyHttpClient httpClient) {
        this.mockServerLogger = mockServerLogger;
        this.configuration = configuration;
        this.httpClient = httpClient;
    }

    protected HttpForwardActionResult sendRequest(HttpRequest request, @Nullable InetSocketAddress remoteAddress, Function<HttpResponse, HttpResponse> overrideHttpResponse) {
        return sendRequest(request, remoteAddress, overrideHttpResponse, false);
    }

    protected HttpForwardActionResult sendRequest(HttpRequest request, @Nullable InetSocketAddress remoteAddress, Function<HttpResponse, HttpResponse> overrideHttpResponse, boolean disableStreaming) {
        try {
            // TODO(jamesdbloom) support proxying via HTTP2, for now always force into HTTP1
            HttpRequest toSend = hopByHopHeaderFilter.onRequest(request).withProtocol(null);
            CompletableFuture<HttpResponse> responseFuture = disableStreaming
                ? httpClient.sendRequest(toSend, remoteAddress, configuration.socketConnectionTimeoutInMillis(), true)
                : httpClient.sendRequest(toSend, remoteAddress);
            return new HttpForwardActionResult(request, responseFuture, overrideHttpResponse, remoteAddress);
        } catch (Exception e) {
            mockServerLogger.logEvent(
                new LogEntry()
                    .setLogLevel(Level.ERROR)
                    .setHttpRequest(request)
                    .setMessageFormat("exception forwarding request " + request)
                    .setThrowable(e)
            );
        }
        return badGatewayFuture(request);
    }

    protected void adjustHostHeader(HttpRequest request) {
        if (configuration != null) {
            String defaultHostHeader = configuration.forwardDefaultHostHeader();
            if (isNotBlank(defaultHostHeader)) {
                request.replaceHeader(new Header("Host", defaultHostHeader));
            } else if (configuration.forwardAdjustHostHeader()) {
                SocketAddress sa = request.getSocketAddress();
                if (sa != null && isNotBlank(sa.getHost())) {
                    boolean defaultPort = (SocketAddress.Scheme.HTTPS.equals(sa.getScheme()) && sa.getPort() != null && sa.getPort() == 443)
                        || (SocketAddress.Scheme.HTTP.equals(sa.getScheme()) && sa.getPort() != null && sa.getPort() == 80)
                        || (sa.getPort() == null);
                    String hostHeader = defaultPort ? sa.getHost() : sa.getHost() + ":" + sa.getPort();
                    request.replaceHeader(new Header("Host", hostHeader));
                }
            }
        }
    }

    HttpForwardActionResult badGatewayFuture(HttpRequest httpRequest) {
        CompletableFuture<HttpResponse> future = new CompletableFuture<>();
        future.complete(badGatewayResponse());
        return new HttpForwardActionResult(httpRequest, future, null);
    }
}
