/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.lookout.remote.report.poller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.lookout.common.log.LookoutLoggerFactory;
import com.alipay.lookout.common.utils.CommonUtil;
import com.google.common.collect.Sets;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;

import static com.alipay.lookout.core.config.LookoutConfig.DEFAULT_HTTP_EXPORTER_PORT;
import static com.alipay.lookout.core.config.LookoutConfig.LOOKOUT_EXPORTER_ACCESS_TOKEN;

/**
 * @author xiangfeng.xzc
 * @since 2018/7/17
 */
public class MetricsHttpExporter {
    static final Logger            logger          = LookoutLoggerFactory
                                                       .getLogger(MetricsHttpExporter.class);
    private static final Charset   UTF8            = Charset.forName("UTF-8");
    private static final int       DEFAULT_BACKLOG = 2;
    private final PollerController controller;
    private final int              port;
    private final int              backlog;
    private HttpServer             httpServer;

    public MetricsHttpExporter(PollerController controller) {
        this(controller, DEFAULT_HTTP_EXPORTER_PORT, DEFAULT_BACKLOG);
    }

    public MetricsHttpExporter(PollerController controller, int port, int backlog) {
        this.controller = controller;
        this.port = port;
        this.backlog = backlog;
    }

    /**
     * 启动exporter, 暴露底层的http端口
     *
     * @throws IOException IOException
     */
    public void start() throws IOException {
        final ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10),
            CommonUtil.getNamedThreadFactory("client-exporter-pool"),
            new ThreadPoolExecutor.CallerRunsPolicy());

        httpServer = HttpServer.create(new InetSocketAddress(port), backlog);
        httpServer.setExecutor(singleThreadPool);
        httpServer.createContext("/get", getHandler);
        // 测试用接口 清理掉数据
        httpServer.createContext("/clear", clearHandler);
        httpServer.start();
    }

    public synchronized void close() {
        if (httpServer != null) {
            httpServer.stop(5);
            httpServer = null;
        }
        getController().close();
    }

    /**
     * 用于get数据
     */
    private final HttpHandler getHandler   = new HttpHandler() {
                                               @Override
                                               public void handle(HttpExchange exchange)
                                                                                        throws IOException {
                                                   try {
                                                       if (!isAccessAllowed(exchange)) {
                                                           sendErrResponse(exchange, 403,
                                                               "Forbidden");
                                                           return;
                                                       }

                                                       // 解析参数
                                                       Set<Long> success = Collections.emptySet();
                                                       long newStep = controller.getStep();
                                                       int newSlotCount = controller.getSlotCount();

                                                       try {
                                                           for (NameValuePair nvp : parseParams(exchange)) {
                                                               String name = nvp.getName();
                                                               String value = nvp.getValue();
                                                               if ("step".equalsIgnoreCase(name)) {
                                                                   newStep = Long.parseLong(value);
                                                               } else if ("slotCount"
                                                                   .equalsIgnoreCase(name)) {
                                                                   newSlotCount = Integer
                                                                       .parseInt(value);
                                                               } else if ("success"
                                                                   .equalsIgnoreCase(name)) {
                                                                   success = parseCursors(value);
                                                               }
                                                           }
                                                       } catch (NumberFormatException nfe) {
                                                           sendErrResponse(exchange, 400,
                                                               nfe.getMessage());
                                                           return;
                                                       }

                                                       Object data = controller
                                                           .getNextData(success);

                                                       JSONObject bodyEntity = new JSONObject();
                                                       // 这里返回newStep给用户 表明我们已经接受了用户修改的step
                                                       bodyEntity.put("step", newStep);
                                                       bodyEntity.put("slotCount", newSlotCount);
                                                       bodyEntity.put("data", data);
                                                       sendResponse(exchange, bodyEntity);

                                                       controller.update(newStep, newSlotCount);

                                                       // if (oldRate != newStep || oldSlotCount != newSlotCount) {
                                                       // }
                                                   } catch (Throwable e) {
                                                       logger.warn("pull metrics failed."
                                                                   + e.getMessage());
                                                   } finally {
                                                       exchange.close();
                                                   }
                                               }
                                           };

    private final HttpHandler clearHandler = new HttpHandler() {
                                               @Override
                                               public void handle(HttpExchange exchange)
                                                                                        throws IOException {
                                                   try {
                                                       if (!isAccessAllowed(exchange)) {
                                                           sendErrResponse(exchange, 403,
                                                               "Forbidden");
                                                           return;
                                                       }
                                                       controller.clear();
                                                       exchange.sendResponseHeaders(204, -1);
                                                   } finally {
                                                       exchange.close();
                                                   }
                                               }
                                           };

    private boolean isAccessAllowed(HttpExchange exchange) {
        if (controller.getMetricConfig().containsKey(LOOKOUT_EXPORTER_ACCESS_TOKEN)) {
            //check access token
            String requestToken = exchange.getRequestHeaders().getFirst("X-Lookout-Token");
            if (!StringUtils.equals(requestToken,
                controller.getMetricConfig().getString(LOOKOUT_EXPORTER_ACCESS_TOKEN))) {

                return false;
            }
        }
        return true;
    }

    private static void sendErrResponse(HttpExchange exchange, int httpErrorStatus, String errorMsg)
                                                                                                    throws IOException {
        byte[] data = errorMsg.getBytes();
        exchange.sendResponseHeaders(httpErrorStatus, data.length);
        exchange.getResponseBody().write(data);
        exchange.getResponseBody().close();
    }

    private static void sendResponse(HttpExchange exchange, Object bodyEntity) throws IOException {
        exchange.getResponseHeaders().set("Content-Type", "application/json;charset=utf-8");
        exchange.getResponseHeaders().set("Content-Encoding", "gzip");
        exchange.sendResponseHeaders(200, 0);
        OutputStream os = new GZIPOutputStream(exchange.getResponseBody());
        try {
            JSON.writeJSONString(os, UTF8, bodyEntity);
        } finally {
            os.close();
        }
    }

    private static Set<Long> parseCursors(String str) {
        if (StringUtils.isEmpty(str)) {
            return Collections.emptySet();
        }
        String[] ss = StringUtils.split(str, ',');
        Set<Long> set = Sets.newHashSetWithExpectedSize(ss.length);
        for (String s : ss) {
            set.add(Long.parseLong(s));
        }
        return set;
    }

    /**
     * 解析参数
     *
     * @param exchange
     * @return the params
     */
    private static List<NameValuePair> parseParams(HttpExchange exchange) {
        return new URIBuilder(exchange.getRequestURI()).getQueryParams();
    }

    public PollerController getController() {
        return controller;
    }
}