package com.ruoyi.generator.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
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.ChartQueryRenderModel;
import com.ruoyi.generator.domain.front.dto.block.ChartSortConfig;

public class ChartBlockRenderSupportTest
{
    private final ChartBlockRenderSupport support = new ChartBlockRenderSupport();

    @Test
    public void rendersBoundSingleTableAggregation()
    {
        ChartQueryRenderModel model = support.build(orderTable(), definition("line"), lineInstance());

        assertEquals("shop_order", model.getTableName());
        assertEquals("DATE_FORMAT(t.create_time, '%Y-%m-%d')", model.getDimensionSql());
        assertEquals("DATE_FORMAT(t.create_time, '%Y-%m-%d')", model.getGroupBySql());
        assertTrue(model.getMetricSelectSql().contains("SUM(t.amount) AS salesAmount"));
        assertTrue(model.getWhereSql().contains("t.status = #{filter0}"));
        assertEquals("filter0", model.getParameters().get(0).getName());
        assertEquals("PAID", model.getParameters().get(0).getValue());
        assertEquals("dimensionKey ASC", model.getOrderBySql());
        assertEquals(Integer.valueOf(100), model.getLimit());
        assertFalse(model.isMetricOnly());
    }

    @Test
    public void neverCopiesFilterValuesIntoSql()
    {
        BusinessBlockInstance instance = lineInstance();
        instance.getDataset().getFilters().get(0).setValue("PAID' OR 1=1 --");

        ChartQueryRenderModel model = support.build(orderTable(), definition("line"), instance);

        assertTrue(model.getWhereSql().contains("#{filter0}"));
        assertFalse(model.getWhereSql().contains("OR 1=1"));
    }

    @Test
    public void rendersFilterValuesAsSafeJavaLiteralsForGeneratedServices()
    {
        BusinessBlockInstance instance = lineInstance();
        instance.getDataset().getFilters().get(0).setValue("PAID\" + run();\n//");

        ChartQueryRenderModel model = support.build(orderTable(), definition("line"), instance);

        assertEquals("\"PAID\\\" + run();\\n//\"", model.getParameters().get(0).getJavaLiteral());
    }

    @Test
    public void formatsDayWeekAndMonthDimensionsFromResolvedColumns()
    {
        assertDimension("day", "DATE_FORMAT(t.create_time, '%Y-%m-%d')");
        assertDimension("week", "DATE_FORMAT(t.create_time, '%x-W%v')");
        assertDimension("month", "DATE_FORMAT(t.create_time, '%Y-%m')");
        assertDimension(null, "t.create_time");
    }

    @Test
    public void rendersCountSumAndAverageMetrics()
    {
        BusinessBlockInstance instance = lineInstance();
        instance.getDataset().setMetrics(Arrays.asList(
                metric(null, "count", "orderCount"),
                metric("amount", "sum", "salesAmount"),
                metric("amount", "avg", "averageAmount")));

        ChartQueryRenderModel model = support.build(orderTable(), definition("line"), instance);

        assertEquals("COUNT(*) AS orderCount, SUM(t.amount) AS salesAmount, AVG(t.amount) AS averageAmount",
                model.getMetricSelectSql());
        assertEquals(3, model.getMetrics().size());
    }

    @Test
    public void rendersBetweenInNullAndDateRangeConditionsAsBoundParameters()
    {
        BusinessBlockInstance instance = lineInstance();
        ChartDateRangeConfig dateRange = new ChartDateRangeConfig();
        dateRange.setField("createTime");
        dateRange.setPreset("last_30_days");
        instance.getDataset().setDateRange(dateRange);
        instance.getDataset().setFilters(Arrays.asList(
                filter("amount", "between", Arrays.asList(10, 20)),
                filter("status", "in", new String[] { "PAID", "SHIPPED" }),
                filter("createTime", "is_not_null", null),
                filter("id", "is_null", null)));

        ChartQueryRenderModel model = support.build(orderTable(), definition("line"), instance);

        assertTrue(model.getWhereSql().contains("t.amount BETWEEN #{filter0} AND #{filter1}"));
        assertTrue(model.getWhereSql().contains("t.status IN (#{filter2}, #{filter3})"));
        assertTrue(model.getWhereSql().contains("t.create_time IS NOT NULL"));
        assertTrue(model.getWhereSql().contains("t.id IS NULL"));
        assertTrue(model.getWhereSql().contains("t.create_time >= #{dateStart}"));
        assertTrue(model.getWhereSql().contains("t.create_time < #{dateEnd}"));
        assertEquals(Arrays.asList(10, 20, "PAID", "SHIPPED"), Arrays.asList(
                model.getParameters().get(0).getValue(),
                model.getParameters().get(1).getValue(),
                model.getParameters().get(2).getValue(),
                model.getParameters().get(3).getValue()));
        assertSame(dateRange, model.getDateRange());
    }

    @Test
    public void rendersMetricOnlyQueryWithoutDimensionOrGrouping()
    {
        BusinessBlockInstance instance = lineInstance();
        instance.getDataset().setDimension(null);
        instance.getDataset().setMetrics(Collections.singletonList(metric("id", "count", "orderCount")));
        instance.getDataset().setSort(null);
        instance.getDataset().setLimit(Integer.valueOf(1));

        ChartQueryRenderModel model = support.build(orderTable(), definition("metric"), instance);

        assertTrue(model.isMetricOnly());
        assertEquals("", model.getDimensionSql());
        assertEquals("", model.getGroupBySql());
        assertEquals("", model.getOrderBySql());
        assertEquals("COUNT(t.id) AS orderCount", model.getMetricSelectSql());
        assertEquals(Integer.valueOf(1), model.getLimit());
    }

    @Test
    public void rendersMetricAliasSortAndConfiguredChartLimits()
    {
        BusinessBlockInstance instance = lineInstance();
        instance.getDataset().getSort().setBy("salesAmount");
        instance.getDataset().getSort().setOrder("desc");
        instance.getDataset().setLimit(Integer.valueOf(20));

        ChartQueryRenderModel model = support.build(orderTable(), definition("bar"), instance);

        assertEquals("salesAmount DESC", model.getOrderBySql());
        assertEquals(Integer.valueOf(20), model.getLimit());

        instance.getDataset().setLimit(Integer.valueOf(10));
        assertEquals(Integer.valueOf(10),
                support.build(orderTable(), definition("pie"), instance).getLimit());
    }

    private void assertDimension(String timeUnit, String expected)
    {
        BusinessBlockInstance instance = lineInstance();
        instance.getDataset().getDimension().setField("createTime");
        instance.getDataset().getDimension().setTimeUnit(timeUnit);
        assertEquals(expected, support.build(orderTable(), definition("line"), instance).getDimensionSql());
    }

    private BusinessBlockDefinition definition(String chartType)
    {
        BusinessBlockDefinition definition = new BusinessBlockDefinition();
        definition.setKind("chart");
        definition.setChartType(chartType);
        return definition;
    }

    private BusinessBlockInstance lineInstance()
    {
        ChartDimensionConfig dimension = new ChartDimensionConfig();
        dimension.setField("create_time");
        dimension.setTimeUnit("day");

        ChartSortConfig sort = new ChartSortConfig();
        sort.setBy("dimension");
        sort.setOrder("asc");

        ChartDatasetConfig dataset = new ChartDatasetConfig();
        dataset.setTable("shop_order");
        dataset.setDimension(dimension);
        dataset.setMetrics(Collections.singletonList(metric("amount", "sum", "salesAmount")));
        dataset.setFilters(Collections.singletonList(filter("status", "eq", "PAID")));
        dataset.setSort(sort);
        dataset.setLimit(Integer.valueOf(100));

        BusinessBlockInstance instance = new BusinessBlockInstance();
        instance.setId("admin_line_chart_001");
        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 GenTable orderTable()
    {
        GenTable table = new GenTable();
        table.setTableName("shop_order");
        table.setColumns(Arrays.asList(
                column("id", "id"),
                column("amount", "amount"),
                column("status", "status"),
                column("create_time", "createTime")));
        return table;
    }

    private GenTableColumn column(String columnName, String javaField)
    {
        GenTableColumn column = new GenTableColumn();
        column.setColumnName(columnName);
        column.setJavaField(javaField);
        return column;
    }
}
