package easydo.technology.utils;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import easydo.technology.annotation.NotTableField;
import easydo.technology.annotation.TableKey;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ResultSetUtil {
    private static Pattern linePattern = Pattern.compile("_(\\w)");
    private static String suffixBegin = "_begin";
    private static String suffixEnd = "_end";
    private static String suffixLike = "_like";
    private static String suffixNot = "_not";
    private static String suffixIn = "_in";
    private static String suffixNotIn = "_notin";
    private static String suffixNull = "_null";
    private static String suffixNotNull = "_notnull";
    private static String suffixApplySql = "apply_sql";


    /**
     * 下划线转驼峰
     */
    public static String lineToHump(String str) {
        str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }


    private static Pattern humpPattern = Pattern.compile("[A-Z]");

    /**
     * 驼峰转下划线,效率比上面高
     */
    public static String humpToLine(String str) {
        Matcher matcher = humpPattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        if (sb.toString().startsWith("_")) {
            sb.deleteCharAt(0);
        }
        return sb.toString();
    }

    public static List<Map<String, Object>> formatResultSetToListMap(ResultSet resultSet) throws SQLException {
        List<Map<String, Object>> resultList = new ArrayList<>();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            Map<String, Object> resultMap = new HashMap<>();
            for (int i = 1; i <= columnCount; i++) {
                String key = lineToHump(metaData.getColumnName(i));
                if (key.endsWith("Time") || key.endsWith("Date")) {
                    resultMap.put(key, formatDateAndTime(key, resultSet.getObject(i)));
                } else {
                    resultMap.put(key, resultSet.getObject(i));
                }
            }
            resultList.add(resultMap);
        }
        return resultList;
    }

    public static <T> List<T> formatResultSetToList(ResultSet resultSet, Class<T> clazz) throws SQLException {
        List<T> resultList = new ArrayList<>();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            T t = null;
            JSONObject jsonObject = new JSONObject();
            for (int i = 1; i <= columnCount; i++) {
                String key = lineToHump(metaData.getColumnName(i));
                if (key.endsWith("Time") || key.endsWith("Date")) {
                    jsonObject.put(key, formatDateAndTime(key, resultSet.getObject(i)));
                } else {
                    jsonObject.put(key, resultSet.getObject(i));
                }

            }
            t = jsonObject.toJavaObject(clazz);
            resultList.add(t);
        }
        return resultList;
    }

    public static List<String> formatResultSetToArr(ResultSet resultSet) throws SQLException {
        List<String> resultList = new ArrayList<>();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            String key = lineToHump(metaData.getColumnName(1));
            if (key.endsWith("Time") || key.endsWith("Date")) {
                Object object = formatDateAndTime(key, resultSet.getObject(1));
                resultList.add(object == null ? "" : object.toString());
            } else {
                resultList.add(resultSet.getObject(1) == null ? "" : resultSet.getObject(1).toString());
            }

        }
        return resultList;
    }

    public static <T> T formatResultSetToModel(ResultSet resultSet, Class<T> clazz) throws SQLException {
        T t;
        JSONObject jsonObject = new JSONObject();
        if (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                String key = lineToHump(metaData.getColumnName(i));
                if (key.endsWith("Time") || key.endsWith("Date")) {
                    jsonObject.put(key, formatDateAndTime(key, resultSet.getObject(i)));
                } else {
                    jsonObject.put(key, resultSet.getObject(i));
                }

            }
        }
        t = jsonObject.toJavaObject(clazz);
        return t;
    }

    private static Object formatDateAndTime(String key, Object object) {
        if (key.endsWith("Time")) {
            try {
                String timeStr = object.toString();
                timeStr = timeStr.replace("T", "").substring(0, 19);
                return timeStr;
            } catch (Exception e) {
                return object;
            }
        } else if (key.endsWith("Date")) {
            try {
                String timeStr = object.toString();
                timeStr = timeStr.substring(0, 10);
                return timeStr;
            } catch (Exception e) {
                return object;
            }
        } else {
            return object;
        }
    }

    public static String formatSqlCondition(Map<String, Object> map) throws RuntimeException {
        String orderLimitSql = formatPageSqlCondition(map);

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(" where 1=1 ");
        map.entrySet().stream()
                .filter(entry -> StringUtil.isNotEmpty(entry.getKey()) && StringUtil.isNotEmpty(entry.getValue()))
                .forEach(entry -> {
                    String key = entry.getKey();
                    Object value = entry.getValue();
                    if (containsSqlInjection(value) && !"apply_sql".equals(key)) {
                        throw new RuntimeException("非法参数");
                    }
                    String column = humpToLine(key);
                    if (column.endsWith(suffixBegin)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixBegin, "");
                        stringBuilder.append("`").append(column).append("`");
                        stringBuilder.append(">=").append("'").append(value).append("'");
                    } else if (column.endsWith(suffixEnd)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixEnd, "");
                        stringBuilder.append("`").append(column).append("`");
                        stringBuilder.append("<=").append("'").append(value).append("'");
                    } else if (column.endsWith(suffixLike)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixLike, "");
                        stringBuilder.append("`").append(column).append("`");
                        stringBuilder.append(" like '%").append(value).append("%'");
                    } else if (column.endsWith(suffixNot)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixNot, "");
                        stringBuilder.append("`").append(column).append("`").append("!=");
                        if ("true".equals(value.toString()) || "false".equals(value.toString())) {
                            stringBuilder.append(value);
                        } else {
                            stringBuilder.append("'").append(value).append("'");
                        }
                    } else if (column.endsWith(suffixIn)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixIn, "");
                        List<Object> list = (List<Object>) value;
                        stringBuilder.append("`").append(column).append("`");
                        stringBuilder.append(" in (");
                        if (!list.isEmpty()) {
                            list.forEach(str -> {
                                stringBuilder.append("'").append(str).append("',");
                            });
                        } else {
                            stringBuilder.append("'null',");
                        }
                        stringBuilder.deleteCharAt(stringBuilder.toString().length() - 1);
                        stringBuilder.append(")");
                    } else if (column.endsWith(suffixNotIn)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixNotIn, "");
                        List<String> list = (List<String>) value;
                        stringBuilder.append("`").append(column).append("`");
                        stringBuilder.append(" not in (");
                        if (!list.isEmpty()) {
                            list.forEach(str -> {
                                stringBuilder.append("'").append(str).append("',");
                            });
                        }else {
                            stringBuilder.append("'null',");
                        }
                        stringBuilder.deleteCharAt(stringBuilder.toString().length() - 1);
                        stringBuilder.append(")");
                    } else if (column.endsWith(suffixNull)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixNull, "");
                        stringBuilder.append("`").append(column).append("`").append(" is null");
                    } else if (column.endsWith(suffixNotNull)) {
                        stringBuilder.append(" and ");
                        column = column.replace(suffixNotNull, "");
                        stringBuilder.append("`").append(column).append("`").append(" is not null");
                    } else if (column.equals(suffixApplySql)) {
                        stringBuilder.append(" and ");
                        stringBuilder.append("(").append(value).append(")");
                    } else if (column.startsWith("(") && column.endsWith(")")) {
                        stringBuilder.append(" and ");
                        column = key.substring(0, key.length() - 1);
                        stringBuilder.append("`").append(column).append("`").append("=").append("'").append(value).append("')");
                    } else {
                        stringBuilder.append(" and ");
                        stringBuilder.append("`").append(column).append("`").append("=");
                        if ("true".equals(value.toString()) || "false".equals(value.toString())) {
                            stringBuilder.append(value);
                        } else {
                            stringBuilder.append("'").append(value).append("'");
                        }
                    }
                });
        stringBuilder.append(orderLimitSql);
        return stringBuilder.toString();
    }

    private static String formatPageSqlCondition(Map<String, Object> map) {
        String limitSql = "";
        String orderBySql = "";
        if (map.containsKey("current") & map.containsKey("size")) {
            int current = (int) map.get("current");
            int size = (int) map.get("size");
            limitSql = " limit " + (current - 1) * size + "," + size;
        }
        if (map.containsKey("orderBy")) {
            String orderBy = map.get("orderBy").toString();
            String[] orderByArr = orderBy.split("_");
            orderBySql = " order by `" + ResultSetUtil.humpToLine(orderByArr[0]) + "` " + orderByArr[1];
        }

        map.remove("current");
        map.remove("size");
        map.remove("orderBy");
        map.remove("folders");
        map.remove("serialVersionUID");
        return orderBySql + limitSql;
    }

    public static String formatSelectByIdSql(Object obj) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);
        Map<String, Object> map = MapUtil.objectToMap(obj);

        return "select * from " + tableName + " where id = '" + map.get("id") + "'";
    }

    public static String formatSelectCountSql(Object obj) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);

        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append(" where 1=1 ");
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
                continue;
            }
            if (field.isAnnotationPresent(NotTableField.class)) {
                continue;
            }
            if (field.isAnnotationPresent(TableField.class)) {
                if (!field.getAnnotation(TableField.class).exist()) {
                    continue;
                }
            }
            field.setAccessible(true);
            if (StringUtil.isEmpty(field.get(obj))) {
                continue;
            }
            String column = humpToLine(field.getName());
            stringBuffer.append(" and `").append(column).append("`=");
            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
                stringBuffer.append(field.get(obj));
            } else {
                stringBuffer.append("'").append(field.get(obj)).append("'");
            }
        }

        return "select count(1) from " + tableName + stringBuffer.toString();
    }

    public static String formatSelectCountSql(Class clazz, Map<String, Object> map) throws Exception {
        String tableName = clazz.getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);

        return "select count(1) from " + tableName + formatSqlCondition(map);
    }

    public static String formatSelectSql(Object obj) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);

        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append(" where 1=1 ");
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
                continue;
            }
            if (field.isAnnotationPresent(NotTableField.class)) {
                continue;
            }
            if (field.isAnnotationPresent(TableField.class)) {
                if (!field.getAnnotation(TableField.class).exist()) {
                    continue;
                }
            }
            field.setAccessible(true);
            if (StringUtil.isEmpty(field.get(obj))) {
                continue;
            }
            String column = humpToLine(field.getName());
            stringBuffer.append(" and `").append(column).append("`=");
            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
                stringBuffer.append(field.get(obj));
            } else {
                stringBuffer.append("'").append(field.get(obj)).append("'");
            }
        }
        if (stringBuffer.toString().endsWith("1=1 ")) {
            throw new Exception("关键参数缺失");
        }
        return "select * from " + tableName + stringBuffer.toString() + " limit 1";
    }

    public static String formatSelectSql(Map<String, Object> map, Class clazz) throws Exception {
        String tableName = clazz.getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);
        String condition = formatSqlCondition(map);
        if (condition.endsWith("1=1 ")) {
            throw new Exception("关键参数缺失");
        }
        return "select * from " + tableName + condition + " limit 1";
    }

    public static String formatSelectSql(Map<String, Object> map, String sql) throws Exception {
        String condition = formatSqlCondition(map);
        return sql + condition;
    }

    public static String formatInsertSql(Object obj) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);

        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append("insert into ").append(tableName);
        StringBuilder stringBufferBe = new StringBuilder();
        StringBuilder stringBufferAf = new StringBuilder();

        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
                continue;
            }
            if (field.isAnnotationPresent(NotTableField.class)) {
                continue;
            }
            if (field.isAnnotationPresent(TableField.class)) {
                if (!field.getAnnotation(TableField.class).exist()) {
                    continue;
                }
            }
            field.setAccessible(true);
            if ("id".equals(field.getName())) {
                if (StringUtil.isEmpty(field.get(obj))) {
                    field.set(obj, UUID.randomUUID().toString());
                }
            } else if ("createTime".equals(field.getName()) || "updateTime".equals(field.getName())) {
                if (StringUtil.isEmpty(field.get(obj))) {
                    if (field.getType().getSimpleName().equals(LocalDateTime.class.getSimpleName())) {
                        field.set(obj, LocalDateTime.now());
                    } else {
                        field.set(obj, LocalDateUtil.localDateTimeFormatyMdHms(LocalDateTime.now()));
                    }
                }
            }
            if (StringUtil.isEmpty(field.get(obj))) {
                continue;
            }
            if (containsSqlInjection(field.get(obj))) {
                throw new Exception("非法参数");
            }

            String column = humpToLine(field.getName());
            stringBufferBe.append("`").append(column).append("`").append(",");
            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
                stringBufferAf.append(field.get(obj));
            } else {
                stringBufferAf.append("'").append(field.get(obj)).append("'");
            }
            stringBufferAf.append(",");
        }

        stringBufferBe.deleteCharAt(stringBufferBe.toString().length() - 1);
        stringBufferAf.deleteCharAt(stringBufferAf.toString().length() - 1);
        stringBuffer.append("(").append(stringBufferBe).append(") values (").append(stringBufferAf).append(")");

        return stringBuffer.toString();
    }


    public static String formatUpdateSql(Object obj, Map<String, Object> whereMap) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);

        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append("update ").append(tableName).append(" set ");
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
                continue;
            }
            if (field.isAnnotationPresent(NotTableField.class)) {
                if ("emptyField".equals(field.getName()) && StringUtil.isNotEmpty(field.get(obj))) {
                    String[] emptyFields = field.get(obj).toString().split(",");
                    for (String fieldName : emptyFields) {
                        stringBuffer.append("`").append(humpToLine(fieldName)).append("`").append("=null,");
                    }
                }
                continue;
            }
            if (field.isAnnotationPresent(TableField.class)) {
                if (!field.getAnnotation(TableField.class).exist()) {
                    continue;
                }
            }
            if (field.isAnnotationPresent(TableKey.class) || field.isAnnotationPresent(TableId.class) || "id".equals(field.getName())) {
                continue;
            }

            if ("updateTime".equals(field.getName()) && field.get(obj) == null) {
                if (field.getType().getSimpleName().equals(LocalDateTime.class.getSimpleName())) {
                    field.set(obj, LocalDateTime.now());
                } else {
                    field.set(obj, LocalDateUtil.localDateTimeFormatyMdHms(LocalDateTime.now()));
                }
            }
            if (StringUtil.isEmpty(field.get(obj))) {
                continue;
            }
            if (containsSqlInjection(field.get(obj))) {
                throw new Exception("非法参数");
            }
            String column = humpToLine(field.getName());
            stringBuffer.append("`").append(column).append("`").append("=");
            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
                stringBuffer.append(field.get(obj));
            } else {
                stringBuffer.append("'").append(field.get(obj)).append("'");
            }
            stringBuffer.append(",");
        }

        if (whereMap.size() == 0) {
            throw new Exception("关键参数缺失");
        }

        stringBuffer.deleteCharAt(stringBuffer.toString().length() - 1);

        stringBuffer.append(formatSqlCondition(whereMap));
        if (stringBuffer.toString().endsWith("1=1 ")) {
            throw new Exception("关键参数缺失");
        }
        return stringBuffer.toString();
    }

    public static String formatRemoveByIdSql(Object obj) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);
        Map<String, Object> map = new HashMap<>();

        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(TableKey.class)) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(obj));
                break;
            }
            if (field.isAnnotationPresent(TableId.class)) {
                field.setAccessible(true);
                map.put(field.getAnnotation(TableId.class).value(), field.get(obj));
                break;
            }
        }
        if (map.isEmpty()) {
            Field idField = obj.getClass().getDeclaredField("id");
            idField.setAccessible(true);
            map.put("id", idField.get(obj));
        }
        StringBuilder sql = new StringBuilder("delete from " + tableName + " where ");

        for (String key : map.keySet()) {
            sql.append(humpToLine(key)).append(" = '").append(map.get(key)).append("'");
        }

        return sql.toString();
    }

    public static String formatRemoveSql(Object obj) throws Exception {
        String tableName = obj.getClass().getSimpleName();
        tableName = ResultSetUtil.humpToLine(tableName);
        Map<String, Object> map = MapUtil.objectToMap(obj);

        String sql = "delete from " + tableName + formatSqlCondition(map);
        if (sql.endsWith("1=1 ")) {
            throw new Exception("关键参数缺失");
        }
        return sql;
    }

    /**
     * 是否含有sql注入，返回true表示含有
     *
     * @param obj
     * @return
     */
    private static boolean containsSqlInjection(Object obj) {
        String objStr = obj.toString().toLowerCase();
        // 使用正则表达式匹配独立的SQL关键字，注意正则表达式需要根据实际需求调整
        String pattern = "\\b(and|exec|insert|select|drop|grant|alter|delete|update|count|chr|mid|master|truncate|char|declare|or|;)\\b";
        return objStr.matches(pattern);
    }
}
