package cn.datax.service.data.market.service.impl;

import cn.datax.common.exception.DataException;
import cn.datax.common.utils.ThrowableUtil;
import cn.datax.service.data.market.api.dto.*;
import cn.datax.service.data.market.api.entity.DataApiEntity;
import cn.datax.service.data.market.api.enums.ConfigType;
import cn.datax.service.data.market.api.vo.DataApiVo;
import cn.datax.service.data.market.api.vo.SqlParseVo;
import cn.datax.service.data.market.service.DataApiService;
import cn.datax.service.data.market.mapstruct.DataApiMapper;
import cn.datax.service.data.market.dao.DataApiDao;
import cn.datax.common.base.BaseServiceImpl;
import cn.datax.service.data.market.utils.SqlBuilderUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.SimpleNode;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.util.SelectUtils;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 数据API信息表 服务实现类
 * </p>
 *
 * @author yuwei
 * @since 2020-03-31
 */
@CacheConfig(cacheNames = "data:market:apis")
@Slf4j
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class DataApiServiceImpl extends BaseServiceImpl<DataApiDao, DataApiEntity> implements DataApiService {

    @Autowired
    private DataApiDao dataApiDao;

    @Autowired
    private DataApiMapper dataApiMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveDataApi(DataApiDto dataApiDto) {
        DataApiEntity dataApi = shareCode(dataApiDto);
        dataApiDao.insert(dataApi);
    }

    @CachePut(key = "#p0.id")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateDataApi(DataApiDto dataApiDto) {
        DataApiEntity dataApi = shareCode(dataApiDto);
        dataApiDao.updateById(dataApi);
    }

    private DataApiEntity shareCode(DataApiDto dataApiDto) {
        DataApiEntity dataApi = dataApiMapper.toEntity(dataApiDto);
        Integer configType = dataApi.getExecuteConfig().getConfigType();
        if (ConfigType.FORM.getKey() == configType) {
            try {
                dataApi.getExecuteConfig().setSqlText(sqlJdbcNamedParameterBuild(dataApi));
            } catch (JSQLParserException e) {
                log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
                throw new DataException("SQL语法有问题，解析出错");
            }
        } else if (ConfigType.SCRIPT.getKey() == configType) {}
        return dataApi;
    }

    @Cacheable(key = "#id", unless = "#result == null")
    public DataApiVo getDataApiById(String id) {
        DataApiEntity dataApiEntity = super.getById(id);
        return dataApiMapper.toVO(dataApiEntity);
    }

    @CacheEvict(key = "#id")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDataApiById(String id) {
        dataApiDao.deleteById(id);
    }

    @Override
    public SqlParseVo sqlParse(SqlParseDto sqlParseDto) {
        String sql = sqlParseDto.getSqlText();
        sql = sql.replace(SqlBuilderUtil.getInstance().MARK_KEY_START, "");
        sql = sql.replace(SqlBuilderUtil.getInstance().MARK_KEY_END, "");
        Statement stmt;
        try {
            stmt = CCJSqlParserUtil.parse(sql);
        } catch (JSQLParserException e) {
            log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
            throw new DataException("SQL语法有问题，解析出错");
        }
        final List<String> variables = new ArrayList<>();
        final List<String> cols = new ArrayList<>();
        stmt.accept(new StatementVisitorAdapter() {
            @Override
            public void visit(Select select) {
                select.getSelectBody().accept(new SelectVisitorAdapter() {
                    @Override
                    public void visit(PlainSelect plainSelect) {
                        plainSelect.getSelectItems().stream().forEach(selectItem -> {
                            selectItem.accept(new SelectItemVisitorAdapter() {
                                @Override
                                public void visit(SelectExpressionItem item) {
                                    String columnName;
                                    if (item.getAlias() == null) {
                                        SimpleNode node = item.getExpression().getASTNode();
                                        Object value = node.jjtGetValue();
                                        if (value instanceof Column) {
                                            columnName = ((Column) value).getColumnName();
                                        } else if (value instanceof Function) {
                                            columnName = value.toString();
                                        } else {
                                            // 增加对select 'aaa' from table; 的支持
                                            columnName = String.valueOf(value);
                                            columnName = columnName.replace("'", "");
                                            columnName = columnName.replace("\"", "");
                                            columnName = columnName.replace("`", "");
                                        }
                                    } else {
                                        columnName = item.getAlias().getName();
                                    }
                                    columnName = columnName.replace("'", "");
                                    columnName = columnName.replace("\"", "");
                                    columnName = columnName.replace("`", "");
                                    cols.add(columnName);
                                }
                            });
                        });
                        plainSelect.getWhere().accept(new ExpressionVisitorAdapter() {
                            @Override
                            public void visit(JdbcNamedParameter jdbcNamedParameter) {
                                variables.add(jdbcNamedParameter.getName());
                            }
                        });
                    }
                });
            }
        });
        SqlParseVo sqlParseVo = new SqlParseVo();
        List<ReqParam> reqParams = variables.stream().map(s -> {
            ReqParam reqParam = new ReqParam();
            reqParam.setParamName(s);
            reqParam.setNullable(0);
            return reqParam;
        }).collect(Collectors.toList());
        sqlParseVo.setReqParams(reqParams);
        List<ResParam> resParams = cols.stream().map(s -> {
            ResParam resParam = new ResParam();
            resParam.setParamName(s);
            return resParam;
        }).collect(Collectors.toList());
        sqlParseVo.setResParams(resParams);
        return sqlParseVo;
    }

    private String sqlJdbcNamedParameterBuild(DataApiEntity dataApi) throws JSQLParserException {
        Table table = new Table(dataApi.getExecuteConfig().getTableName());
        String[] resParams = dataApi.getResParams().stream().map(s -> s.getParamName()).toArray(String[]::new);
        Select select = SelectUtils.buildSelectFromTableAndExpressions(table, resParams);
        return SqlBuilderUtil.getInstance().buildHql(select.toString(), dataApi.getReqParams());
    }

    private String sqlJdbcNamedParameterParse(String sqlText) throws JSQLParserException {
        final StringBuilder buffer = new StringBuilder();
        ExpressionDeParser expressionDeParser = new ExpressionDeParser() {
            @Override
            public void visit(JdbcNamedParameter jdbcNamedParameter) {
                this.getBuffer().append("?");
            }
        };
        SelectDeParser deparser = new SelectDeParser(expressionDeParser, buffer);
        expressionDeParser.setSelectVisitor(deparser);
        expressionDeParser.setBuffer(buffer);
        Statement stmt = CCJSqlParserUtil.parse(sqlText);
        stmt.accept(new StatementVisitorAdapter() {
            @Override
            public void visit(Select select) {
                select.getSelectBody().accept(deparser);
            }
        });
        return buffer.toString();
    }
}
