package org.bdware.sc.handler;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import io.netty.channel.ChannelHandlerContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.doip.codec.doipMessage.DoipMessage;
import org.bdware.doip.codec.operations.BasicOperations;
import org.bdware.doip.endpoint.server.DoipRequestHandler;
import org.bdware.doip.endpoint.server.NettyServerHandler;
import org.bdware.doip.endpoint.server.RepositoryHandler;
import org.bdware.sc.ContractProcess;
import org.bdware.sc.bean.ContractRequest;
import org.bdware.sc.engine.hook.DOOPHandler;
import org.bdware.sc.entity.DoipMessagePacker;
import org.bdware.sc.node.FunctionNode;

import java.util.HashMap;
import java.util.Map;

public class DOOPRequestHandler implements DoipRequestHandler, RepositoryHandler {
    public Map<String, FunctionNode> doipFunctionNodeMap;
    static Logger logger = LogManager.getLogger(NettyServerHandler.class);
    static Gson gson;

    public static DOOPRequestHandler instance;

    public DOOPRequestHandler() {
        doipFunctionNodeMap = new HashMap<>();
        gson = new Gson();
    }

    public static DOOPRequestHandler createHandler() {
        if(instance == null) {
            instance = new DOOPRequestHandler();
        }

        return instance;
    }

    public void addDoipOperation(FunctionNode function) {
        doipFunctionNodeMap.put(function.getDoipOperationInfo().operationName, function);
    }

    @Override
    public DoipMessage onRequest(ChannelHandlerContext ctx, DoipMessage msg) {
        String str = msg.header.parameters.operation;
        logger.debug("[Call operation] name: " + str);
        if (str != null) {
            FunctionNode fn;
            fn = doipFunctionNodeMap.get(str);
            if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
            if (fn != null) {
                return buildRequestAndInvokeEngine(fn, msg);
            }
        }
        return null;
    }

    @Override
    public DoipMessage handleHello(DoipMessage request) {
        FunctionNode fn = doipFunctionNodeMap.get(BasicOperations.Hello.getName());
        if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
        if (fn != null) {
            return buildRequestAndInvokeEngine(fn, request);
        } else {
            logger.error("DoipOperation Hello is not provided");
        }
        return null;
    }

    @Override
    public DoipMessage handleListOps(DoipMessage request) {
        FunctionNode fn = doipFunctionNodeMap.get(BasicOperations.ListOps.getName());
        if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
        if (fn != null) {
            return buildRequestAndInvokeEngine(fn, request);
        } else {
            logger.error("DoipOperation ListOps is not provided");
        }

        return null;
    }

    @Override
    public DoipMessage handleCreate(DoipMessage request) {
        FunctionNode fn = doipFunctionNodeMap.get(BasicOperations.Create.getName());
        if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
        if (fn != null) {
            return buildRequestAndInvokeEngine(fn, request);
        } else {
            logger.error("DoipOperation Create is not provided");
        }
        return null;
    }

    @Override
    public DoipMessage handleUpdate(DoipMessage request) {
        FunctionNode fn = doipFunctionNodeMap.get(BasicOperations.Update.getName());
        if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
        if (fn != null) {
            return buildRequestAndInvokeEngine(fn, request);
        } else {
            logger.error("DoipOperation Update is not provided");
        }

        return null;
    }

    @Override
    public DoipMessage handleDelete(DoipMessage request) {
        FunctionNode fn = doipFunctionNodeMap.get(BasicOperations.Delete.getName());
        if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
        if (fn != null) {
            return buildRequestAndInvokeEngine(fn, request);
        } else {
            logger.error("DoipOperation Delete is not provided");
        }
        return null;
    }

    @Override
    public DoipMessage handleRetrieve(DoipMessage request) {
        FunctionNode fn = doipFunctionNodeMap.get(BasicOperations.Retrieve.getName());
        if (fn == null) fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
        if (fn != null) {
            return buildRequestAndInvokeEngine(fn, request);
        } else {
            logger.error("DoipOperation Retrieve is not provided");
        }

        return null;
    }

    public DoipMessage buildRequestAndInvokeEngine(FunctionNode fn, DoipMessage msg) {
        ContractRequest contractRequest = constructContractRequest(fn, msg);
        DoipMessagePacker arg = new DoipMessagePacker("doip", msg);
        try {
            // 改变调用的函数 + 构造DoipMessagePacker
            Object ret = ContractProcess.instance.engine.executeWithoutLock(fn, contractRequest, arg);
            return DOOPHandler.convertJsonResponseToDoipMessage(fn, (JsonElement) ret, msg);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("buildRequestAndInvokeEngine has something wrong, executeWithoutLock err or validateJsonElementRulesByArgSchemaVisitor err");
        }

        return null;
    }

    public ContractRequest constructContractRequest(FunctionNode fn, DoipMessage request) {
        ContractRequest cr  = new ContractRequest();
        cr.setContractID("");
        if(request.credential == null) {
            cr.setRequester(null);
        } else {
            cr.setRequester(request.credential.getSigner());
        }
        cr.setAction(fn.functionName);
        return cr;
    }
}
