package com.purui.prims.headless.core.translator.calcite.sql.render;


import com.purui.prims.common.jsqlparser.SqlSelectHelper;
import com.purui.prims.common.pojo.DimensionConstants;
import com.purui.prims.common.pojo.enums.TimeDimensionEnum;
import com.purui.prims.headless.api.pojo.MeasureParam;
import com.purui.prims.headless.api.pojo.enums.DimensionType;
import com.purui.prims.headless.api.pojo.enums.EngineType;
import com.purui.prims.headless.core.adaptor.db.DbAdaptor;
import com.purui.prims.headless.core.adaptor.db.DbAdaptorFactory;
import com.purui.prims.headless.core.pojo.MetricQueryParam;
import com.purui.prims.headless.core.translator.calcite.s2sql.Constants;
import com.purui.prims.headless.core.translator.calcite.s2sql.DataSource;
import com.purui.prims.headless.core.translator.calcite.s2sql.Dimension;
import com.purui.prims.headless.core.translator.calcite.s2sql.Identify;
import com.purui.prims.headless.core.translator.calcite.s2sql.Materialization;
import com.purui.prims.headless.core.translator.calcite.s2sql.Measure;
import com.purui.prims.headless.core.translator.calcite.s2sql.Metric;
import com.purui.prims.headless.core.translator.calcite.schema.SemanticSchema;
import com.purui.prims.headless.core.translator.calcite.sql.Renderer;
import com.purui.prims.headless.core.translator.calcite.sql.TableView;
import com.purui.prims.headless.core.translator.calcite.sql.node.DataSourceNode;
import com.purui.prims.headless.core.translator.calcite.sql.node.DimensionNode;
import com.purui.prims.headless.core.translator.calcite.sql.node.FilterNode;
import com.purui.prims.headless.core.translator.calcite.sql.node.IdentifyNode;
import com.purui.prims.headless.core.translator.calcite.sql.node.MetricNode;
import com.purui.prims.headless.core.translator.calcite.sql.node.SemanticNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 查询渲染
 */
@Slf4j
public class SourceRender extends Renderer {

    public static TableView renderOne(String alias, List<String> fieldWheres,
                                      List<String> reqMetrics, List<String> reqDimensions,
                                      String queryWhere, DataSource datasource, SqlValidatorScope scope,
                                      SemanticSchema schema, boolean nonAgg, Map<String, MeasureParam> derivedMetrics)
        throws Exception {

        TableView dataSet = new TableView();
        TableView output = new TableView();
        List<String> queryMetrics = new ArrayList<>(reqMetrics);
        List<String> queryDimensions = new ArrayList<>(reqDimensions);
        List<String> fieldWhere = new ArrayList<>(fieldWheres);
        Map<String, String> extendFields = new HashMap<>();

        if (!fieldWhere.isEmpty()) {
            Set<String> dimensions = new HashSet<>();
            Set<String> metrics = new HashSet<>();
            whereDimMetric(fieldWhere, queryMetrics, queryDimensions, datasource, schema, dimensions, metrics);
            queryMetrics.addAll(metrics);
            queryMetrics = uniqList(queryMetrics);
            queryDimensions.addAll(dimensions);
            queryDimensions = uniqList(queryDimensions);
            mergeWhere(fieldWhere, dataSet, output, queryMetrics, queryDimensions, extendFields,
                datasource, scope,
                schema, nonAgg);
        }
        // addTimeDimension(datasource, queryDimensions);
        // 顺序输出维度
        for (String dimension : queryDimensions) {
            if (dimension.contains(Constants.DIMENSION_IDENTIFY) && queryDimensions.contains(
                dimension.split(Constants.DIMENSION_IDENTIFY)[1])) {
                continue;
            }

            // 处理不同的日期维度[默认采用模型的日期维度]（更换指标的日期维度）
            if (queryMetrics.size() == 1 && TimeDimensionEnum.containsTimeDimension(dimension)) {
                Optional<Metric> metric = getMetricByName(queryMetrics.get(0), schema);
                if (metric.isPresent() && metric.get().getTimeDimension() != null) {
                    Dimension dim = datasource.getDimensions()
                                              .stream()
                                              .filter(d -> d.getName().equalsIgnoreCase(dimension))
                                              .findFirst().orElse(null);
                    if (dim != null) {
                        if (StringUtils.isEmpty(dim.getExpr())) {
                            dim.setExpr(metric.get().getTimeDimension().getExpr());
                            dim.setExt(metric.get().getTimeDimension().getExt());
                        }
                        parseDateDimension(dim, EngineType.fromString(datasource.getType()));

                    }
                }
            }

            buildDimension(dimension.contains(Constants.DIMENSION_IDENTIFY) ? dimension : "",
                dimension.contains(Constants.DIMENSION_IDENTIFY) ? dimension.split(Constants.DIMENSION_IDENTIFY)[1]
                    : dimension, datasource, schema, nonAgg, extendFields, dataSet, output,
                scope);


        }
        // 顺序输出指标(不同指标来源的字段需做去重)
        List<String> outMetrics = new ArrayList<>();
        List<String> innerMetrics = new ArrayList<>();
        List<String> filterMetrics = new ArrayList<>();
        for (String metric : queryMetrics) {
            MetricNode metricNode =
                buildMetricNode(metric, datasource, scope, schema, nonAgg, alias, derivedMetrics, queryDimensions, output);
            // 聚合字段(外层）
            if (!metricNode.getAggNode().isEmpty()) {
                metricNode.getAggNode().entrySet().stream().forEach(m -> {
                    if (!outMetrics.contains(m.getKey())) {
                        output.getMeasure().add(m.getValue());
                        outMetrics.add(m.getKey());
                    }
                });
            }
            // 原始字段(内层)
            if (metricNode.getNonAggNode() != null) {
                metricNode.getNonAggNode().entrySet().stream().forEach(m -> {
                    if (!innerMetrics.contains(m.getKey())) {
                        dataSet.getMeasure().add(m.getValue());
                        innerMetrics.add(m.getKey());
                    }
                });
            }
            if (metricNode.getMeasureFilter() != null) {
                metricNode.getMeasureFilter().entrySet().stream().forEach(m -> {
                    if (!filterMetrics.contains(m.getKey())) {
                        dataSet.getFilter().add(m.getValue());
                    }
                });
            }

        }

        // 简化单表查询
        if (StringUtils.isNotEmpty(datasource.getTableQuery())) {
            // 将表名按点号分割
            String[] parts = datasource.getTableQuery().split("\\.");
            SqlIdentifier tableNode = new SqlIdentifier(Arrays.asList(parts), SqlParserPos.ZERO);
            dataSet.setTable(tableNode);
        } else {
            // SQL查询封装为子表查询
            SqlNode tableNode = DataSourceNode.buildExtend(datasource, extendFields, scope);
            dataSet.setTable(tableNode);
        }
        output.setTable(SemanticNode.buildAs(
            Constants.DATASOURCE_TABLE_OUT_PREFIX + datasource.getName() + "_" + UUID.randomUUID().toString()
                                                                                     .substring(32),
            dataSet.build()));
        return output;
    }

    private static void parseDateDimension(Dimension dim, EngineType engineType) {
        // 日期格式转换
        if (dim.getExt() != null && dim.getExt().containsKey(DimensionConstants.DIMENSION_TIME_FORMAT)) {
            String timeFormat = dim.getExt().get(DimensionConstants.DIMENSION_TIME_FORMAT).toString();
            DbAdaptor dbAdaptor = DbAdaptorFactory.getEngineAdaptor(engineType.name().toLowerCase(Locale.ROOT));
            TimeDimensionEnum timeType = TimeDimensionEnum.parseTimeDimension(timeFormat);
            String expr = dbAdaptor.getDateFormat(timeType.name(), timeFormat, dim.getExpr());
            dim.setExpr(expr);
        }
    }

    private static void buildDimension(String alias, String dimension, DataSource datasource, SemanticSchema schema,
                                       boolean nonAgg, Map<String, String> extendFields, TableView dataSet,
                                       TableView output,
                                       SqlValidatorScope scope)
        throws Exception {
        List<Dimension> dimensionList = schema.getDimension().get(datasource.getName());
        EngineType engineType = EngineType.fromString(schema.getSemanticModel().getDatabase().getType());
        boolean isAdd = false;
        if (!CollectionUtils.isEmpty(dimensionList)) {
            for (Dimension dim : dimensionList) {
                if (!dim.getName().equalsIgnoreCase(dimension)) {
                    continue;
                }
                if (DimensionType.isTimeDimension(dim.getType())) {
                    parseDateDimension(dim, engineType);
                }
                dataSet.getMeasure().add(DimensionNode.buildArray(dim, scope, engineType));
                addExtendFields(dim, extendFields);
                if (nonAgg) {
                    output.getMeasure().add(DimensionNode.buildName(dim, scope, engineType));
                    isAdd = true;
                    continue;
                }

                if ("".equals(alias)) {
                    output.getDimension().add(DimensionNode.buildName(dim, scope, engineType));
                } else {
                    output.getDimension().add(DimensionNode.buildNameAs(alias, dim, scope, engineType));
                }
                isAdd = true;
                break;
            }
        }
        // 主键
        if (!isAdd) {
            Optional<Identify> identify = datasource.getIdentifiers().stream()
                                                    .filter(i -> i.getName().equalsIgnoreCase(dimension)).findFirst();
            if (identify.isPresent()) {
                if (nonAgg) {
                    dataSet.getMeasure().add(SemanticNode.parse(identify.get().getName(), scope, engineType));
                    output.getMeasure().add(SemanticNode.parse(identify.get().getName(), scope, engineType));
                } else {
                    // 聚合
                    dataSet.getMeasure().add(SemanticNode.parse(identify.get().getName(), scope, engineType));
                    output.getDimension().add(SemanticNode.parse(identify.get().getName(), scope, engineType));
                }
                isAdd = true;
            }
        }
        if (isAdd) {
            return;
        }
        // 维度
        Optional<Dimension> dimensionOptional = getDimensionByName(dimension, datasource);
        if (dimensionOptional.isPresent()) {
            dataSet.getMeasure().add(DimensionNode.buildArray(dimensionOptional.get(), scope, engineType));
            addExtendFields(dimensionOptional.get(), extendFields);
            if (nonAgg) {
                output.getMeasure().add(DimensionNode.buildName(dimensionOptional.get(), scope, engineType));
                return;
            }
            output.getDimension().add(DimensionNode.buildName(dimensionOptional.get(), scope, engineType));
        }
    }

    private static void addExtendFields(Dimension dimension, Map<String, String> extendFields) {
        if (dimension.getDataType().isArray()) {
            if (Objects.nonNull(dimension.getExt()) && dimension.getExt()
                                                                .containsKey(Constants.DIMENSION_DELIMITER)) {
                extendFields.put(dimension.getExpr(),
                    (String) dimension.getExt().get(Constants.DIMENSION_DELIMITER));
            } else {
                extendFields.put(dimension.getExpr(), "");
            }
        }
    }

    private static List<SqlNode> getWhereMeasure(List<String> fields, List<String> queryMetrics,
                                                 List<String> queryDimensions, Map<String, String> extendFields,
                                                 DataSource datasource,
                                                 SqlValidatorScope scope,
                                                 SemanticSchema schema, boolean nonAgg) throws Exception {
        Iterator<String> iterator = fields.iterator();
        List<SqlNode> whereNode = new ArrayList<>();
        EngineType engineType = EngineType.fromString(schema.getSemanticModel().getDatabase().getType());
        while (iterator.hasNext()) {
            String cur = iterator.next();
            if (queryDimensions.contains(cur) || queryMetrics.contains(cur)) {
                iterator.remove();
            }
        }
        for (String where : fields) {
            List<Dimension> dimensionList = schema.getDimension().get(datasource.getName());
            boolean isAdd = false;
            if (!CollectionUtils.isEmpty(dimensionList)) {
                for (Dimension dim : dimensionList) {
                    if (!dim.getName().equalsIgnoreCase(where)) {
                        continue;
                    }
                    whereNode.addAll(DimensionNode.expand(dim, scope, engineType));
                    isAdd = true;
                }
            }
            Optional<Identify> identify = getIdentifyByName(where, datasource);
            if (identify.isPresent()) {
                // 避免重复 2024-08-09
                if (identify.get().getName().equalsIgnoreCase(where)) {
                    continue;
                }
                whereNode.add(IdentifyNode.build(identify.get(), scope, engineType));
                isAdd = true;
            }
            if (isAdd) {
                continue;
            }
            Optional<Dimension> dimensionOptional = getDimensionByName(where, datasource);
            if (dimensionOptional.isPresent()) {
                whereNode.add(DimensionNode.buildArray(dimensionOptional.get(), scope, engineType));
                addExtendFields(dimensionOptional.get(), extendFields);
            }
        }
        return whereNode;
    }

    private static void mergeWhere(List<String> fields, TableView dataSet, TableView outputSet,
                                   List<String> queryMetrics,
                                   List<String> queryDimensions, Map<String, String> extendFields,
                                   DataSource datasource,
                                   SqlValidatorScope scope,
                                   SemanticSchema schema,
                                   boolean nonAgg) throws Exception {
        List<SqlNode> whereNode = getWhereMeasure(fields, queryMetrics, queryDimensions, extendFields, datasource,
            scope, schema,
            nonAgg);
        dataSet.getMeasure().addAll(whereNode);
    }

    public static void whereDimMetric(List<String> fields, List<String> queryMetrics,
                                      List<String> queryDimensions, DataSource datasource, SemanticSchema schema,
                                      Set<String> dimensions,
                                      Set<String> metrics) {
        for (String field : fields) {
            if (queryDimensions.contains(field) || queryMetrics.contains(field)) {
                continue;
            }
            String filterField = field;
            if (field.contains(Constants.DIMENSION_IDENTIFY)) {
                filterField = field.split(Constants.DIMENSION_IDENTIFY)[1];
            }
            addField(filterField, field, datasource, schema, dimensions, metrics);
        }
    }

    private static void addField(String field, String oriField, DataSource datasource, SemanticSchema schema,
                                 Set<String> dimensions,
                                 Set<String> metrics) {
        Optional<Dimension> dimension = datasource.getDimensions().stream()
                                                  .filter(d -> d.getName().equalsIgnoreCase(field)).findFirst();
        if (dimension.isPresent()) {
            dimensions.add(oriField);
            return;
        }
        Optional<Identify> identify = datasource.getIdentifiers().stream()
                                                .filter(i -> i.getName().equalsIgnoreCase(field)).findFirst();
        if (identify.isPresent()) {
            dimensions.add(oriField);
            return;
        }
        if (schema.getDimension().containsKey(datasource.getName())) {
            Optional<Dimension> dataSourceDim = schema.getDimension().get(datasource.getName()).stream()
                                                      .filter(d -> d.getName().equalsIgnoreCase(field)).findFirst();
            if (dataSourceDim.isPresent()) {
                dimensions.add(oriField);
                return;
            }
        }
        Optional<Measure> metric = datasource.getMeasures()
                                             .stream().filter(m -> m.getName().equalsIgnoreCase(field)).findFirst();
        if (metric.isPresent()) {
            metrics.add(oriField);
            return;
        }
        Optional<Metric> datasourceMetric = schema.getMetrics()
                                                  .stream().filter(m -> m.getName().equalsIgnoreCase(field))
                                                  .findFirst();
        if (datasourceMetric.isPresent()) {
            Set<String> measures = datasourceMetric.get().getMetricTypeParams().getMeasures().stream()
                                                   .map(m -> m.getName()).collect(
                    Collectors.toSet());
            if (datasource.getMeasures().stream().map(m -> m.getName()).collect(Collectors.toSet())
                          .containsAll(measures)) {
                metrics.add(oriField);
                return;
            }
        }
    }

    public static boolean isDimension(String name, DataSource datasource, SemanticSchema schema) {
        Optional<Dimension> dimension = datasource.getDimensions().stream()
                                                  .filter(d -> d.getName().equalsIgnoreCase(name)).findFirst();
        if (dimension.isPresent()) {
            return true;
        }
        Optional<Identify> identify = datasource.getIdentifiers().stream()
                                                .filter(i -> i.getName().equalsIgnoreCase(name)).findFirst();
        if (identify.isPresent()) {
            return true;
        }
        if (schema.getDimension().containsKey(datasource.getName())) {
            Optional<Dimension> dataSourceDim = schema.getDimension().get(datasource.getName()).stream()
                                                      .filter(d -> d.getName().equalsIgnoreCase(name)).findFirst();
            if (dataSourceDim.isPresent()) {
                return true;
            }
        }
        return false;
    }

    private static void addTimeDimension(DataSource dataSource, List<String> queryDimension) {
        if (Materialization.TimePartType.ZIPPER.equals(dataSource.getTimePartType())) {
            Optional<Dimension> startTimeOp = dataSource.getDimensions().stream()
                                                        .filter(d -> Constants.DIMENSION_TYPE_TIME.equalsIgnoreCase(
                                                            d.getType()))
                                                        .filter(d -> d.getName().startsWith(
                                                            Constants.MATERIALIZATION_ZIPPER_START)).findFirst();
            Optional<Dimension> endTimeOp = dataSource.getDimensions().stream()
                                                      .filter(d -> Constants.DIMENSION_TYPE_TIME.equalsIgnoreCase(
                                                          d.getType()))
                                                      .filter(d -> d.getName()
                                                                    .startsWith(Constants.MATERIALIZATION_ZIPPER_END))
                                                      .findFirst();
            if (startTimeOp.isPresent() && !queryDimension.contains(startTimeOp.get().getName())) {
                queryDimension.add(startTimeOp.get().getName());
            }
            if (endTimeOp.isPresent() && !queryDimension.contains(endTimeOp.get().getName())) {
                queryDimension.add(endTimeOp.get().getName());
            }
        } else {
            Optional<Dimension> timeOp = dataSource.getDimensions().stream()
                                                   .filter(d -> Constants.DIMENSION_TYPE_TIME.equalsIgnoreCase(
                                                       d.getType())).findFirst();
            if (timeOp.isPresent() && !queryDimension.contains(timeOp.get().getName())) {
                queryDimension.add(timeOp.get().getName());
            }
        }
    }

    public void render(MetricQueryParam metricQueryParam, List<DataSource> dataSources, SqlValidatorScope scope,
                       SemanticSchema schema, boolean nonAgg) throws Exception {
        String queryWhere = metricQueryParam.getWhere();
        Set<String> whereFields = new HashSet<>();
        List<String> fieldWhere = new ArrayList<>();
        EngineType engineType = EngineType.fromString(schema.getSemanticModel().getDatabase().getType());
        if (queryWhere != null && !queryWhere.isEmpty()) {
            SqlNode sqlNode = SemanticNode.parse(queryWhere, scope, engineType);
            FilterNode.getFilterField(sqlNode, whereFields);
            fieldWhere = whereFields.stream().collect(Collectors.toList());
        }
        // 增加复合指标中的内容
        if (metricQueryParam.getCompositeMetrics() != null) {
            metricQueryParam.getCompositeMetrics().forEach((k, v) -> {
                Set<String> metrics = SqlSelectHelper.getColumnFromExpr(v);
                if (metrics != null) {
                    metrics.stream().forEach(m -> {
                        if (!metricQueryParam.getMetrics().contains(m)) {
                            metricQueryParam.getMetrics().add(m);
                        }
                    });
                }
            });
        }

        if (dataSources.size() == 1) {
            DataSource dataSource = dataSources.get(0);
            super.tableView = renderOne("", fieldWhere, metricQueryParam.getMetrics(),
                metricQueryParam.getDimensions(),
                metricQueryParam.getWhere(), dataSource, scope, schema, nonAgg,
                metricQueryParam.getDerivedMetrics());
            return;
        }
        JoinRender joinRender = new JoinRender();
        joinRender.render(metricQueryParam, dataSources, scope, schema, nonAgg);
        super.tableView = joinRender.getTableView();
    }

    private void addLimitCondition(TableView output, SemanticSchema schema) {
        List<Metric> metricList = schema.getSemanticModel().getMetrics()
                                        .stream()
                                        .filter(m -> m.getMetricTypeParams() != null)
                                        .collect(Collectors.toList());

        for (Metric metricResp : metricList) {
            List<Measure> measureParamsList = metricResp.getMetricTypeParams().getMeasures();
            measureParamsList.stream()
                             .filter(measure -> StringUtils.isNotEmpty(measure.getConstraint()))
                             .forEach(param -> {
                             });
        }

    }

}
