package com.xjeffrose.xio.http;

import com.xjeffrose.xio.client.XioClient;
import com.xjeffrose.xio.client.XioClientBootstrap;
import com.xjeffrose.xio.client.XioRequest;
import com.xjeffrose.xio.server.Route;
import com.xjeffrose.xio.tracing.HttpTracingState;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import java.util.Optional;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SimpleProxyHandler implements RequestHandler {

  private static final AttributeKey<XioClient> key = AttributeKey.newInstance("xio_client");
  @Getter private final Route route;
  @Getter private final ProxyConfig config;
  private final XioClientBootstrap bootstrap;
  private XioClient client;

  public SimpleProxyHandler(Route route, ProxyConfig config, XioClientBootstrap bootstrap) {
    this.route = route;
    this.config = config;
    this.bootstrap = bootstrap;
  }

  private void buildAndAttach(ChannelHandlerContext ctx) {
    client =
        bootstrap
            .clone(ctx.channel().eventLoop())
            .address(config.address)
            .ssl(config.needSSL)
            .applicationProtocol(HttpClientCodec::new)
            .handler(new RawBackendHandler(ctx))
            .build();
    ctx.channel().attr(key).set(client);
  }

  @Override
  public RequestUpdateHandler handle(HttpRequest payload, ChannelHandlerContext ctx) {
    ReferenceCountUtil.retain(payload);
    buildAndAttach(ctx);
    if (HttpUtil.is100ContinueExpected(payload)) {
      ctx.writeAndFlush(
          new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
    }

    Optional<String> path =
        route
            .groups(payload.uri())
            .entrySet()
            .stream()
            .filter(e -> e.getKey().equals("path"))
            .map(e -> e.getValue())
            .findFirst();

    if (!config.pathPassthru) {
      payload.setUri(path.map(config.urlPath::concat).orElse(config.urlPath));
    }

    payload.headers().set("Host", config.hostHeader);

    XioRequest request =
        HttpTracingState.hasSpan(ctx)
            ? new XioRequest(payload, HttpTracingState.getSpan(ctx).context())
            : new XioRequest(payload, null);

    log.info("Requesting {}", payload);
    ctx.channel().attr(key).get().write(request);

    return new RequestUpdateHandler() {
      @Override
      public void update(HttpContent content) {
        ReferenceCountUtil.retain(content);
        client.write(content);
      }

      @Override
      public void update(LastHttpContent last) {
        ReferenceCountUtil.retain(last);
        client.write(last);
      }
    };
  }

  @Override
  public void close() {
    if (client != null) {
      try {
        client.close();
      } catch (java.io.IOException e) {
        throw new RuntimeException(e);
      }
    }
  }

  @Override
  public String toString() {
    return String.format(
        "%s:%s:%s",
        config.address.getHostString(), config.address.getPort(), config.address.getHostName());
  }
}