package com.ruoyi.generator.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.domain.GenTable;
import com.ruoyi.generator.domain.GenTableColumn;
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockDefinition;
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockInstance;
import com.ruoyi.generator.domain.front.dto.block.ChartDatasetConfig;
import com.ruoyi.generator.domain.front.dto.block.ChartDateRangeConfig;
import com.ruoyi.generator.domain.front.dto.block.ChartDimensionConfig;
import com.ruoyi.generator.domain.front.dto.block.ChartFilterConfig;
import com.ruoyi.generator.domain.front.dto.block.ChartMetricConfig;
import com.ruoyi.generator.domain.front.dto.block.ChartQueryParameter;
import com.ruoyi.generator.domain.front.dto.block.ChartQueryRenderModel;
import com.ruoyi.generator.domain.front.dto.block.ChartSortConfig;
import org.springframework.stereotype.Component;

@Component
public class ChartBlockRenderSupport
{
    public ChartQueryRenderModel build(GenTable table, BusinessBlockDefinition definition,
            BusinessBlockInstance instance)
    {
        if (table == null || definition == null || instance == null || instance.getDataset() == null)
        {
            throw new ServiceException("图表查询配置不完整");
        }

        ChartDatasetConfig dataset = instance.getDataset();
        boolean metricOnly = "metric".equalsIgnoreCase(StringUtils.defaultString(definition.getChartType()));
        List<ChartMetricConfig> metrics = dataset.getMetrics() == null
                ? Collections.<ChartMetricConfig>emptyList() : dataset.getMetrics();
        List<ChartQueryParameter> parameters = new ArrayList<ChartQueryParameter>();

        String dimensionSql = metricOnly ? "" : dimensionSql(table, dataset.getDimension());
        ChartQueryRenderModel model = new ChartQueryRenderModel();
        model.setTableName(table.getTableName());
        model.setDimensionSql(dimensionSql);
        model.setMetricSelectSql(metricSelectSql(table, metrics));
        model.setWhereSql(whereSql(table, dataset, parameters));
        model.setGroupBySql(metricOnly ? "" : dimensionSql);
        model.setOrderBySql(orderBySql(dataset.getSort(), metricOnly));
        model.setLimit(dataset.getLimit());
        model.setParameters(parameters);
        model.setMetrics(metrics);
        model.setDateRange(dataset.getDateRange());
        model.setMetricOnly(metricOnly);
        return model;
    }

    private String dimensionSql(GenTable table, ChartDimensionConfig dimension)
    {
        if (dimension == null)
        {
            throw new ServiceException("图表维度配置不存在");
        }
        String column = requireColumn(table, dimension.getField()).getColumnName();
        String timeUnit = StringUtils.defaultString(dimension.getTimeUnit()).toLowerCase();
        if ("day".equals(timeUnit))
        {
            return "DATE_FORMAT(t." + column + ", '%Y-%m-%d')";
        }
        if ("week".equals(timeUnit))
        {
            return "DATE_FORMAT(t." + column + ", '%x-W%v')";
        }
        if ("month".equals(timeUnit))
        {
            return "DATE_FORMAT(t." + column + ", '%Y-%m')";
        }
        return "t." + column;
    }

    private String metricSelectSql(GenTable table, List<ChartMetricConfig> metrics)
    {
        List<String> selections = new ArrayList<String>();
        for (ChartMetricConfig metric : metrics)
        {
            String aggregate = StringUtils.defaultString(metric.getAggregate()).toLowerCase();
            String column = StringUtils.isEmpty(metric.getField())
                    ? "" : requireColumn(table, metric.getField()).getColumnName();
            selections.add(metricSql(column, aggregate, metric.getAlias()));
        }
        return join(selections, ", ");
    }

    private String metricSql(String column, String aggregate, String alias)
    {
        if ("count".equals(aggregate))
        {
            return StringUtils.isEmpty(column)
                    ? "COUNT(*) AS " + alias
                    : "COUNT(t." + column + ") AS " + alias;
        }
        if ("avg".equals(aggregate))
        {
            return "AVG(t." + column + ") AS " + alias;
        }
        return "SUM(t." + column + ") AS " + alias;
    }

    private String whereSql(GenTable table, ChartDatasetConfig dataset, List<ChartQueryParameter> parameters)
    {
        List<String> conditions = new ArrayList<String>();
        List<ChartFilterConfig> filters = dataset.getFilters() == null
                ? Collections.<ChartFilterConfig>emptyList() : dataset.getFilters();
        for (ChartFilterConfig filter : filters)
        {
            String column = requireColumn(table, filter.getField()).getColumnName();
            String operator = StringUtils.defaultString(filter.getOperator()).toLowerCase();
            addFilterCondition(conditions, parameters, column, operator, filter.getValue());
        }

        ChartDateRangeConfig dateRange = dataset.getDateRange();
        if (dateRange != null)
        {
            String column = requireColumn(table, dateRange.getField()).getColumnName();
            conditions.add("t." + column + " >= #{dateStart}");
            conditions.add("t." + column + " < #{dateEnd}");
        }
        return join(conditions, " AND ");
    }

    private void addFilterCondition(List<String> conditions, List<ChartQueryParameter> parameters,
            String column, String operator, Object value)
    {
        String columnSql = "t." + column;
        if ("is_null".equals(operator))
        {
            conditions.add(columnSql + " IS NULL");
            return;
        }
        if ("is_not_null".equals(operator))
        {
            conditions.add(columnSql + " IS NOT NULL");
            return;
        }
        if ("between".equals(operator))
        {
            List<Object> values = values(value);
            String first = addParameter(parameters, values.get(0));
            String second = addParameter(parameters, values.get(1));
            conditions.add(columnSql + " BETWEEN " + first + " AND " + second);
            return;
        }
        if ("in".equals(operator))
        {
            List<String> placeholders = new ArrayList<String>();
            for (Object item : values(value))
            {
                placeholders.add(addParameter(parameters, item));
            }
            conditions.add(columnSql + " IN (" + join(placeholders, ", ") + ")");
            return;
        }

        String sqlOperator = operatorSql(operator);
        conditions.add(columnSql + " " + sqlOperator + " " + addParameter(parameters, value));
    }

    private String operatorSql(String operator)
    {
        if ("ne".equals(operator)) return "<>";
        if ("gt".equals(operator)) return ">";
        if ("gte".equals(operator)) return ">=";
        if ("lt".equals(operator)) return "<";
        if ("lte".equals(operator)) return "<=";
        return "=";
    }

    private String addParameter(List<ChartQueryParameter> parameters, Object value)
    {
        String name = "filter" + parameters.size();
        parameters.add(new ChartQueryParameter(name, value));
        return "#{" + name + "}";
    }

    private List<Object> values(Object value)
    {
        List<Object> values = new ArrayList<Object>();
        if (value instanceof Collection)
        {
            values.addAll((Collection<?>) value);
            return values;
        }
        if (value != null && value.getClass().isArray())
        {
            for (int index = 0; index < Array.getLength(value); index++)
            {
                values.add(Array.get(value, index));
            }
            return values;
        }
        values.add(value);
        return values;
    }

    private String orderBySql(ChartSortConfig sort, boolean metricOnly)
    {
        if (sort == null)
        {
            return "";
        }
        String by = "dimension".equals(sort.getBy()) && !metricOnly ? "dimensionKey" : sort.getBy();
        return by + " " + StringUtils.defaultString(sort.getOrder()).toUpperCase();
    }

    private GenTableColumn requireColumn(GenTable table, String field)
    {
        List<GenTableColumn> columns = table.getColumns() == null
                ? Collections.<GenTableColumn>emptyList() : table.getColumns();
        for (GenTableColumn column : columns)
        {
            if (column != null && (StringUtils.equals(field, column.getColumnName())
                    || StringUtils.equals(field, column.getJavaField())))
            {
                return column;
            }
        }
        throw new ServiceException("图表字段不存在：" + table.getTableName() + "."
                + StringUtils.defaultString(field));
    }

    private String join(List<String> values, String delimiter)
    {
        StringBuilder result = new StringBuilder();
        for (String value : values)
        {
            if (result.length() > 0)
            {
                result.append(delimiter);
            }
            result.append(value);
        }
        return result.toString();
    }
}
