package com.tofly.feesapi.common.api.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tofly.feesapi.common.api.ISqlStatementService;
import com.tofly.feesapi.common.exception.FeesErrorCode;
import com.tofly.feesapi.common.exception.FeesException;
import com.tofly.feesapi.common.mapper.CommonMapper;
import com.tofly.feesapi.common.model.CollectionData;
import com.tofly.feesapi.common.model.PageLink;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.SqlSessionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Transactional
public class SqlStatementServiceImpl implements ISqlStatementService {

    @Autowired
    private SqlSessionTemplate sessionTemplate;
    @Autowired
    private CommonMapper commonMapper;
    @Autowired
    private SqlSourceBuilder sqlSourceBuilder;

    private Logger log = LoggerFactory.getLogger("sql_api");

    @Value("${sql.basePath:${user.home}/sqls}")
    private File basePath;

    public static final String UTF8_BOM = "\uFEFF";

    @Override
    public Object execute(String sqlFile, String operation, Map<String, Object> executeParams) {
        String originalSql = null;
        MDC.put("sqlFile", sqlFile);
        try {
            if (!basePath.exists()) {
                basePath.mkdirs();
            }
            Stream<String> lines = Files.lines(Paths.get(basePath.getAbsolutePath(), sqlFile));
            try {
                originalSql = lines.collect(Collectors.joining(""));
            } finally {
                lines.close();
            }

            SqlSource sqlSource = sqlSourceBuilder.parse(originalSql, Map.class, new LinkedHashMap<>());

            BoundSql boundSql = sqlSource.getBoundSql(executeParams);
            String sql = boundSql.getSql();
            SqlSession session = getSqlSession();

            PreparedStatement pst = null;
            try {
                pst = session.getConnection().prepareStatement(sql);
                log.info("执行SQL({}):{}", operation, originalSql);
                log.info("参数列表:{}", executeParams);
                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    String property = parameterMapping.getProperty();
                    Object o = executeParams.get(property);
                    if (o != null) {
                        pst.setObject(i + 1, o);
                    }
                }
                if ("query".equalsIgnoreCase(operation)) {
                    ResultSet resultSet = pst.executeQuery();
                    List<Map<String, Object>> list = new ArrayList<>();
                    ResultSetMetaData md = resultSet.getMetaData(); //获得结果集结构信息,元数据
                    int columnCount = md.getColumnCount();   //获得列数
                    while (resultSet.next()) {
                        Map<String, Object> rowData = new LinkedHashMap<>();
                        for (int i = 1; i <= columnCount; i++) {
                            rowData.put(md.getColumnName(i).toLowerCase(), resultSet.getObject(i));
                        }
                        list.add(rowData);
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("结果:{}", list);
                    }
                    return list;
                } else if ("update".equalsIgnoreCase(operation)) {
                    int ret = pst.executeUpdate();
                    log.debug("结果:{}", ret);
                    return ret;
                } else if ("cmd".equalsIgnoreCase(operation)) {
                    boolean ret = pst.execute();
                    log.debug("结果:{}", ret);
                    return ret;
                } else {
                    return null;
                }
            } finally {
                if (pst != null) {
                    try {
                        pst.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                closeSqlSession(session);
            }
        } catch (SQLException e) {
            log.error("执行SQL语句错误", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("读取SQL文件错误", e);
            throw new RuntimeException(e);
        } catch (RuntimeException e) {
            log.error("其他运行时错误", e);
            throw e;
        } finally {
            MDC.clear();
        }

    }

    @Override
    public Object execute2(String sqlFile, String operation, Map<String, Object> executeParams, PageLink pageLink) throws FeesException {
        String originalSqlText = null;
        MDC.put("sqlFile", sqlFile);
        try {
            if (!securityCheck(executeParams)) {//防止SQL注入
                throw new FeesException("请求参数安全检查不通过", FeesErrorCode.BAD_REQUEST_PARAMS);
            }
            if (!basePath.exists()) {
                basePath.mkdirs();
            }
            Stream<String> lines = Files.lines(Paths.get(basePath.getAbsolutePath(), sqlFile));
            try {
                originalSqlText = lines.collect(Collectors.joining(""));
                originalSqlText = removeUTF8BOM(originalSqlText);
            } finally {
                lines.close();
            }
            log.debug("原始SQL:{}", originalSqlText);
            log.debug("参数列表:{}", executeParams);
            String[] originalSqls = originalSqlText.split("\\$\\$\\$");// 分隔符:$$$
            if ("queryPage".equalsIgnoreCase(operation)) {
                String sql = MapVariablesParser.parse(originalSqls[0], executeParams);//文本替换
                log.debug("执行SQL({}):{}", operation, sql);
                Page<Map<String, Object>> page = new Page<>(pageLink.getPage(), pageLink.getPageSize());
                Page<Map<String, Object>> newPage = commonMapper.queryForPage(page, sql);
                return new CollectionData(newPage.getRecords().stream().map(e -> {
                    Map<String, Object> newMap = new LinkedHashMap<>();
                    e.forEach((k, v) -> newMap.put(k.toLowerCase(), v));
                    return newMap;
                }).collect(Collectors.toList()), newPage.getTotal());
            }

            SqlSession session = getSqlSession();

            List<Statement> statements = new ArrayList<>();
            try {

                if ("queryList".equalsIgnoreCase(operation)) {
                    String sql = MapVariablesParser.parse(originalSqls[0], executeParams);//文本替换
                    PreparedStatement statement = session.getConnection().prepareStatement(sql);
                    log.debug("执行SQL({}):{}", operation, sql);
                    ResultSet resultSet = statement.executeQuery();
                    List<Map<String, Object>> list = new ArrayList<>();
                    ResultSetMetaData md = resultSet.getMetaData(); //获得结果集结构信息,元数据
                    int columnCount = md.getColumnCount();   //获得列数
                    while (resultSet.next()) {
                        Map<String, Object> rowData = new LinkedHashMap<>();
                        for (int i = 1; i <= columnCount; i++) {
                            rowData.put(md.getColumnName(i).toLowerCase(), resultSet.getObject(i));
                        }
                        list.add(rowData);
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("结果:{}", list);
                    }
                    return new CollectionData<>(list, list.size());
                } else if ("cmd".equalsIgnoreCase(operation)) {
                    Statement statement = session.getConnection().createStatement();
                    statements.add(statement);
                    List<String> sqls = new ArrayList<>();
                    for (String originalSql : originalSqls) {
                        if(StringUtils.isNotBlank(originalSql)) {
                            String sql = MapVariablesParser.parse(originalSql, executeParams);//文本替换
                            sqls.add(sql);
                            statement.addBatch(sql);
                        }
                    }
                    log.debug("执行SQL({}):{}", operation, sqls);
                    int[] ret = statement.executeBatch();
                    if (ret.length == 1) {
                        return ret[0];
                    }
                    log.debug("结果:{}", ret);
                    return ret;
                } else {
                    return null;
                }
            } finally {
                if (statements != null && statements.size() > 0) {
                    try {
                        for (Statement pst : statements) {
                            pst.close();
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                closeSqlSession(session);
            }
        } catch (SQLException e) {
            log.error("执行SQL语句错误", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("读取SQL文件错误", e);
            throw new RuntimeException(e);
        } catch (RuntimeException e) {
            log.error("其他运行时错误", e);
            throw e;
        } finally {
            MDC.clear();
        }
    }

    private boolean securityCheck(Map<String, Object> executeParams) {
        List<String> risks = Arrays.asList("select", "insert", "update", "delete", "drop", "alter");
        Arrays.asList("select", "insert", "update", "delete", "drop", "alter");
        Set<String> keySet = executeParams.keySet();
        for (String key : keySet) {
            Object value = executeParams.get(key);
            if (value instanceof String) {
                String s = (String) value;
                for (String risk : risks) {
                    if (s.contains(risk)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    private static String removeUTF8BOM(String s) {
        if (s.startsWith(UTF8_BOM)) {
            s = s.substring(1);
        }
        return s;
    }

    private SqlSession getSqlSession() {
        return SqlSessionUtils.getSqlSession(sessionTemplate.getSqlSessionFactory(),
                sessionTemplate.getExecutorType(), sessionTemplate.getPersistenceExceptionTranslator());
    }

    private void closeSqlSession(SqlSession session) {
        SqlSessionUtils.closeSqlSession(session, sessionTemplate.getSqlSessionFactory());
    }

}
