package com.jajian.search.service;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledResultSet;
import com.alibaba.druid.pool.ElasticSearchDruidDataSourceFactory;
import com.alibaba.druid.pool.ElasticSearchResultSetMetaDataBase;
import com.alibaba.druid.util.jdbc.ResultSetMetaDataBase;
import com.alibaba.fastjson.JSONObject;
import com.jajian.search.common.ElasticSearchConfig;
import com.jajian.search.dto.IndexRowData;
import com.jajian.search.dto.SearchResultDTO;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.plugin.nlpcn.QueryActionElasticExecutor;
import org.elasticsearch.search.SearchHits;
import org.nlpcn.es4sql.SearchDao;
import org.nlpcn.es4sql.jdbc.ObjectResult;
import org.nlpcn.es4sql.jdbc.ObjectResultsExtractor;
import org.nlpcn.es4sql.query.QueryAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @Auther: JaJIan
 * @Date: 2018/12/09 13:53
 * @Description: ES索引数据查询 - elasticsearch-sql 方式
 */
@Service
public class ElasticSearchSqlServiceImpl implements ElasticSearchSqlService {

    private final static Logger logger = LoggerFactory.getLogger(ElasticSearchSqlServiceImpl.class);

    @Autowired
    private ElasticSearchConfig esConfiguration;
    @Autowired
    private TransportClient transportClient;

    @Override
    public SearchResultDTO search(String sql) {
        SearchResultDTO resultDTO = new SearchResultDTO();
        try {
            long before = System.currentTimeMillis();
            SearchDao searchDao = new SearchDao(transportClient);
            QueryAction queryAction = searchDao.explain(sql);
            Object execution = QueryActionElasticExecutor.executeAnyAction(searchDao.getClient(), queryAction);
            ObjectResult result = getObjectResult(execution, true, false, false, true);
            resultDTO.setResultColumns(Sets.newHashSet(result.getHeaders()));
            List<IndexRowData> indexRowDatas = new ArrayList<>();
            for (List<Object> line : result.getLines()) {
                IndexRowData indexRowData = new IndexRowData();
                for (int i = 0; i < result.getHeaders().size(); i++) {
                    indexRowData.build(result.getHeaders().get(i), line.get(i));
                }
                indexRowDatas.add(indexRowData);
            }
            resultDTO.setResultSize(indexRowDatas.size());
            if (execution instanceof SearchHits) {
                resultDTO.setTotal(((SearchHits) execution).getTotalHits());
            } else {
                resultDTO.setTotal(indexRowDatas.size());
            }
            resultDTO.setResult(indexRowDatas);
            resultDTO.setTime((System.currentTimeMillis() - before) / 1000);
            logger.info("查询数据结果集: {}", JSONObject.toJSONString(resultDTO));
        } catch (Exception e) {
            logger.error("根据ES-SQL查询数据异常: {}", e.getMessage());
            throw new ElasticsearchException(e.getMessage());
        }
        return resultDTO;
    }

    private ObjectResult getObjectResult(Object execution,
                                         boolean flat,
                                         boolean includeScore,
                                         boolean includeType,
                                         boolean includeId) throws Exception {

        return (new ObjectResultsExtractor(includeScore, includeType, includeId)).extractResults(execution, flat);
    }

    /**
     * 查询替换为别名进行
     *
     * @param sql
     * @return
     * @throws JSQLParserException
     */
    private String replaceTableName(String sql) throws JSQLParserException {
        Statement statement = CCJSqlParserUtil.parse(sql);
        Select select = (Select) statement;

        StringBuilder buffer = new StringBuilder();
        ExpressionDeParser expressionDeParser = new ExpressionDeParser();

        TableNameParser tableNameParser = new TableNameParser(expressionDeParser, buffer);

        expressionDeParser.setSelectVisitor(tableNameParser);
        expressionDeParser.setBuffer(buffer);
        select.getSelectBody().accept(tableNameParser);
        return select.toString();
    }

    public class TableNameParser extends SelectDeParser {

        public TableNameParser(ExpressionVisitor expressionVisitor, StringBuilder buffer) {
            super(expressionVisitor, buffer);
        }

        @Override
        public void visit(Table tableName) {
            tableName.setName(tableName.getName());
        }
    }

    @Override
    public String explain(String sql) {
        try {
            SearchDao searchDao = new SearchDao(transportClient);
            QueryAction queryAction = searchDao.explain(sql);
            return queryAction.explain().explain();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * JDBC 方式查询
     *
     * @param sql
     * @param index
     * @return
     */
    @Override
    public SearchResultDTO query(String sql, String index) {
        SearchResultDTO searchResultDTO = new SearchResultDTO();
        PreparedStatement ps = null;
        Connection connection = null;
        DruidDataSource dds = null;
        try {
            long before = System.currentTimeMillis();
            Properties properties = new Properties();
            properties.put("url", buildJdbcUrl() + index);
            dds = (DruidDataSource) ElasticSearchDruidDataSourceFactory.createDataSource(properties);
            connection = dds.getConnection();
            ps = connection.prepareStatement(sql);
            DruidPooledResultSet resultSet = (DruidPooledResultSet) ps.executeQuery();
            ElasticSearchResultSetMetaDataBase metaData = (ElasticSearchResultSetMetaDataBase) resultSet.getMetaData();
            List<ResultSetMetaDataBase.ColumnMetaData> columns = metaData.getColumns();
            List<IndexRowData> indexRowDatas = new ArrayList<>();
            while (resultSet.next()) {
                IndexRowData indexRowData = new IndexRowData();
                for (int i = 0, size = columns.size(); i < size; i++) {
                    String columnLabel = columns.get(i).getColumnLabel();
                    Object value = resultSet.getObject(columnLabel);
                    indexRowData.put(columnLabel, value);
                }
                indexRowDatas.add(indexRowData);
            }
            searchResultDTO.setResult(indexRowDatas);
            searchResultDTO.setResultSize(indexRowDatas.size());
            searchResultDTO.setTotal(indexRowDatas.size());
            searchResultDTO.setResult(indexRowDatas);
            searchResultDTO.setTime((System.currentTimeMillis() - before) / 1000);
            logger.info("根据ES-SQL-JDBC查询数据结果: {}", JSONObject.toJSONString(searchResultDTO));
        } catch (SQLException e) {
            logger.error("根据ES-SQL-JDBC查询数据异常: {}", JSONObject.toJSONString(e.getMessage()));
        } catch (Exception e) {
            logger.error("根据ES-SQL-JDBC查询数据异常: {}", JSONObject.toJSONString(e.getMessage()));
        } finally {
            try {
                ps.close();
                connection.close();
                dds.close();
            } catch (SQLException e) {
                logger.error("根据ES-SQL-JDBC查询数据, 关闭数据源异常: {}", e.getMessage());
            }
        }
        return searchResultDTO;
    }

    private String buildJdbcUrl() {
        StringBuffer url = new StringBuffer("jdbc:elasticsearch://");
        String[] esSqlAddress = esConfiguration.getEsSqlAddress();
        if (esSqlAddress.length == 0) {
            throw new ElasticsearchException("there is no value of [elasticSearch.sql.host], please set it.");
        }
        url.append(String.join(",", esSqlAddress)).append("/");
        return url.toString();
    }


}