package ma.luan.yiyan.api;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.eventbus.ReplyException;
import io.vertx.core.eventbus.ReplyFailure;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import ma.luan.yiyan.constants.Key;
import ma.luan.yiyan.util.ConvertUtil;
import ma.luan.yiyan.util.JsonCollector;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Arrays;

public class ApiVerticle extends AbstractVerticle {
    private Logger log = LogManager.getLogger(this.getClass());

    @Override
    public void start(Future<Void> startFuture) {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.get("/*").handler(this::log); // 全局日志处理,会执行 next() 到下一个
        router.get("/").handler(this::handleRoot); // 首页
        router.get("/favicon.ico").handler(c -> c.fail(404)); // 针对浏览器返回404
        router.get("/log").handler(this::showLog); // 显示日志
        router.routeWithRegex("/([a-z0-9/]*)\\.?(txt|json|png|svg|)")
            .handler(this::handleGushici); // 核心API调用
        router.route().last().handler(c -> c.fail(404)) // 其他返回404
            .failureHandler(this::returnError); // 对上面所有的错误进行处理
        vertx
            .createHttpServer()
            .requestHandler(router)
            .listen(
                config().getInteger("http.port", 8080),
                result -> {
                    if (result.succeeded()) {
                        startFuture.complete();
                    } else {
                        startFuture.fail(result.cause());
                    }
                }
            );
    }


    private void handleRoot(RoutingContext routingContext) {
        JsonObject result = new JsonObject();
        result.put("welcome", "欢迎使用古诗词·一言");
        result.put("api-document", "下面为本API可用的所有类型,使用时,在链接最后面加上 .svg / .txt / .json / .png 可以获得不同格式的输出");
        result.put("help", "具体安装方法请访问项目首页 " + config().getString("index.url", "http://localhost/"));
        vertx.eventBus().<JsonArray>send(Key.GET_HELP_FROM_REDIS, null, res -> {
            if (res.succeeded()) {
                result.put("list", res.result().body());
                returnJsonWithCache(routingContext, result);
            } else {
                routingContext.fail(res.cause());
            }
        });
    }

    private void handleGushici(RoutingContext routingContext) {
        // 这里有两层回调,因为第二层回调需要用到第一层回调的数据。
        parseURI(routingContext) // 获取到 URI 上面的参数
            .setHandler(params -> {
                if (params.succeeded()) {
                    vertx.eventBus().<String>send(Key.GET_GUSHICI_FROM_REDIS, params.result(), res -> { // 从 Redis 拿数据
                        if (res.succeeded()) {
                            returnGushici(routingContext, res.result().body(), params.result());
                        } else {
                            routingContext.fail(res.cause());
                        }
                    });
                } else {
                    routingContext.fail(params.cause());
                }
            });
    }

    private void showLog(RoutingContext routingContext) {
        vertx.eventBus().<JsonObject>send(Key.GET_HISTORY_FROM_REDIS, null, res -> {
            if (res.succeeded()) {
                returnJson(routingContext, res.result().body());
            } else {
                routingContext.fail(res.cause());
            }
        });
    }


    private void returnJson(RoutingContext routingContext, JsonObject jsonObject) {
        setCommonHeader(routingContext.response()
            .putHeader("content-type", "application/json; charset=utf-8"))
            .end(jsonObject.encodePrettily());
    }

    private void returnJsonWithCache(RoutingContext routingContext, JsonObject jsonObject) {
        routingContext.response()
            .putHeader("content-type", "application/json; charset=utf-8")
            .end(jsonObject.encodePrettily());
    }

    private void returnError(RoutingContext routingContext) {
        JsonObject result = new JsonObject();
        int errorCode = routingContext.statusCode() > 0 ? routingContext.statusCode() : 500;
        // 不懂 Vert.x 为什么 EventBus 和 Web 是两套异常系统
        if (routingContext.failure() instanceof ReplyException) {
            errorCode = ((ReplyException) routingContext.failure()).failureCode();
        }
        result.put("error-code", errorCode);
        if (routingContext.failure() != null) {
            result.put("reason", routingContext.failure().getMessage());
        }
        setCommonHeader(routingContext.response()
            .setStatusCode(errorCode)
            .putHeader("content-type", "application/json; charset=utf-8"))
            .end(result.encodePrettily());
    }

    /*
     * 根据不同的 format 选择不同的返回策略
     */
    private void returnGushici(RoutingContext routingContext, String obj, JsonObject params) {
        switch (params.getString("format")) {
            case "json": {
                returnJson(routingContext, new JsonObject(obj));
                break;
            }
            case "svg": {
                setCommonHeader(routingContext.response()
                    .putHeader("Content-Type", "image/svg+xml; charset=utf-8"))
                    .end(ConvertUtil.getSvg(new JsonObject(obj).getString("content"),
                                params.getDouble("font-size"),
                                params.getDouble("spacing")));
                break;
            }
            case "txt": {
                setCommonHeader(routingContext.response()
                    .putHeader("Content-Type", "text/plain; charset=utf-8"))
                    .end(new JsonObject(obj).getString("content"));
                break;
            }
            case "png": {
                ConvertUtil.getImageFromBase64(obj).setHandler(res -> {
                    if (res.succeeded()) {
                        setCommonHeader(routingContext.response()
                            .putHeader("Content-Type", "image/png"))
                            .putHeader("Content-Length", res.result().length() + "")
                            .write(res.result()).end();
                    } else {
                        routingContext.fail(res.cause());
                    }
                });
                break;
            }
            default:
                routingContext.fail(new ReplyException(ReplyFailure.RECIPIENT_FAILURE, 400, "参数错误"));
        }
    }

    private HttpServerResponse setCommonHeader(HttpServerResponse response) {
        return response
            .putHeader("Access-Control-Allow-Origin", "*")
            .putHeader("Cache-Control", "no-cache");
    }

    /*
     * 记录点击量,只需要 publish 上 bus 即可
     */
    private void log(RoutingContext routingContext) {
        vertx.eventBus().publish(Key.SET_HISTORY_TO_REDIS, null);
        routingContext.next();
    }

    /**
     * 根据 uri 获取参数
     *
     * @param routingContext example: uri: /shenghuo/buyi.png , /all
     * @return {format: "png", categories: [shenghuo, buyi]}, {format:"json", categories:[""]}
     */
    private Future<JsonObject> parseURI(RoutingContext routingContext) {
        Future<JsonObject> result = Future.future();

        String rawCategory = routingContext.request().getParam("param0");
        String rawFormat = routingContext.request().getParam("param1");
        // 如果是 "all" 则当没有分类处理
        JsonArray categories = Arrays.stream(rawCategory.split("/"))
            .filter(s -> !s.isEmpty())
            .filter(s -> !"all".equals(s))
            .collect(JsonCollector.toJsonArray());
        // 默认 json
        String format = "".equals(rawFormat) ? "json" : rawFormat;

        JsonObject pathParams = new JsonObject();

        // svg 额外配置
        if ("svg".equals(format)) {
            HttpServerRequest request = routingContext.request();
            parseAndSet(pathParams,"font-size", request.getParam("font-size")
                    , 20, 8, 50);
            parseAndSet(pathParams,"spacing", request.getParam("spacing")
                    , 1.5, 0, 30);
        }

        pathParams.put("categories", categories);
        pathParams.put("format", format);
        result.complete(pathParams);
        return result;
    }

    private void parseAndSet(JsonObject jsonObject, String paramName,
                             String value, double defaultValue, double minValue, double maxValue) {
        if (value == null) {
            jsonObject.put(paramName, defaultValue);
        } else {
            try {
                double i = Double.parseDouble(value);
                if (Double.compare(i, minValue) >= 0 && Double.compare(i, maxValue) <= 0) {
                    jsonObject.put(paramName, i);
                } else {
                    jsonObject.put(paramName, defaultValue);
                }
            } catch (NumberFormatException ex) {
                jsonObject.put(paramName, defaultValue);
            }
        }
    }
}