package com.ruoyi.generator.service.front;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collections;
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.ChartMetricConfig;
import com.ruoyi.generator.domain.front.dto.block.PageBusinessBlockLayout;

public class BusinessBlockRegistryServiceTest
{
    private final BusinessBlockRegistryService service = new BusinessBlockRegistryService();

    @Test
    public void listDefinitionsReturnsBundledCartBlock()
    {
        BusinessBlockDefinition definition = service.findDefinition("cart");

        assertNotNull(definition);
        assertEquals("cart", definition.getCode());
        assertTrue(definition.getConfigs().size() >= 6);
    }

    @Test
    public void listDefinitionsReturnsBundledBusinessBlocks()
    {
        assertEquals("notice", service.findDefinition("notice").getCode());
        assertEquals("carousel", service.findDefinition("carousel").getCode());
        assertEquals("order", service.findDefinition("order").getCode());
        assertEquals("master_detail", service.findDefinition("master_detail").getCode());
    }

    @Test
    public void bundledDefinitionsDeclarePageOrEmbeddedPlacement()
    {
        assertEquals("embedded", service.findDefinition("notice").getPlacement());
        assertEquals("embedded", service.findDefinition("carousel").getPlacement());
        assertEquals("page", service.findDefinition("cart").getPlacement());
        assertEquals("page", service.findDefinition("order").getPlacement());
        assertEquals("page", service.findDefinition("master_detail").getPlacement());
    }

    @Test
    public void legacyDefinitionsDefaultToFrontendBusinessBlocks()
    {
        BusinessBlockDefinition definition = service.findDefinition("cart");

        assertEquals("business", definition.getKind());
        assertEquals(Collections.singletonList("frontend"), definition.getSurfaces());
        assertEquals(Collections.singletonList("page"), definition.getPlacements());
    }

    @Test
    public void listDefinitionsFiltersBySurface()
    {
        for (BusinessBlockDefinition definition : service.listDefinitions("frontend"))
        {
            assertTrue(definition.getSurfaces().contains("frontend"));
        }
        for (BusinessBlockDefinition definition : service.listDefinitions("admin"))
        {
            assertTrue(definition.getSurfaces().contains("admin"));
        }
    }

    @Test
    public void adminSurfaceIncludesFourChartBlocks()
    {
        java.util.List<BusinessBlockDefinition> definitions = service.listDefinitions("admin");

        assertTrue(hasCode(definitions, "admin_metric_chart"));
        assertTrue(hasCode(definitions, "admin_line_chart"));
        assertTrue(hasCode(definitions, "admin_bar_chart"));
        assertTrue(hasCode(definitions, "admin_pie_chart"));
        assertEquals(4, countKind(definitions, "chart"));
    }

    @Test
    public void frontendSurfaceExcludesAdminCharts()
    {
        assertEquals(0, countKind(service.listDefinitions("frontend"), "chart"));
    }

    @Test
    public void chartValidationRejectsAdminChartOnFrontendPage()
    {
        ServiceException exception = expectServiceException(() ->
                service.validateInstanceConfig(service.findDefinition("admin_line_chart"),
                        validLineChartInstance(), chartTables(), "frontend"));

        assertTrue(exception.getMessage().contains("后台"));
    }

    @Test
    public void parseLayoutReadsBusinessBlockInstances()
    {
        PageBusinessBlockLayout layout = service.parseLayout("{\"canvas\":\"business-blocks-v1\",\"blocks\":[{\"id\":\"cart_001\",\"blockCode\":\"cart\",\"config\":{\"cartTable\":\"shop_cart\"}}]}");

        assertEquals("business-blocks-v1", layout.getCanvas());
        assertEquals(1, layout.getBlocks().size());
        assertEquals("cart_001", layout.getBlocks().get(0).getId());
        assertEquals("cart", layout.getBlocks().get(0).getBlockCode());
    }

    @Test
    public void parseLayoutReadsChartDatasetAndSpan()
    {
        PageBusinessBlockLayout layout = service.parseLayout(
                "{\"canvas\":\"business-blocks-v1\",\"blocks\":[{"
                + "\"id\":\"line_001\",\"blockCode\":\"admin_line_chart\",\"span\":6,"
                + "\"dataset\":{\"schema\":\"single-table-aggregate-v1\",\"table\":\"shop_order\","
                + "\"dimension\":{\"field\":\"create_time\",\"timeUnit\":\"day\"},"
                + "\"metrics\":[{\"field\":\"amount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\"}]},"
                + "\"display\":{\"showLegend\":true}}]}");

        BusinessBlockInstance instance = layout.getBlocks().get(0);
        assertEquals(Integer.valueOf(6), instance.getSpan());
        assertEquals("shop_order", instance.getDataset().getTable());
        assertEquals("amount", instance.getDataset().getMetrics().get(0).getField());
        assertEquals(Boolean.TRUE, instance.getDisplay().get("showLegend"));
    }

    @Test
    public void validateInstanceConfigRejectsMissingRequiredConfig()
    {
        BusinessBlockDefinition definition = service.findDefinition("cart");
        BusinessBlockInstance instance = new BusinessBlockInstance();
        instance.setBlockCode("cart");
        instance.setConfig(Collections.singletonMap("productTable", "shop_product"));

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.validateInstanceConfig(definition, instance, tables());
            }
        });

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

    @Test
    public void validateInstanceConfigRejectsUnknownTable()
    {
        BusinessBlockDefinition definition = service.findDefinition("cart");
        BusinessBlockInstance instance = validCartInstance();
        instance.getConfig().put("cartTable", "missing_cart");

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.validateInstanceConfig(definition, instance, tables());
            }
        });

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

    @Test
    public void validateInstanceConfigRejectsUnknownField()
    {
        BusinessBlockDefinition definition = service.findDefinition("cart");
        BusinessBlockInstance instance = validCartInstance();
        instance.getConfig().put("productName", "missing_name");

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.validateInstanceConfig(definition, instance, tables());
            }
        });

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

    @Test
    public void validateLayoutIgnoresNonBusinessBlockLayout()
    {
        service.validateLayout("{\"canvas\":\"frontend-list-v1\",\"regions\":[]}", tables());
    }

    @Test
    public void validateEmbeddedBlocksAcceptsEmbeddedDefinitions()
    {
        service.validateEmbeddedBlocks("{\"canvas\":\"frontend-list-v1\",\"regions\":[],\"embeddedBlocks\":{\"top\":["
                + "{\"id\":\"notice_001\",\"blockCode\":\"notice\",\"config\":{"
                + "\"noticeTable\":\"sys_notice\",\"title\":\"noticeTitle\",\"content\":\"noticeContent\"}}"
                + "]}}", contentTables());
    }

    @Test
    public void validateEmbeddedBlocksRejectsPageOnlyDefinitions()
    {
        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.validateEmbeddedBlocks("{\"canvas\":\"frontend-list-v1\",\"regions\":[],\"embeddedBlocks\":{\"top\":["
                        + "{\"id\":\"cart_001\",\"blockCode\":\"cart\",\"config\":{"
                        + "\"cartTable\":\"shop_cart\",\"productTable\":\"shop_product\",\"cartProductId\":\"product_id\","
                        + "\"productId\":\"id\",\"productName\":\"name\",\"price\":\"price\",\"quantity\":\"quantity\","
                        + "\"userId\":\"user_id\"}}"
                        + "]}}", contentTables());
            }
        });

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

    private BusinessBlockInstance validCartInstance()
    {
        BusinessBlockInstance instance = new BusinessBlockInstance();
        instance.setId("cart_001");
        instance.setBlockCode("cart");
        java.util.Map<String, String> config = new java.util.LinkedHashMap<String, String>();
        config.put("cartTable", "shop_cart");
        config.put("productTable", "shop_product");
        config.put("cartProductId", "product_id");
        config.put("productId", "id");
        config.put("productName", "name");
        config.put("price", "price");
        config.put("quantity", "quantity");
        config.put("userId", "user_id");
        instance.setConfig(config);
        return instance;
    }

    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(12));

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

        ChartMetricConfig metric = new ChartMetricConfig();
        metric.setField("amount");
        metric.setAggregate("sum");
        metric.setAlias("salesAmount");
        metric.setLabel("销售额");

        ChartDatasetConfig dataset = new ChartDatasetConfig();
        dataset.setSchema("single-table-aggregate-v1");
        dataset.setTable("shop_order");
        dataset.setDimension(dimension);
        dataset.setMetrics(Collections.singletonList(metric));
        dataset.setLimit(Integer.valueOf(100));
        instance.setDataset(dataset);
        return instance;
    }

    private java.util.List<GenTable> tables()
    {
        return Arrays.asList(
                table("shop_cart", column("id", "id"), column("product_id", "productId"), column("quantity", "quantity"), column("user_id", "userId")),
                table("shop_product", column("id", "id"), column("name", "name"), column("price", "price"), column("stock", "stock")));
    }

    private java.util.List<GenTable> contentTables()
    {
        return Arrays.asList(
                table("sys_notice", column("id", "id"), column("notice_title", "noticeTitle"),
                        column("notice_content", "noticeContent")),
                table("shop_cart", column("id", "id"), column("product_id", "productId"),
                        column("quantity", "quantity"), column("user_id", "userId")),
                table("shop_product", column("id", "id"), column("name", "name"),
                        column("price", "price")));
    }

    private java.util.List<GenTable> chartTables()
    {
        GenTable order = table("shop_order",
                typedColumn("id", "id", "Long", "bigint"),
                typedColumn("amount", "amount", "BigDecimal", "decimal(10,2)"),
                typedColumn("create_time", "createTime", "Date", "datetime"));
        return Collections.singletonList(order);
    }

    private GenTable table(String tableName, GenTableColumn... columns)
    {
        GenTable table = new GenTable();
        table.setTableName(tableName);
        table.setColumns(Arrays.asList(columns));
        return table;
    }

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

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

    private boolean hasCode(java.util.List<BusinessBlockDefinition> definitions, String code)
    {
        for (BusinessBlockDefinition definition : definitions)
        {
            if (code.equals(definition.getCode()))
            {
                return true;
            }
        }
        return false;
    }

    private int countKind(java.util.List<BusinessBlockDefinition> definitions, String kind)
    {
        int count = 0;
        for (BusinessBlockDefinition definition : definitions)
        {
            if (kind.equals(definition.getKind()))
            {
                count++;
            }
        }
        return count;
    }

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

    private interface ThrowingRunnable
    {
        void run();
    }
}
