package net.northking.cctp.element.json.keywords;

import cn.hutool.core.util.ReUtil;
import com.alibaba.fastjson.JSONPath;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.annotation.*;
import net.northking.cctp.element.core.type.DataType;
import net.northking.cctp.element.core.type.ParamScope;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.UUID;

/**
 * JSON组件
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
@Keywords
public class JSON {

    /**
     * <p>将数据对象转换为JSON格式字符串。</p>
     *
     * @param object 数据对象
     * @return JSON字符串
     */
    @Keyword(alias = "转换为JSON字符串")
    @Return(name = "jsonString", comment = "JSON字符串", type = DataType.STRING)
    public String toJSONString(
            @Argument(name = "object", comment = "数据对象", type = DataType.OBJECT, notNull = false) Object object,
            @Argument(name = "pretty", comment = "是否格式化", type = DataType.BOOLEAN, required = false,
                    defaultValue = "false") boolean pretty,
            @Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String value,
            IExecuteContext executeContext) {
        save2ContentText(value, executeContext, com.alibaba.fastjson.JSON.toJSONString(object, pretty));
        return com.alibaba.fastjson.JSON.toJSONString(object, pretty);
    }

    /**
     * <p>获取jsonPath匹配的所有节点对象。</p>
     * Example:
     * <pre>
     * Object jsonObject = {"className":"class-1","students":[{"name":"张三","age":18}]}
     * Object jsonObject1 = [{"name":"张三","age":18}]
     *
     * getNode(jsonObject, "$.className")        = class-1
     * getNode(jsonObject, "$.students[0]")      = {"name":"张三","age":18}
     * getNode(jsonObject, "$.students[0].age")  = 18
     * getNode(jsonObject1, "$[0].age")  = 18
     * </pre>
     *
     * @param parent   JSON字符串或对象
     * @param jsonPath jsonPath表达式
     * @return 对象
     */
    @Keyword(alias = "获取JSON节点")
    @Return(name = "nodes", comment = "节点集", type = DataType.OBJECT)
    public Object getNode(
            @Argument(name = "parent", comment = "JSON对象", type = DataType.OBJECT) Object parent,
            @Argument(name = "jsonPath", comment = "jsonPath表达式", type = DataType.STRING) String jsonPath,
            @Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String value,
            IExecuteContext executeContext) {
        Object target = object2JSON(parent);
        Object nodes =new Object();
        try {
            nodes = ObjectUtils.cloneIfPossible(JSONPath.eval(target, jsonPath));
            save2ContentText(value, executeContext, nodes);
        }catch (Exception e){
            throw new IllegalArgumentException("jsonPath表达式不符合规则");
        }
        return nodes;
    }

    /**
     * <p>删除jsonPath匹配的所有节点。</p>
     * Example:
     * <pre>
     * Object jsonObject = {"className":"class-1","students":[{"name":"张三","age":18}]}
     *
     * removeNode(jsonObject, "$.className") => {"students":[{"name":"张三","age":18}]}
     * removeNode(jsonObject, "$.students[0].age") => {"className":"class-1","students":[{"name":"张三"}]}
     * </pre>
     *
     * @param parent   JSON对象
     * @param jsonPath jsonPath表达式
     * @return 修改后的JSON对象
     */
    @Keyword(alias = "删除JSON节点")
    @Return(name = "result", comment = "新JSON对象", type = DataType.OBJECT)
    public Object removeNode(
            @Argument(name = "parent", comment = "JSON对象", type = DataType.OBJECT) Object parent,
            @Argument(name = "jsonPath", comment = "jsonPath表达式", type = DataType.STRING) String jsonPath,
            @Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String value,
            IExecuteContext executeContext) {
        Object target = object2JSON(parent);
        boolean remove = JSONPath.remove(target, jsonPath);
        save2ContentText(value, executeContext, target);
        return target;
    }

    /**
     * <p>设置jsonPath匹配的所有节点的值。</p>
     * <i>注意：</i>如果表达式匹配的节点存在则修改，不存在则新增该节点。<br>
     * Example:
     * <pre>
     * Object jsonObject = {"className":"class-1","students":[{"name":"张三","age":18}]}
     *
     * // 修改className节点值
     * setNode(jsonObject, "$.className", "class-2")  => {"students":[{"name":"张三","age":18}],"className":"class-2"}
     *
     * // 新增节点
     * Object s = {"name":"李四","age":19}
     * jsonObject = setNode(jsonObject, "$.students[1]", s) => {"className":"class-1","students":[{"name":"张三","age":20},{"name":"李四","age":19}]}
     * </pre>
     *
     * @param parent   JSON对象
     * @param jsonPath jsonPath表达式
     * @param value    JSON对象
     * @return 修改后的JSON对象
     */
    @Keyword(alias = "设置JSON节点")
    @Return(name = "result", comment = "新JSON对象", type = DataType.OBJECT)
    public Object setNode(
            @Argument(name = "parent", comment = "JSON对象", type = DataType.OBJECT) Object parent,
            @Argument(name = "jsonPath", comment = "jsonPath表达式", type = DataType.STRING) String jsonPath,
            @Argument(name = "value", comment = "JSON对象", type = DataType.OBJECT) Object value,
            @Argument(name = "value1", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String value1,
            IExecuteContext executeContext) {
        Object target = object2JSON(parent);
        boolean set = JSONPath.set(target, jsonPath, value.toString());
        save2ContentText(value1, executeContext, target);
        return target;
    }

    /**
     * <p>获取jsonPath匹配节点个数。</p>
     * Example:
     * <pre>
     * String json = {"className":"class-1","students":[{"name":"张三","age":18},{"name":"李四","age":19}]}
     *
     * getNumber(json, "$.students")      = 1
     * getNumber(json, "$.students.name")   = 2
     * getNumber(json, "$..age")   = 2
     * </pre>
     *
     * @param parent   JSON字符串或对象
     * @param jsonPath jsonPath表达式
     * @return 匹配节点个数
     */
    @Keyword(alias = "获取匹配节点个数")
    @Return(name = "result", comment = "匹配节点个数", type = DataType.INTEGER)
    public int getNumber(
            @Argument(name = "parent", comment = "JSON对象", type = DataType.OBJECT) Object parent,
            @Argument(name = "jsonPath", comment = "jsonPath表达式", type = DataType.STRING) String jsonPath,
            @Argument(name = "result", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String result,
            IExecuteContext executeContext) {
        Object target = object2JSON(parent);
        String uuid = UUID.randomUUID().toString();
        JSONPath.set(target, jsonPath, uuid);
        int n = StringUtils.countMatches(target.toString(), uuid);
        save2ContentText(result, executeContext, n);
        return n;
    }

    // ################################################################################################
    // Internal Methods
    // ################################################################################################

    private Object object2JSON(Object parent) {
        if (!(parent instanceof com.alibaba.fastjson.JSON)) {
            try {
                return com.alibaba.fastjson.JSON.parse(parent.toString());
            } catch (Exception e) {
                throw new IllegalArgumentException("参数错误，参数必须为JSON对象或JSON格式字符串");
            }
        }
        return parent;
    }

    public static void save2ContentText(String value, IExecuteContext executeContext, Object s) {
        //将值塞入到组件返回值中
        if (value.contains("#{") && value.contains("}")) {
            String keyValue = ReUtil.get("(?<=\\#\\{)(.+?)(?=\\})", value, 1);
            // #{} 全局变量
            executeContext.setProjectGlobalVariable(keyValue, null == s ? "" : s.toString());
        }
        if (value.contains("${") && value.contains("}")) {
            String keyValue = ReUtil.get("(?<=\\$\\{)(.+?)(?=\\})", value, 1);
            // ${} 全局变量
            executeContext.setVariable(keyValue, null == s ? "" : s.toString());
        }
    }
}