package com.ruoyi.generator.service.front;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
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.ChartSortConfig;
import org.springframework.stereotype.Service;

@Service
public class ChartDatasetValidator
{
    public static final String DATASET_SCHEMA = "single-table-aggregate-v1";

    private static final Set<String> CHART_TYPES =
            new HashSet<String>(Arrays.asList("metric", "line", "bar", "pie"));
    private static final Set<String> AGGREGATES =
            new HashSet<String>(Arrays.asList("count", "sum", "avg"));
    private static final Set<String> TIME_UNITS =
            new HashSet<String>(Arrays.asList("day", "week", "month"));
    private static final Set<String> OPERATORS =
            new HashSet<String>(Arrays.asList(
                    "eq", "ne", "gt", "gte", "lt", "lte", "between", "in", "is_null", "is_not_null"));
    private static final Set<String> DATE_PRESETS =
            new HashSet<String>(Arrays.asList(
                    "today", "last_7_days", "last_30_days", "this_month", "this_year", "fixed"));
    private static final Set<Integer> SPANS =
            new HashSet<Integer>(Arrays.asList(Integer.valueOf(12), Integer.valueOf(6), Integer.valueOf(4)));
    private static final Pattern ALIAS_PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9_]{0,39}");

    public void validate(BusinessBlockDefinition definition, BusinessBlockInstance instance, List<GenTable> tables)
    {
        if (definition == null || instance == null)
        {
            throw new ServiceException("图表业务块配置不存在");
        }
        String blockName = StringUtils.defaultIfEmpty(instance.getName(), definition.getName());
        ChartDatasetConfig dataset = instance.getDataset();
        if (dataset == null)
        {
            throw error(blockName, "数据集配置缺失");
        }

        String expectedSchema = StringUtils.defaultIfEmpty(definition.getDatasetSchema(), DATASET_SCHEMA);
        if (!expectedSchema.equals(dataset.getSchema()))
        {
            throw error(blockName, "数据集协议必须为 " + expectedSchema);
        }

        GenTable table = requireTable(blockName, dataset.getTable(), tables);
        validateSpan(definition, instance, blockName);

        String chartType = StringUtils.defaultString(definition.getChartType()).toLowerCase();
        if (!CHART_TYPES.contains(chartType))
        {
            throw error(blockName, "未知图表类型：" + chartType);
        }

        validateDimension(blockName, chartType, dataset.getDimension(), table);
        Set<String> aliases = validateMetrics(blockName, chartType, dataset.getMetrics(), table);
        validateFilters(blockName, dataset.getFilters(), table);
        validateDateRange(blockName, dataset.getDateRange(), table);
        validateSort(blockName, dataset.getSort(), aliases);
        validateLimit(blockName, chartType, dataset);
    }

    private void validateSpan(BusinessBlockDefinition definition, BusinessBlockInstance instance, String blockName)
    {
        if (instance.getSpan() == null)
        {
            Integer defaultSpan = definition.getDefaultSpan();
            instance.setSpan(SPANS.contains(defaultSpan) ? defaultSpan : Integer.valueOf(12));
        }
        if (!SPANS.contains(instance.getSpan()))
        {
            throw error(blockName, "宽度只能为 12、6 或 4");
        }
    }

    private void validateDimension(String blockName, String chartType, ChartDimensionConfig dimension, GenTable table)
    {
        if ("metric".equals(chartType))
        {
            return;
        }
        if (dimension == null || StringUtils.isEmpty(dimension.getField()))
        {
            throw error(blockName, "请选择维度字段");
        }
        GenTableColumn column = requireColumn(blockName, table, dimension.getField());
        String timeUnit = StringUtils.defaultString(dimension.getTimeUnit()).toLowerCase();
        if (StringUtils.isEmpty(timeUnit))
        {
            return;
        }
        if (!TIME_UNITS.contains(timeUnit))
        {
            throw error(blockName, "时间粒度只能为 day、week 或 month");
        }
        if (!isTemporal(column))
        {
            throw error(blockName, "字段 " + dimension.getField() + " 不是时间类型，不能设置时间粒度");
        }
    }

    private Set<String> validateMetrics(String blockName, String chartType, List<ChartMetricConfig> metrics,
            GenTable table)
    {
        List<ChartMetricConfig> safeMetrics = metrics == null
                ? Collections.<ChartMetricConfig>emptyList() : metrics;
        int size = safeMetrics.size();
        if (("metric".equals(chartType) || "pie".equals(chartType)) && size != 1)
        {
            throw error(blockName, "指标卡和饼图必须配置 1 个指标");
        }
        if (("line".equals(chartType) || "bar".equals(chartType)) && (size < 1 || size > 3))
        {
            throw error(blockName, "折线图和柱状图必须配置 1 到 3 个指标");
        }

        Set<String> aliases = new HashSet<String>();
        for (ChartMetricConfig metric : safeMetrics)
        {
            if (metric == null)
            {
                throw error(blockName, "指标配置不能为空");
            }
            String aggregate = StringUtils.defaultString(metric.getAggregate()).toLowerCase();
            if (!AGGREGATES.contains(aggregate))
            {
                throw error(blockName, "未知聚合方式：" + aggregate);
            }
            GenTableColumn column = null;
            if (StringUtils.isNotEmpty(metric.getField()))
            {
                column = requireColumn(blockName, table, metric.getField());
            }
            else if (!"count".equals(aggregate))
            {
                throw error(blockName, aggregate.toUpperCase() + " 必须选择指标字段");
            }
            if (("sum".equals(aggregate) || "avg".equals(aggregate)) && !isNumeric(column))
            {
                throw error(blockName, "指标字段 " + metric.getField() + " 不是数值类型，不能使用 "
                        + aggregate.toUpperCase());
            }

            String alias = StringUtils.defaultString(metric.getAlias());
            if (!ALIAS_PATTERN.matcher(alias).matches())
            {
                throw error(blockName, "指标别名必须以字母开头，且只能包含字母、数字和下划线");
            }
            if (!aliases.add(alias))
            {
                throw error(blockName, "指标别名不能重复：" + alias);
            }
        }
        return aliases;
    }

    private void validateFilters(String blockName, List<ChartFilterConfig> filters, GenTable table)
    {
        List<ChartFilterConfig> safeFilters = filters == null
                ? Collections.<ChartFilterConfig>emptyList() : filters;
        if (safeFilters.size() > 10)
        {
            throw error(blockName, "筛选条件不能超过 10 个");
        }
        for (ChartFilterConfig filter : safeFilters)
        {
            if (filter == null)
            {
                throw error(blockName, "筛选条件不能为空");
            }
            requireColumn(blockName, table, filter.getField());
            String operator = StringUtils.defaultString(filter.getOperator()).toLowerCase();
            if (!OPERATORS.contains(operator))
            {
                throw error(blockName, "未知筛选运算符：" + operator);
            }
            validateFilterValue(blockName, operator, filter.getValue());
        }
    }

    private void validateFilterValue(String blockName, String operator, Object value)
    {
        if ("is_null".equals(operator) || "is_not_null".equals(operator))
        {
            return;
        }
        if ("between".equals(operator))
        {
            if (valueCount(value) != 2)
            {
                throw error(blockName, "between 筛选必须提供两个值");
            }
            return;
        }
        if ("in".equals(operator))
        {
            if (valueCount(value) < 1)
            {
                throw error(blockName, "in 筛选至少提供一个值");
            }
            return;
        }
        if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
        {
            throw error(blockName, operator + " 筛选必须提供值");
        }
    }

    private int valueCount(Object value)
    {
        if (value instanceof Collection)
        {
            return ((Collection<?>) value).size();
        }
        if (value != null && value.getClass().isArray())
        {
            return Array.getLength(value);
        }
        return value == null ? 0 : 1;
    }

    private void validateDateRange(String blockName, ChartDateRangeConfig dateRange, GenTable table)
    {
        if (dateRange == null)
        {
            return;
        }
        GenTableColumn column = requireColumn(blockName, table, dateRange.getField());
        if (!isTemporal(column))
        {
            throw error(blockName, "日期范围字段 " + dateRange.getField() + " 不是时间类型");
        }
        String preset = StringUtils.defaultString(dateRange.getPreset()).toLowerCase();
        if (!DATE_PRESETS.contains(preset))
        {
            throw error(blockName, "未知日期范围：" + preset);
        }
        if ("fixed".equals(preset)
                && (StringUtils.isEmpty(dateRange.getStart()) || StringUtils.isEmpty(dateRange.getEnd())))
        {
            throw error(blockName, "固定日期范围必须提供开始和结束日期");
        }
    }

    private void validateSort(String blockName, ChartSortConfig sort, Set<String> aliases)
    {
        if (sort == null)
        {
            return;
        }
        String by = StringUtils.defaultString(sort.getBy());
        if (!"dimension".equals(by) && !aliases.contains(by))
        {
            throw error(blockName, "排序字段必须为 dimension 或已配置的指标别名");
        }
        String order = StringUtils.defaultString(sort.getOrder()).toLowerCase();
        if (!"asc".equals(order) && !"desc".equals(order))
        {
            throw error(blockName, "排序方向只能为 asc 或 desc");
        }
    }

    private void validateLimit(String blockName, String chartType, ChartDatasetConfig dataset)
    {
        if (dataset.getLimit() == null)
        {
            dataset.setLimit(Integer.valueOf(defaultLimit(chartType)));
        }
        if (dataset.getLimit().intValue() < 1 || dataset.getLimit().intValue() > 500)
        {
            throw error(blockName, "分组上限必须在 1 到 500 之间");
        }
    }

    private int defaultLimit(String chartType)
    {
        if ("metric".equals(chartType)) return 1;
        if ("pie".equals(chartType)) return 10;
        if ("bar".equals(chartType)) return 20;
        return 100;
    }

    private GenTable requireTable(String blockName, String tableName, List<GenTable> tables)
    {
        Map<String, GenTable> tableMap = new LinkedHashMap<String, GenTable>();
        for (GenTable table : tables == null ? Collections.<GenTable>emptyList() : tables)
        {
            if (table != null && StringUtils.isNotEmpty(table.getTableName()))
            {
                tableMap.put(table.getTableName(), table);
            }
        }
        if (StringUtils.isEmpty(tableName) || !tableMap.containsKey(tableName))
        {
            throw error(blockName, "数据表不存在：" + StringUtils.defaultString(tableName));
        }
        return tableMap.get(tableName);
    }

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

    private boolean isNumeric(GenTableColumn column)
    {
        return column != null && StringUtils.equalsAnyIgnoreCase(column.getJavaType(),
                "Integer", "Long", "Double", "Float", "Short", "BigDecimal");
    }

    private boolean isTemporal(GenTableColumn column)
    {
        if (column == null)
        {
            return false;
        }
        String columnType = StringUtils.defaultString(column.getColumnType()).toLowerCase();
        return StringUtils.equalsAnyIgnoreCase(column.getJavaType(), "Date", "LocalDate", "LocalDateTime")
                || columnType.contains("date")
                || columnType.contains("time")
                || columnType.contains("timestamp")
                || columnType.contains("year");
    }

    private ServiceException error(String blockName, String message)
    {
        return new ServiceException("图表“" + StringUtils.defaultString(blockName) + "”" + message);
    }
}
