package com.ag.log;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ag.base.BaseService;
import com.ag.base.DefaultBussniessImpl;
import com.ag.base.IRedisService;
import com.ag.entity.log.DefineLogConfig;
import com.ag.log.entity.LogParam;
import com.ag.log.entity.OperLog;
import com.ag.log.entity.OperLogMain;
import com.ag.util.StrUtil;
import com.ag.util.UUIDUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wb.util.WebUtil;
import lombok.extern.log4j.Log4j;
import org.json.JSONTokener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by yangxh on 2022/3/9.
 */
@Service
@Log4j
public class OperLogServcie extends BaseService {

    @Autowired
    private DefaultBussniessImpl bizDao;
    @Autowired
    private IRedisService redis;


    /**
     * 公共保存方法的日志记录
     * @param logParam
     * @param response
     * @throws IOException
     */
    @Transactional
    public void saveLogCommon(LogParam logParam, HttpServletResponse response) throws IOException {
        try {
            saveLogGrid(logParam, "common");
        } catch (Exception ex) {
            log.error(ex.getMessage());
            WebUtil.send(response, "日志记录异常！");
        }
    }

    /**
     * grid页面数据日志保存，同时支持新增和删除=后台数据的日志保存（wb控件参数可为空）
     * @param logParam
     * @param response
     * @throws IOException
     */
    @Transactional
    public void saveLogGrid(LogParam logParam, HttpServletResponse response) throws IOException {
        try {
            saveLogGrid(logParam, "grid");
        } catch (Exception ex) {
            log.error(ex.getMessage());
            WebUtil.send(response, "日志记录异常！");
        }
    }

    public void saveLogGrid(LogParam logParam, String sourceType) {
        org.json.JSONObject reqParam = logParam.getReqParam();
        JSONArray createArray = new JSONArray();
        JSONArray updateArray = new JSONArray();
        JSONArray deleteArray = new JSONArray();
        if (!StrUtil.isEmpty(logParam.getWbName())){
            JSONObject datatable = JSON.parseArray(reqParam.getString(logParam.getWbName())).getJSONObject(0);
            createArray = JSON.parseArray(datatable.getString("create"));
            updateArray = JSON.parseArray(datatable.getString("update"));
            deleteArray = JSON.parseArray(datatable.getString("destroy"));
        }
        if (CollUtil.isNotEmpty(logParam.getCreate())) {
            createArray = JSON.parseArray(JSON.toJSONString(logParam.getCreate()));
        }
        if (CollUtil.isNotEmpty(logParam.getDestroy())) {
            deleteArray = JSON.parseArray(JSON.toJSONString(logParam.getDestroy()));
        }

        OperLogMain logMain = new OperLogMain();
        List<OperLog> creates = new ArrayList<>();
        List<OperLog> updates = new ArrayList<>();
        List<OperLog> deletes = new ArrayList<>();
        super.setSaveUser(reqParam, logMain);
        logMain.setId(UUIDUtil.getUUID());
        logMain.setIp(super.getUserIpAddress(reqParam));
        logMain.setBizType(logParam.getBizType());
        logMain.setKeyword(logParam.getKeyword());
        logMain.setSourceType(sourceType);
        logMain.setMenuName(logParam.getMenuName());

        DefineLogConfig query = new DefineLogConfig();
        query.setBizType(logParam.getBizType());
        List<DefineLogConfig> configs = bizDao.findByEntity(query, DefineLogConfig.class, false);
        logParam.setCudType("C");
        executeSaveOrDel(createArray, creates, configs, logParam);
        executeUpdate(updateArray, updates, configs, logParam);
        logParam.setCudType("D");
        executeSaveOrDel(deleteArray, deletes, configs, logParam);
        creates.forEach(x -> x.setMainId(logMain.getId()));
        updates.forEach(x -> x.setMainId(logMain.getId()));
        deletes.forEach(x -> x.setMainId(logMain.getId()));
        bizDao.save(logMain);
        bizDao.saveAll(creates);
        bizDao.saveAll(updates);
        bizDao.saveAll(deletes);

    }

    /**
     *  panel页面数据日志保存，同时支持修改前数据=后台数据的日志保存（wb控件参数不能为空）
     * @param logParam
     * @param response
     * @throws IOException
     */
    @Transactional
    public void saveLogPanel(LogParam logParam, HttpServletResponse response) throws IOException {
        try {
            saveLogPanel(logParam);
        } catch (Exception ex) {
            log.error(ex.getMessage());
            WebUtil.send(response, "日志记录异常！");
        }
    }

    public void saveLogPanel(LogParam logParam) {
        org.json.JSONObject reqParam = logParam.getReqParam();
        Assert.isTrue(StrUtil.isNotEmpty(logParam.getWbName()), "panel日志保存，wb组件名称不可为空！");
        String reqStr = reqParam.getString(logParam.getWbName());
        JSONArray array = new JSONArray();
        if (checkJSONArray(reqStr)) {
            array = JSON.parseArray(reqStr);
        } else {
            JSONObject data = JSON.parseObject(reqStr);
            array.add(data);
            if (!StrUtil.isEmpty(logParam.getCreateDataId())) {
                data.put(logParam.getPrimaryKeyColName(), logParam.getCreateDataId());
            }
        }
        OperLogMain logMain = new OperLogMain();
        List<OperLog> operLogs = new ArrayList<>();
        super.setSaveUser(reqParam, logMain);
        logMain.setId(UUIDUtil.getUUID());
        logMain.setIp(super.getUserIpAddress(reqParam));
        logMain.setBizType(logParam.getBizType());
        logMain.setKeyword(logParam.getKeyword());
        logMain.setSourceType("panel");
        logMain.setMenuName(logParam.getMenuName());

        DefineLogConfig query = new DefineLogConfig();
        query.setBizType(logParam.getBizType());
        List<DefineLogConfig> configs = bizDao.findByEntity(query, DefineLogConfig.class, false);

        if (ObjectUtil.equal("U", logParam.getCudType())) {
            //修改panel补充原始数据
            mergeUpdateBean(logParam, array);
            executeUpdate(array, operLogs, configs, logParam);
        } else {
            executeSaveOrDel(array, operLogs, configs, logParam);
        }
        //无修改数据不记录
        if (CollUtil.isEmpty(operLogs)) {
            return;
        }
        operLogs.forEach(x -> {
            x.setMainId(logMain.getId());
            x.setBizType(logMain.getBizType());
            super.setSaveUser(reqParam, x);
        });
        bizDao.save(logMain);
        bizDao.saveAll(operLogs);
    }

    private void executeSaveOrDel(JSONArray createArray, List<OperLog> creates, List<DefineLogConfig> configs, LogParam logParam) {
        //对象循环
        for (int i = 0; i < createArray.size(); i++) {
            JSONObject create = createArray.getJSONObject(i);
            OperLog operLog = new OperLog();
            creates.add(operLog);
            operLog.setId(UUIDUtil.getUUID());
            operLog.setCudType(logParam.getCudType());
            String changeKey = "", changeVal = "";
            //属性循环
            for (Map.Entry entry : create.entrySet()) {
                String key = Convert.toStr(entry.getKey());
                String val = Convert.toStr(entry.getValue());
                if (key.startsWith("#")) {
                    continue;
                }
                if (key.startsWith("sys.")) {
                    continue;
                }
                //key不是列名大写，将驼峰改大写
                if (!StrUtil.equals(key, key.toUpperCase())) {
                    key = StrUtil.toUpperCol(key);
                }
                //保存业务id
                if (ObjectUtil.equal(key, logParam.getPrimaryKeyColName())) {
                    operLog.setBizId(val);
                }
                if (!StrUtil.isEmpty(val)) {
                    changeKey = StrUtil.join(changeKey, key, ":[", val, "],");
                    //配置表
                    for (DefineLogConfig config : configs) {
                        if (ObjectUtil.equal(config.getBizCol(), key)) {
                            changeVal = StrUtil.join(changeVal, config.getMenuName(), ":");
                            //keyname转换
                            if (!StrUtil.isEmpty(config.getKeywordCol())) {
                                if (!StrUtil.isEmpty(val)) {
                                    val = redis.getTargetName(config.getKeywordCol(), val);
                                } else {
                                    val = "''";
                                }
                            }
                            changeVal = StrUtil.join(changeVal, val, ",");
                            break;
                        }
                    }
                }
            }
            if (changeKey.length() > 2000) {
                changeKey = changeKey.substring(0, 2000);
            }
            if (changeVal.length() > 2000) {
                changeVal = changeVal.substring(0, 2000);
            }
            operLog.setChangeKey(changeKey);
            operLog.setChangeVal(changeVal);
        }
    }


    private void executeUpdate(JSONArray updateArray, List<OperLog> updates, List<DefineLogConfig> configs, LogParam logParam) {
        //对象循环
        for (int i = 0; i < updateArray.size(); i++) {
            JSONObject update = updateArray.getJSONObject(i);
            OperLog operLog = new OperLog();

            operLog.setId(UUIDUtil.getUUID());
            operLog.setCudType("U");
            String changeKey = "", changeVal = "";
            //属性循环
            for (Map.Entry entry : update.entrySet()) {
                String key = Convert.toStr(entry.getKey());
                String val = Convert.toStr(entry.getValue());
                //保存业务id
                if (ObjectUtil.equal(key.toUpperCase(), logParam.getPrimaryKeyColName())) {
                    operLog.setBizId(val);
                }
                //只保存有变更的值
                if (!key.startsWith("#")) {
                    continue;
                }
                key = key.substring(1, key.length());
                String newVal = update.getString(key);
                //只保存有变更的值
                if (ObjectUtil.equal(val, newVal)) {
                    continue;
                }
                if (StrUtil.isEmpty(val) && StrUtil.isEmpty(newVal)){
                    continue;
                }
                /*if (StrUtil.isEmpty(val)) {
                    continue;
                }*/
                if (StrUtil.isEmpty(val)){
                    changeKey = StrUtil.join(changeKey, key, ":[null->");
                }else{
                    changeKey = StrUtil.join(changeKey, key, ":[", val, "->");
                }
                if (StrUtil.isEmpty(newVal)){
                    changeKey = StrUtil.join(changeKey, "null],");
                }else {
                    changeKey = StrUtil.join(changeKey, newVal, "],");
                }
//                changeKey = StrUtil.join(changeKey, key, ":[", val, "->", newVal, "],");
                for (DefineLogConfig config : configs) {
                    if (ObjectUtil.equal(config.getBizCol(), key)) {
                        changeVal = StrUtil.join(changeVal, config.getMenuName());
                        //keyname转换
                        if (!StrUtil.isEmpty(config.getKeywordCol())) {
                            if (!StrUtil.isEmpty(val)) {
                                val = redis.getTargetName(config.getKeywordCol(), val);
                            } else {
                                val = "''";
                            }
                            if (!StrUtil.isEmpty(newVal)) {
                                newVal = redis.getTargetName(config.getKeywordCol(), newVal);
                            } else {
                                newVal = "''";
                            }
                        }
                        if (StrUtil.isEmpty(val)){
                            changeVal = StrUtil.join(changeVal, ":[null->");
                        }else{
                            changeVal = StrUtil.join(changeVal, ":[", val, "->");
                        }
                        if (StrUtil.isEmpty(newVal)){
                            changeVal = StrUtil.join(changeVal, "null],");
                        }else {
                            changeVal = StrUtil.join(changeVal, newVal, "],");
                        }
//                        changeVal = StrUtil.join(changeVal, val, "->", newVal, "],");
                        break;
                    }
                }
            }
            if (changeKey.length() > 2000) {
                changeKey = changeKey.substring(0, 2000);
            }
            if (changeVal.length() > 2000) {
                changeVal = changeVal.substring(0, 2000);
            }
            operLog.setChangeKey(changeKey);
            operLog.setChangeVal(changeVal);
            //无数据变化不记录
            if (StrUtil.isEmpty(changeKey)) {
                continue;
            }
            updates.add(operLog);
        }
    }

    private void mergeUpdateBean(LogParam logParam, JSONArray array) {
        if (CollUtil.isEmpty(logParam.getUpdate())){
            return;
        }
        for (Serializable source : logParam.getUpdate()){
            Map<String, Object> sourceMap = objectToMap(source);
            for (Map.Entry<String, Object> sourceEntry : sourceMap.entrySet()) {
                String sourceKey = Convert.toStr(sourceEntry.getKey());
                Object sourceVal = sourceEntry.getValue();
                for (int i=0; i<array.size(); i++){
                    JSONObject target = array.getJSONObject(i);
                    if (!target.containsKey(logParam.getPrimaryKeyColName())){
                        continue;
                    }
                    for (Map.Entry targetEntry : target.entrySet()) {
                        String targetKey = Convert.toStr(targetEntry.getKey());
                        //String targetVal = Convert.toStr(targetEntry.getValue());
                        if (StrUtil.startsWithAny(targetKey, "#", "%")){
                            continue;
                        }
                        if (ObjectUtil.equal(sourceMap.get(logParam.getPrimaryKeyColName()), target.get(logParam.getPrimaryKeyColName())) && ObjectUtil.equal(sourceKey, targetKey)){
                            target.put("#"+sourceKey, valToStr(sourceVal));
                            break;
                        }
                    }
                }
            }
        }



    }

    private Map<String, Object> objectToMap(Object object){
        Map<String,Object> dataMap = MapUtil.newHashMap();
        Class<?> clazz = object.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            try {
                field.setAccessible(true);
                dataMap.put(StrUtil.toUpperCol(field.getName()), field.get(object));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return dataMap;
    }


    private String valToStr(Object val){
        if (val == null){
            return null;
        }
        if (val instanceof String || val instanceof Number || val instanceof Boolean){
            return Convert.toStr(val);
        }
        if(val instanceof Date) {
            return DateUtil.formatDateTime((Date)val);
        }
        return null;
    }

    public boolean checkJSONArray(String str) {
        Object json = new JSONTokener(str).nextValue();
        if (json instanceof org.json.JSONObject) {
            return false;
        } else if (json instanceof org.json.JSONArray) {
            return true;
        }
        throw new RuntimeException("报文格式错误！");
    }

    public static void main(String[] args) {
        String str = "[{\"S_ID\":\"01059b60bf574ac295759adf08bbfd1f\",\"NAME\":\"test\",\"SEX\":\"男\",\"AGE\":18,\"C_ID\":\"\",\"T_ID\":\"\",\"SUB_ID\":\"\",\"CREATED_BY\":\"\",\"CREATED_ON\":null,\"UPDATED_BY\":\"\",\"UPDATED_ON\":null},{\"S_ID\":\"01e37822d4aa4e27a35e6293534c71ca\",\"NAME\":\"test\",\"SEX\":\"男\",\"AGE\":18,\"C_ID\":\"\",\"T_ID\":\"\",\"SUB_ID\":\"\",\"CREATED_BY\":\"\",\"CREATED_ON\":null,\"UPDATED_BY\":\"\",\"UPDATED_ON\":null},{\"S_ID\":\"02607039d05f4d859625aa75feeec5fe\",\"NAME\":\"test\",\"SEX\":\"男\",\"AGE\":18,\"C_ID\":\"\",\"T_ID\":\"\",\"SUB_ID\":\"\",\"CREATED_BY\":\"\",\"CREATED_ON\":null,\"UPDATED_BY\":\"\",\"UPDATED_ON\":null}]";
        Object json = new JSONTokener(str).nextValue();
        if (json instanceof org.json.JSONObject) {
            System.out.println("JSONObject");
        } else if (json instanceof org.json.JSONArray) {
            System.out.println("JSONArray");
        } else {
            System.out.println("非法数据");
        }
    }

}
