package com.ruoyi.oa.service.impl;

import com.ruoyi.common.helper.DataBaseHelper;
import com.ruoyi.oa.domain.vo.DynamicDataVo;
import com.ruoyi.oa.domain.vo.TableColumnVo;
import com.ruoyi.oa.service.IDatabaseQueryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.Pattern;

/**
 * 数据库查询服务实现类
 *
 * @author ruoyi
 * @date 2024-12-19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DatabaseQueryServiceImpl implements IDatabaseQueryService {

    private final JdbcTemplate jdbcTemplate;

    // 优化后的SQL查询语句验证正则表达式
    // 确保能匹配SELECT/WITH开头，无论后面是空格、换行还是其他空白字符
    private static final Pattern QUERY_PATTERN = Pattern.compile(
            "^\\s*(SELECT|WITH)\\s+",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL
    );

    // 同时检查禁止关键字列表，确保没有包含正常查询所需的关键字
    private static final List<String> FORBIDDEN_KEYWORDS = Arrays.asList(
            "INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "TRUNCATE",
            "CREATE", "RENAME", "GRANT", "REVOKE", "EXEC", "CALL", "MERGE"
    );

    @Override
    public List<TableColumnVo> getTableColumns(String tableName) {
        if (!DataBaseHelper.isMySql()) {
            throw new UnsupportedOperationException("目前只支持MySQL数据库");
        }

        String sql = "select column_name,\n" +
                "       (case when (is_nullable = 'no' && column_key != 'PRI') then '1' else null end) as is_required,\n" +
                "       (case when column_key = 'PRI' then '1' else '0' end) as is_pk,\n" +
                "       ordinal_position as sort,\n" +
                "       column_comment,\n" +
                "       (case when extra = 'auto_increment' then '1' else '0' end) as is_increment,\n" +
                "       column_type,\n" +
                "       data_type,\n" +
                "       character_maximum_length,\n" +
                "       numeric_precision,\n" +
                "       numeric_scale,\n" +
                "       column_default,\n" +
                "       is_nullable\n" +
                "from information_schema.columns \n" +
                "where table_schema = (select database()) \n" +
                "and table_name = ?\n" +
                "order by ordinal_position";

        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            TableColumnVo column = new TableColumnVo();
            column.setColumnName(rs.getString("column_name"));
            column.setIsRequired(rs.getString("is_required"));
            column.setIsPk(rs.getString("is_pk"));
            column.setSort(rs.getInt("sort"));
            column.setColumnComment(rs.getString("column_comment"));
            column.setIsIncrement(rs.getString("is_increment"));
            column.setColumnType(rs.getString("column_type"));
            column.setDataType(rs.getString("data_type"));
            column.setCharacterMaximumLength(rs.getLong("character_maximum_length"));
            column.setNumericPrecision(rs.getInt("numeric_precision"));
            column.setNumericScale(rs.getInt("numeric_scale"));
            column.setColumnDefault(rs.getString("column_default"));
            column.setIsNullable(rs.getString("is_nullable"));
            return column;
        }, tableName);
    }

    @Override
    public Map<String, List<TableColumnVo>> getTableColumns(List<String> tableNames) {
        Map<String, List<TableColumnVo>> result = new HashMap<>();
        for (String tableName : tableNames) {
            result.put(tableName, getTableColumns(tableName));
        }
        return result;
    }

    @Override
    public List<Map<String, Object>> executeQuery(String sql) {
        // 验证SQL安全性
        if (!isQuerySql(sql)) {
            throw new IllegalArgumentException("只允许执行查询语句");
        }

        log.info("执行SQL查询: {}", sql);
        return jdbcTemplate.queryForList(sql);
    }

    @Override
    public DynamicDataVo executeQueryWithMeta(String sql, String tableName, boolean includeMeta) {
        // 执行查询
        List<Map<String, Object>> data = executeQuery(sql);

        DynamicDataVo result = new DynamicDataVo();
        result.setRenderType("table");

        DynamicDataVo.TableData tableData = new DynamicDataVo.TableData();
        tableData.setDatasource(data);

        // 动态生成 columns/fields，优先以 datasource 的 key 为主
        List<DynamicDataVo.Field> fields = new ArrayList<>();
        List<Map<String, Object>> columnsList = new ArrayList<>();
        Map<String, TableColumnVo> columnMetaMap = new HashMap<>();
        if (includeMeta && tableName != null) {
            List<TableColumnVo> columns = getTableColumns(tableName);
            for (TableColumnVo col : columns) {
                columnMetaMap.put(col.getColumnName(), col);
            }
        }

        if (data != null && !data.isEmpty()) {
            Map<String, Object> firstRow = data.get(0);
            for (String key : firstRow.keySet()) {
                DynamicDataVo.Field field = new DynamicDataVo.Field();
                field.setFieldName(key);
                TableColumnVo meta = columnMetaMap.get(key);
                if (meta != null) {
                    field.setLabel(meta.getColumnComment() != null ? meta.getColumnComment() : key);
                    field.setType(getFieldType(meta));
                    field.setFormat(getFieldFormat(meta));
                } else {
                    field.setLabel(key);
                    field.setType("string");
                    field.setFormat(null);
                }
                fields.add(field);

                Map<String, Object> columnInfo = new HashMap<>();
                columnInfo.put("field", key);
                columnInfo.put("title", field.getLabel());
                columnInfo.put("type", field.getType());
                columnInfo.put("format", field.getFormat());
                columnsList.add(columnInfo);
            }
        }

        // meta 只做补充
        if (includeMeta) {
            DynamicDataVo.Meta meta = new DynamicDataVo.Meta();
            meta.setFields(fields);
            result.setMeta(meta);
        }
        tableData.setColumns(columnsList);
        result.setResponse(tableData);
        return result;
    }

    @Override
    public boolean isQuerySql(String sql) {
        if (sql == null || sql.trim().isEmpty()) {
            log.warn("SQL为空");
            return false;
        }

        // 首先尝试正则表达式匹配
        if (QUERY_PATTERN.matcher(sql).matches()) {
            log.debug("正则表达式匹配成功");
        } else {
            // 如果正则表达式匹配失败，使用简单的字符串检查作为备选
            String trimmedSql = sql.trim().toUpperCase();
            if (!trimmedSql.startsWith("SELECT") && !trimmedSql.startsWith("WITH")) {
                log.warn("SQL不以SELECT或WITH开头: {}", sql);
                return false;
            }
            log.debug("使用字符串检查匹配成功");
        }

        String upperSql = sql.toUpperCase();
        // 检查是否包含禁止的关键字（使用单词边界避免部分匹配）
        for (String keyword : FORBIDDEN_KEYWORDS) {
            if (Pattern.compile("\\b" + keyword + "\\b").matcher(upperSql).find()) {
                log.warn("SQL包含禁止关键字 {}: {}", keyword, sql);
                return false;
            }
        }

        log.debug("SQL验证通过: {}", sql);
        return true;
    }


    /**
     * 根据字段信息推断字段类型
     */
    private String getFieldType(TableColumnVo column) {
        String dataType = column.getDataType().toLowerCase();
        
        if (dataType.contains("int") || dataType.contains("decimal") || 
            dataType.contains("float") || dataType.contains("double")) {
            return "number";
        } else if (dataType.contains("date") || dataType.contains("time")) {
            return "date";
        } else if (dataType.contains("bool")) {
            return "bool";
        } else {
            return "string";
        }
    }

    /**
     * 根据字段信息生成格式化规则
     */
    private String getFieldFormat(TableColumnVo column) {
        String dataType = column.getDataType().toLowerCase();
        
        if (dataType.contains("int")) {
            return "0,0";
        } else if (dataType.contains("decimal") || dataType.contains("float") || dataType.contains("double")) {
            return "0.00";
        } else if (dataType.contains("date")) {
            return "YYYY-MM-DD";
        } else if (dataType.contains("datetime") || dataType.contains("timestamp")) {
            return "YYYY-MM-DD HH:mm:ss";
        }
        
        return null;
    }
} 