package com.purui.prims.headless.server.utils;

import com.purui.prims.common.jsqlparser.SqlSelectHelper;
import com.purui.prims.common.pojo.Constants;
import com.purui.prims.common.pojo.QueryColumn;
import com.purui.prims.common.pojo.enums.TimeDimensionEnum;
import com.purui.prims.headless.api.pojo.enums.SemanticType;
import com.purui.prims.headless.api.pojo.request.QueryMultiStructReq;
import com.purui.prims.headless.api.pojo.response.MetricResp;
import com.purui.prims.headless.api.pojo.response.MetricSchemaResp;
import com.purui.prims.headless.api.pojo.response.SemanticQueryResp;
import com.purui.prims.headless.api.pojo.response.SemanticSchemaResp;
import com.purui.prims.headless.core.pojo.QueryStatement;
import com.purui.prims.headless.core.utils.SqlGenerateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.purui.prims.common.pojo.Constants.JOIN_UNDERLINE;
import static com.purui.prims.common.pojo.Constants.UNIONALL;


@Slf4j
@Component
public class QueryUtils {

    private static final String pattern = "\\(`(.*?)`\\)";

    private static final String no_quotation_pattern = "\\((.*?)\\)";

    @Value("${s2.query-optimizer.enable:true}")
    private Boolean optimizeEnable;

    public void populateQueryColumns(SemanticQueryResp semanticQueryResp, SemanticSchemaResp semanticSchemaResp) {
        Map<String, MetricResp> metricRespMap = createMetricRespMap(semanticSchemaResp);
        Map<String, String> namePair = new HashMap<>();
        Map<String, String> nameTypePair = new HashMap<>();
        populateNamePairs(semanticSchemaResp, namePair, nameTypePair);
        List<QueryColumn> columns = semanticQueryResp.getColumns();
        columns.forEach(column -> processColumn(column, namePair, nameTypePair, metricRespMap));
    }

    private Map<String, MetricResp> createMetricRespMap(SemanticSchemaResp semanticSchemaResp) {
        List<MetricSchemaResp> metrics = semanticSchemaResp.getMetrics();
        return metrics.stream().collect(Collectors.toMap(MetricResp::getBizName, a -> a, (k1, k2) -> k1));
    }

    private void populateNamePairs(SemanticSchemaResp semanticSchemaResp,
                                   Map<String, String> namePair,
                                   Map<String, String> nameTypePair) {
        for (TimeDimensionEnum timeDimensionEnum : TimeDimensionEnum.values()) {
            namePair.put(timeDimensionEnum.getName().toLowerCase(), "日期");
            nameTypePair.put(timeDimensionEnum.getName(), "DATE");
        }
        semanticSchemaResp.getMetrics().forEach(metricDesc -> {
            namePair.put(metricDesc.getBizName().toLowerCase(Locale.ROOT), metricDesc.getName());
            namePair.put(getName(metricDesc.getName().toLowerCase(Locale.ROOT)), metricDesc.getDescription());
            nameTypePair.put(metricDesc.getBizName().toLowerCase(Locale.ROOT), SemanticType.NUMBER.name());
        });
        semanticSchemaResp.getDimensions().forEach(dimensionDesc -> {
            namePair.put(dimensionDesc.getBizName().toLowerCase(Locale.ROOT), dimensionDesc.getName());
            namePair.put(getName(dimensionDesc.getBizName().toLowerCase(Locale.ROOT)), dimensionDesc.getDescription());
            nameTypePair.put(dimensionDesc.getBizName().toLowerCase(Locale.ROOT), dimensionDesc.getSemanticType());
        });
    }

    private void processColumn(QueryColumn column,
                               Map<String, String> namePair,
                               Map<String, String> nameTypePair,
                               Map<String, MetricResp> metricRespMap) {
        String nameEn = getName(column.getNameEn().toLowerCase());
        if (nameEn.contains(JOIN_UNDERLINE)) {
            nameEn = nameEn.split(JOIN_UNDERLINE)[1];
        }
        //set name
        if (namePair.containsKey(nameEn)) {
            column.setName(namePair.get(nameEn));
        } else {
            String nameEnByRegex = getNameEnByRegex(nameEn, pattern);
            if (StringUtils.isEmpty(nameEnByRegex)) {
                nameEnByRegex = getNameEnByRegex(nameEn, no_quotation_pattern);
            }
            if (StringUtils.isNotEmpty(nameEnByRegex) && StringUtils.isNotEmpty(namePair.get(nameEnByRegex))) {
                String filedName = namePair.get(nameEnByRegex);
                column.setName(nameEn.replaceAll(nameEnByRegex, filedName));
            }
        }
        //set showType
        if (nameTypePair.containsKey(nameEn)) {
            column.setShowType(nameTypePair.get(nameEn));
        }
        if (!nameTypePair.containsKey(nameEn) && isNumberType(column.getType())) {
            column.setShowType(SemanticType.NUMBER.name());
        }
        if (StringUtils.isEmpty(column.getShowType())) {
            column.setShowType(SemanticType.CATEGORY.name());
        }
        //set dataFormat/dataFormatType
        if (metricRespMap.containsKey(nameEn)) {
            column.setDataFormatType(metricRespMap.get(nameEn).getDataFormatType());
            column.setDataFormat(metricRespMap.get(nameEn).getDataFormat());
        }
    }

    private boolean isNumberType(String type) {
        if (StringUtils.isBlank(type)) {
            return false;
        }
        return type.equalsIgnoreCase("int") || type.equalsIgnoreCase("bigint")
               || type.equalsIgnoreCase("float") || type.equalsIgnoreCase("double")
               || type.equalsIgnoreCase("numeric")
               || type.toLowerCase().startsWith("uint") || type.toLowerCase().startsWith("int");
    }

    private String getName(String nameEn) {
        Pattern pattern = Pattern.compile("\\((.*?)\\)");
        Matcher matcher = pattern.matcher(nameEn);
        if (matcher.find()) {
            return matcher.group(1).replaceAll("`", "");
        }
        return nameEn;
    }

    private String getNameEnByRegex(String nameEn, String pattern) {
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(nameEn);

        if (m.find()) {
            String result = m.group(1);
            return result;
        }
        return null;
    }

    public QueryStatement sqlParserUnion(QueryMultiStructReq queryMultiStructCmd, List<QueryStatement> sqlParsers) {
        QueryStatement sqlParser = new QueryStatement();
        StringBuilder unionSqlBuilder = new StringBuilder();
        for (int i = 0; i < sqlParsers.size(); i++) {
            String selectStr = SqlGenerateUtils.getUnionSelect(queryMultiStructCmd.getQueryStructReqs().get(i));
            unionSqlBuilder.append(String.format("select %s from ( %s ) sub_sql_%s",
                selectStr,
                sqlParsers.get(i).getSql(), i));
            unionSqlBuilder.append(UNIONALL);
        }
        String unionSql = unionSqlBuilder.substring(0, unionSqlBuilder.length() - Constants.UNIONALL.length());
        sqlParser.setSql(unionSql);
        return sqlParser;
    }

    public QueryStatement sqlParserLeftJoin(List<QueryStatement> sqlParsers, String outputSql) {
        QueryStatement queryStatement = new QueryStatement();
        try {
            String subQuery =
                SqlSelectHelper.combineSubQueries(sqlParsers.stream().map(QueryStatement::getSql).collect(Collectors.toList()));
            String sql = subQuery;
            if (StringUtils.isNotEmpty(outputSql)) {
                sql = String.format(outputSql, subQuery);
            }
            queryStatement.setSql(sql);
            if (sqlParsers.size() > 0) {
                queryStatement.setSemanticModel(sqlParsers.get(0).getSemanticModel());
                queryStatement.setSemanticSchemaResp(sqlParsers.get(0).getSemanticSchemaResp());
            }
        } catch (Exception ex) {
            log.info("combineSubQueries error:{}", ex);
        }
        return queryStatement;
    }

    public Boolean enableOptimize() {
        return optimizeEnable;
    }
}
