package com.mowforth.netty.util.handlers;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.net.MediaType;
import com.yammer.metrics.MetricRegistry;
import com.yammer.metrics.health.HealthCheck;
import com.yammer.metrics.health.HealthCheckRegistry;
import com.yammer.metrics.json.HealthCheckModule;
import com.yammer.metrics.json.MetricsModule;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;

import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * Handler for exposing metric & healthcheck data.
 *
 * <p>This provides an embedded Jetty instance to run
 * the {@literal MetricsServlet} and {@literal HealthCheckServlet}
 * servlets provided by the Yammer metrics library.</p>
 *
 * <p>Additionally the server lets you ping this node and get
 * instantaneous thread dumps.</p>
 *
 * <p>The only configurable parameter is the port to listen on.
 * The endpoint can only be accessed locally, i.e. it's bound
 * to {@code 127.0.0.1}.</p>
 */
public class MonitoringHandler extends SimpleChannelInboundHandler<DefaultFullHttpRequest> {

    private static final String METRICS = "metrics";
    private static final String PING = "ping";
    private static final String HEALTH = "healthcheck";
    private static final String TEMPLATE =
            "<html lang=\"en\">\n" +
                    "<head>\n" +
                    "  <title>Operational Menu</title>\n" +
                    "</head>\n" +
                    "<body>\n" +
                    "  <h1>Operational Menu</h1>\n" +
                    "  <ul>\n" +
                    "    <li><a href=\"/" + METRICS + "?pretty=true\">Metrics</a></li>\n" +
                    "    <li><a href=\"/" + PING + "\">Ping</a></li>\n" +
                    "    <li><a href=\"/" + HEALTH + "\">Healthcheck</a></li>\n" +
                    "  </ul>\n" +
                    "</body>\n" +
                    "</html>";

    private final MetricRegistry metricRegistry;
    private final HealthCheckRegistry healthCheckRegistry;
    private final ObjectMapper mapper;

    @Inject
    public MonitoringHandler(MetricRegistry metricRegistry,
                             HealthCheckRegistry healthCheckRegistry) {
        this.mapper = new ObjectMapper()
                .registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false))
                .registerModule(new HealthCheckModule());
        this.metricRegistry = metricRegistry;
        this.healthCheckRegistry = healthCheckRegistry;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DefaultFullHttpRequest msg) throws Exception {
        if (msg.getMethod() == HttpMethod.GET) {
            route(ctx, msg);
        } else {
            ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_IMPLEMENTED));
        }
    }

    private void route(ChannelHandlerContext ctx, DefaultFullHttpRequest msg) throws Exception {
        DefaultFullHttpResponse response;
        String uri = msg.getUri();
        if (uri.equals("/")) {
            response = opMenu();
        } else if (uri.startsWith("/" + PING)) {
            response = handlePing();
        } else if (uri.startsWith("/" + HEALTH)) {
            response = handleHealthcheck();
        } else if (uri.startsWith("/" + METRICS)) {
            QueryStringDecoder decoder = new QueryStringDecoder(uri);
            List<String> data = decoder.parameters().get("pretty");
            boolean pretty = true;
            if (data == null || data.size() < 1 || data.get(0).isEmpty()) {
                pretty = false;
            }
            response = handleMetrics(pretty);
        } else {
            // 404
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
        }
        ctx.writeAndFlush(response);
    }

    private DefaultFullHttpResponse opMenu() {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                Unpooled.copiedBuffer(TEMPLATE.getBytes()));
        response.headers().set(HttpHeaders.Names.CONTENT_TYPE, MediaType.HTML_UTF_8);
        return response;
    }

    private DefaultFullHttpResponse handlePing() {
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                Unpooled.copiedBuffer("pong".getBytes()));
    }

    private DefaultFullHttpResponse handleHealthcheck() throws Exception {
        Map<String, HealthCheck.Result> results = healthCheckRegistry.runHealthChecks();
        DefaultFullHttpResponse response;
        if (results.isEmpty()) {
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.NOT_IMPLEMENTED);
        } else {
            String formatted = mapper.writeValueAsString(results);
            HttpResponseStatus status = isAllHealthy(results) ?
                    HttpResponseStatus.OK : HttpResponseStatus.INTERNAL_SERVER_ERROR;
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                    Unpooled.copiedBuffer(formatted.getBytes()));
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json");
        }
        return response;
    }

    private DefaultFullHttpResponse handleMetrics(boolean pretty) throws Exception {
        String formatted;
        if (pretty) {
            formatted = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(metricRegistry);
        } else {
            formatted = mapper.writeValueAsString(metricRegistry);
        }
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK, Unpooled.copiedBuffer(formatted.getBytes()));
        response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json");
        return response;
    }

    private static boolean isAllHealthy(Map<String, HealthCheck.Result> results) {
        for (HealthCheck.Result result : results.values()) {
            if (!result.isHealthy()) {
                return false;
            }
        }
        return true;
    }
}
@@ -399,16 +399,56 @@
 pooled;%0A
+import io.netty.channel.ChannelHandler;%0A
 import i
@@ -1188,16 +1188,41 @@
 /p%3E%0A */%0A
+@ChannelHandler.Sharable%0A
 public c

Document MonitoringHandler as Sharable