package com.ruoyi.generator.service.front;

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import com.ruoyi.common.exception.ServiceException;
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.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;

public class ChartDatasetValidatorTest
{
    private final ChartDatasetValidator validator = new ChartDatasetValidator();

    @Test
    public void chartValidationAcceptsSingleTableTimeSeries()
    {
        validator.validate(lineChartDefinition(), validLineChartInstance(), chartTables());
    }

    @Test
    public void chartValidationRejectsSumOnTextField()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.getDataset().getMetrics().get(0).setField("status");

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("SUM"));
        assertTrue(exception.getMessage().contains("status"));
    }

    @Test
    public void chartValidationRejectsInvalidSpan()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.setSpan(Integer.valueOf(5));

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("宽度"));
    }

    @Test
    public void chartValidationRejectsMoreThanThreeMetrics()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.getDataset().setMetrics(Arrays.asList(
                metric("amount", "sum", "metric1"),
                metric("amount", "avg", "metric2"),
                metric("id", "count", "metric3"),
                metric("amount", "sum", "metric4")));

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("1 到 3"));
    }

    @Test
    public void chartValidationRejectsMoreThanTenFilters()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        List<ChartFilterConfig> filters = new ArrayList<ChartFilterConfig>();
        for (int i = 0; i < 11; i++)
        {
            filters.add(filter("status", "eq", "PAID"));
        }
        instance.getDataset().setFilters(filters);

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("10"));
    }

    @Test
    public void chartValidationRejectsInvalidTimeUnit()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.getDataset().getDimension().setTimeUnit("quarter");

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("时间粒度"));
    }

    @Test
    public void chartValidationRejectsUnknownFilterOperator()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.getDataset().setFilters(Collections.singletonList(filter("status", "contains_sql", "PAID")));

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("运算符"));
    }

    @Test
    public void chartValidationRejectsDuplicateMetricAliases()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.getDataset().setMetrics(Arrays.asList(
                metric("amount", "sum", "metric1"),
                metric("amount", "avg", "metric1")));

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("别名"));
    }

    @Test
    public void chartValidationRejectsLimitAboveFiveHundred()
    {
        BusinessBlockInstance instance = validLineChartInstance();
        instance.getDataset().setLimit(Integer.valueOf(501));

        ServiceException exception = expectServiceException(() ->
                validator.validate(lineChartDefinition(), instance, chartTables()));

        assertTrue(exception.getMessage().contains("500"));
    }

    private BusinessBlockDefinition lineChartDefinition()
    {
        BusinessBlockDefinition definition = new BusinessBlockDefinition();
        definition.setCode("admin_line_chart");
        definition.setName("折线图");
        definition.setKind("chart");
        definition.setChartType("line");
        definition.setDatasetSchema("single-table-aggregate-v1");
        definition.setSurfaces(Collections.singletonList("admin"));
        definition.setPlacements(Arrays.asList("embedded", "page"));
        definition.setDefaultSpan(Integer.valueOf(12));
        return definition;
    }

    private BusinessBlockInstance validLineChartInstance()
    {
        BusinessBlockInstance instance = new BusinessBlockInstance();
        instance.setId("admin_line_chart_001");
        instance.setBlockCode("admin_line_chart");
        instance.setName("近 30 天销售趋势");
        instance.setSpan(Integer.valueOf(6));

        ChartDimensionConfig dimension = new ChartDimensionConfig();
        dimension.setField("create_time");
        dimension.setTimeUnit("day");

        ChartDatasetConfig dataset = new ChartDatasetConfig();
        dataset.setSchema("single-table-aggregate-v1");
        dataset.setTable("shop_order");
        dataset.setDimension(dimension);
        dataset.setMetrics(Collections.singletonList(metric("amount", "sum", "salesAmount")));
        dataset.setFilters(Collections.singletonList(filter("status", "eq", "PAID")));
        ChartSortConfig sort = new ChartSortConfig();
        sort.setBy("dimension");
        sort.setOrder("asc");
        dataset.setSort(sort);
        dataset.setLimit(Integer.valueOf(100));
        instance.setDataset(dataset);
        return instance;
    }

    private ChartMetricConfig metric(String field, String aggregate, String alias)
    {
        ChartMetricConfig metric = new ChartMetricConfig();
        metric.setField(field);
        metric.setAggregate(aggregate);
        metric.setAlias(alias);
        metric.setLabel(alias);
        return metric;
    }

    private ChartFilterConfig filter(String field, String operator, Object value)
    {
        ChartFilterConfig filter = new ChartFilterConfig();
        filter.setField(field);
        filter.setOperator(operator);
        filter.setValue(value);
        return filter;
    }

    private List<GenTable> chartTables()
    {
        GenTable table = new GenTable();
        table.setTableName("shop_order");
        table.setColumns(Arrays.asList(
                typedColumn("id", "id", "Long", "bigint"),
                typedColumn("amount", "amount", "BigDecimal", "decimal(10,2)"),
                typedColumn("status", "status", "String", "varchar(32)"),
                typedColumn("create_time", "createTime", "Date", "datetime")));
        return Collections.singletonList(table);
    }

    private GenTableColumn typedColumn(String columnName, String javaField, String javaType, String columnType)
    {
        GenTableColumn column = new GenTableColumn();
        column.setColumnName(columnName);
        column.setJavaField(javaField);
        column.setJavaType(javaType);
        column.setColumnType(columnType);
        return column;
    }

    private ServiceException expectServiceException(ThrowingRunnable runnable)
    {
        try
        {
            runnable.run();
        }
        catch (ServiceException e)
        {
            return e;
        }
        throw new AssertionError("Expected ServiceException");
    }

    private interface ThrowingRunnable
    {
        void run();
    }
}
