package org.bdware.sc.engine.hook;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bdware.doip.codec.doipMessage.*;
import org.bdware.doip.codec.operations.BasicOperations;
import org.bdware.sc.JSEngine;
import org.bdware.sc.bean.ContractRequest;
import org.bdware.sc.boundry.ScriptReturnException;
import org.bdware.sc.engine.JSONTool;
import org.bdware.sc.entity.DoipMessagePacker;
import org.bdware.sc.node.AnnotationHook;
import org.bdware.sc.node.ArgPacks;
import org.bdware.sc.node.FunctionNode;
import org.bdware.sc.util.JsonUtil;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class DOOPAfterExecHandler implements AnnotationHook {
    public static Map<String, BasicOperations> funcNameToDoipOperations;
    public static DOOPAfterExecHandler instance;
    public DOOPAfterExecHandler() {
        funcNameToDoipOperations = new HashMap<>();
    }

    public static DOOPAfterExecHandler createDOOPHandler() {
        if(instance == null) {
            instance = new DOOPAfterExecHandler();
        }

        return instance;
    }

    public void putFuncNameAndDoipOperationsMapping(FunctionNode fn) {
        String basicOperationsString = fn.getDoipOperationInfo().operationType;
        BasicOperations operation = BasicOperations.Unknown;
        for(BasicOperations basicOperation : BasicOperations.values()) {
            if(basicOperation.toString().equals(basicOperationsString)) {
                operation = basicOperation;
            }
        }
        funcNameToDoipOperations.put(fn.getFunctionName(), operation);
    }

    @Override
    public ArgPacks handle(JSEngine desktopEngine, ArgPacks argPacks) {
        BasicOperations curOp = funcNameToDoipOperations.get(argPacks.request.getAction());
        Object originDoipMsgPacker = argPacks.arg;
        DoipMessage originDoipMsg = null;
        if (originDoipMsgPacker instanceof DoipMessagePacker) {
            DoipMessagePacker doipMessagePacker = (DoipMessagePacker) originDoipMsgPacker;
            originDoipMsg = doipMessagePacker.rawDoipMsg;

            // if http, directly return
            if (doipMessagePacker.source.equals("http")) {
                return argPacks;
            } else {
                // pack
                JsonObject jsonObjectRes = JSONTool.convertMirrorToJson(argPacks.ret).getAsJsonObject();

                // validate json response
                JsonElement jsonResponseRules = getRulesForJsonResponse(curOp);
                ArgSchemaVisitor visitor = new ArgSchemaVisitor(jsonObjectRes);
                validateJsonElementRulesByArgSchemaVisitor(jsonResponseRules, visitor);

                JsonObject header = jsonObjectRes.get("header") != null ? jsonObjectRes.get("header").getAsJsonObject() : null;
                String body = jsonObjectRes.get("body") != null ? jsonObjectRes.get("body").getAsString() : null;

                // 和HTTP一样，所有需要的字段自己封装，校验规则也比较简单，这里只做简单的包装返回即可！！！
                if(header != null) {
                    originDoipMsg.header = JsonUtil.fromJson(header, MessageHeader.class);
                    // response字段根据白皮书上的规定，处于header下，人为包装到parameters的response中
                    String headerRespCode = header.get("response") != null ? header.get("response").getAsString() : null;
                    if(headerRespCode != null) {
                        if (originDoipMsg.header.parameters == null) originDoipMsg.header.parameters = new HeaderParameter(null, null);
                        originDoipMsg.header.parameters.response = DoipResponseCode.valueOf(headerRespCode);
                    }
                }

                if(body != null) {
                    originDoipMsg.body.encodedData = body.getBytes(StandardCharsets.UTF_8);
                }

                argPacks.ret = originDoipMsg;
                return argPacks;
            }
        } else {
            return argPacks;
        }
    }



    public static JsonElement getRulesForJsonResponse(BasicOperations basicOperations) {
        switch (basicOperations) {
            case Hello:
            case Retrieve:
            case Create:
            case Update:
            case Search:
            case ListOps:
                return JsonParser.parseString("{\"!header\":{\"!response\":\"string\"}, \"!body\":\"string\"}");
            case Delete:
                return JsonParser.parseString("{\"!header\":{\"!response\":\"string\"}, \"body\":\"string\"}");
            case Extension:
            case Unknown:
            default:
                return null;
        }
    }

    // old convert jsonResponse from argPack's ret to Doip response in doip chain logic
    public static DoipMessage convertJsonResponseToDoipMessage(FunctionNode fn, JsonElement jsonResponse, DoipMessage msg) {
        BasicOperations curOp = funcNameToDoipOperations.get(fn.getFunctionName());
        JsonObject jsonParams = jsonResponse.getAsJsonObject();

        // validate json response
        JsonElement jsonResponseRules = getRulesForJsonResponse(curOp);
        ArgSchemaVisitor visitor = new ArgSchemaVisitor(jsonResponse);
        validateJsonElementRulesByArgSchemaVisitor(jsonResponseRules, visitor);

        JsonObject header = jsonParams.get("header") != null ? jsonParams.get("header").getAsJsonObject() : null;
        String body = jsonParams.get("body") != null ? jsonParams.get("body").getAsString() : null;

        if(header != null) {
            String headerRespCode = header.get("response") != null ? header.get("response").getAsString() : null;
            if(headerRespCode != null) {
                for (DoipResponseCode responseCode : DoipResponseCode.values()) {
                    if(responseCode.toString().equals(headerRespCode)) {
                        msg.header.parameters.response = responseCode;
                        break;
                    }
                }
            }
        }

        if(body != null) {
            msg.body.encodedData = body.getBytes(StandardCharsets.UTF_8);
        }

        return msg;
    }

    public static void validateJsonElementRulesByArgSchemaVisitor(JsonElement jsonElement, ArgSchemaVisitor visitor) {
        visitor.visit(jsonElement);
        if (!visitor.getStatus()) {
            JsonObject jo = new JsonObject();
            jo.addProperty("msg", visitor.getException());
            jo.addProperty("code", visitor.errorCode);
            throw new ScriptReturnException(jo);
        }
    }
}
