package com.ruoyi.generator.service;

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

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.velocity.VelocityContext;
import org.junit.Before;
import org.junit.Test;
import com.ruoyi.generator.domain.GenProject;
import com.ruoyi.generator.domain.GenTable;
import com.ruoyi.generator.domain.GenTableColumn;
import com.ruoyi.generator.domain.GeneratedBusinessBlockFile;
import com.ruoyi.generator.domain.front.FrontProjectPageDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
import com.ruoyi.generator.domain.front.dto.block.ChartQueryRenderModel;
import com.ruoyi.generator.service.front.BusinessBlockRegistryService;

public class BusinessBlockGenerationServiceTest
{
    private BusinessBlockGenerationService service;

    @Before
    public void setUp() throws Exception
    {
        service = new BusinessBlockGenerationService();
        setField("businessBlockRegistryService", new BusinessBlockRegistryService());
    }

    @Test
    public void listFilesCreatesFrontendPageAndBlockFiles()
    {
        List<GeneratedBusinessBlockFile> files = service.listFiles(project(), "frontend");

        assertTrue(containsPath(files, "src/views/pages/cart_page/index.vue"));
        assertTrue(containsPath(files, "src/views/pages/cart_page/blocks/cart_001/CartBlock.vue"));
        assertTrue(containsPath(files, "src/api/business-blocks/cart_001.js"));
        assertFalse(containsPath(files, "src/utils/chartRuntime.js"));
    }

    @Test
    public void listFilesCreatesBackendFilesWithPackagePath()
    {
        List<GeneratedBusinessBlockFile> files = service.listFiles(project(), "backend");

        assertTrue(containsPath(files, "src/main/java/com/example/demo/controller/block/Cart001BlockController.java"));
        assertTrue(containsPath(files, "src/main/java/com/example/demo/service/block/ICart001BlockService.java"));
        assertTrue(containsPath(files, "src/main/java/com/example/demo/service/block/impl/Cart001BlockServiceImpl.java"));
        assertTrue(containsPath(files, "src/main/java/com/example/demo/mapper/block/Cart001BlockMapper.java"));
        assertTrue(containsPath(files, "src/main/resources/mapper/block/Cart001BlockMapper.xml"));
    }

    @Test
    public void renderFileUsesBusinessBlockContext()
    {
        GenProject project = project();
        GeneratedBusinessBlockFile pageFile = findPath(service.listFiles(project, "frontend"),
                "src/views/pages/cart_page/index.vue");
        GeneratedBusinessBlockFile blockFile = findPath(service.listFiles(project, "frontend"),
                "src/views/pages/cart_page/blocks/cart_001/CartBlock.vue");
        GeneratedBusinessBlockFile apiFile = findPath(service.listFiles(project, "frontend"),
                "src/api/business-blocks/cart_001.js");
        GeneratedBusinessBlockFile controllerFile = findPath(service.listFiles(project, "backend"),
                "src/main/java/com/example/demo/controller/block/Cart001BlockController.java");

        String pageContent = service.renderFile(project, pageFile);
        String blockContent = service.renderFile(project, blockFile);
        String apiContent = service.renderFile(project, apiFile);
        String controllerContent = service.renderFile(project, controllerFile);

        assertTrue(pageContent.contains("import Cart001Block from \"./blocks/cart_001/CartBlock.vue\""));
        assertTrue(pageContent.contains("<Cart001Block />"));
        assertTrue(blockContent.contains("listCart001BlockItems"));
        assertTrue(blockContent.contains("@/api/business-blocks/cart_001"));
        assertTrue(apiContent.contains("import request from \"@/api/request\""));
        assertTrue(apiContent.contains("url: \"/business-blocks/cart_001/\" + id + \"/quantity\""));
        assertTrue(apiContent.contains("url: \"/business-blocks/cart_001/\" + id"));
        assertTrue(controllerContent.contains("class Cart001BlockController"));
        assertTrue(controllerContent.contains("@RequestMapping(\"/business-blocks/cart_001\")"));
    }

    @Test
    public void renderFileConvertsConfiguredJavaFieldsToSqlColumns()
    {
        GenProject project = project();
        GeneratedBusinessBlockFile mapperFile = findPath(service.listFiles(project, "backend"),
                "src/main/resources/mapper/block/Cart001BlockMapper.xml");

        String mapperContent = service.renderFile(project, mapperFile);

        assertTrue(mapperContent.contains("left join shop_product p on c.product_id = p.id"));
        assertTrue(mapperContent.contains("where c.user_id = #{userId}"));
        assertTrue(mapperContent.contains("set quantity = #{quantity}"));
    }

    @Test
    public void listFilesCreatesBundledBusinessBlockFiles()
    {
        List<GeneratedBusinessBlockFile> files = service.listFiles(businessBlocksProject(), "frontend");

        assertTrue(containsPath(files, "src/views/pages/business_page/blocks/notice_001/NoticeBlock.vue"));
        assertTrue(containsPath(files, "src/views/pages/business_page/blocks/carousel_001/CarouselBlock.vue"));
        assertTrue(containsPath(files, "src/views/pages/business_page/blocks/order_001/OrderBlock.vue"));
        assertTrue(containsPath(files, "src/views/pages/business_page/blocks/master_detail_001/MasterDetailBlock.vue"));
        assertTrue(containsPath(files, "src/api/business-blocks/notice_001.js"));
        assertTrue(containsPath(files, "src/api/business-blocks/carousel_001.js"));
        assertTrue(containsPath(files, "src/api/business-blocks/order_001.js"));
        assertTrue(containsPath(files, "src/api/business-blocks/master_detail_001.js"));
    }

    @Test
    public void renderFileSupportsBundledBusinessBlocks()
    {
        GenProject project = businessBlocksProject();
        String noticeMapper = service.renderFile(project, findPath(service.listFiles(project, "backend"),
                "src/main/resources/mapper/block/Notice001BlockMapper.xml"));
        String carouselMapper = service.renderFile(project, findPath(service.listFiles(project, "backend"),
                "src/main/resources/mapper/block/Carousel001BlockMapper.xml"));
        String carouselComponent = service.renderFile(project, findPath(service.listFiles(project, "frontend"),
                "src/views/pages/business_page/blocks/carousel_001/CarouselBlock.vue"));
        String orderMapper = service.renderFile(project, findPath(service.listFiles(project, "backend"),
                "src/main/resources/mapper/block/Order001BlockMapper.xml"));
        String orderComponent = service.renderFile(project, findPath(service.listFiles(project, "frontend"),
                "src/views/pages/business_page/blocks/order_001/OrderBlock.vue"));
        String masterDetailMapper = service.renderFile(project, findPath(service.listFiles(project, "backend"),
                "src/main/resources/mapper/block/MasterDetail001BlockMapper.xml"));

        assertTrue(noticeMapper.contains("from sys_notice n"));
        assertTrue(noticeMapper.contains("n.notice_title as title"));
        assertTrue(carouselMapper.contains("(b.status = '1' or b.status = 1)"));
        assertTrue(carouselComponent.contains("listCarousel001BlockCarouselItems"));
        assertTrue(carouselComponent.contains(":src=\"resolveResourceUrl(item.image)\""));
        assertTrue(carouselComponent.contains("return \"/api\" + url"));
        assertTrue(orderMapper.contains("insert into shop_order"));
        assertTrue(orderMapper.contains("left join shop_product p on i.product_id = p.id"));
        assertTrue(orderComponent.contains("this.$message.success(\"订单已提交\")"));
        assertTrue(masterDetailMapper.contains("from library_book m"));
        assertTrue(masterDetailMapper.contains("where d.book_id = #{masterId}"));
    }

    @Test
    public void renderNoticeMapperUsesPublishedStatusValueFromStatusComment()
    {
        GenProject project = businessBlocksProject();
        findColumn(project, "sys_notice", "status").setColumnComment("状态（1发布 0草稿/下架）");

        String noticeMapper = service.renderFile(project, findPath(service.listFiles(project, "backend"),
                "src/main/resources/mapper/block/Notice001BlockMapper.xml"));

        assertTrue(noticeMapper.contains("where (n.status = '1' or n.status = 1)"));
        assertFalse(noticeMapper.contains("where (n.status = '0' or n.status = 0)"));
    }

    @Test
    public void renderedBackendBusinessBlockFilesDoNotDependOnRuoYiRuntime()
    {
        GenProject project = businessBlocksProject();
        List<GeneratedBusinessBlockFile> files = service.listFiles(project, "backend");

        for (GeneratedBusinessBlockFile file : files)
        {
            String content = service.renderFile(project, file);

            assertFalse(file.getOutputPath(), content.contains("com.ruoyi.common"));
        }
    }

    @Test
    public void renderFileRendersEveryBundledBusinessBlockFile()
    {
        GenProject project = businessBlocksProject();
        assertRenderedFiles(project, service.listFiles(project, "frontend"));
        assertRenderedFiles(project, service.listFiles(project, "backend"));
    }

    @Test
    public void listFilesCreatesEmbeddedBlockFilesWithNormalPage()
    {
        List<GeneratedBusinessBlockFile> files = service.listFiles(embeddedBlocksProject(), "frontend");

        assertTrue(containsPath(files, "src/views/pages/book_catalog/index.vue"));
        assertTrue(containsPath(files, "src/views/pages/book_catalog/blocks/notice_001/NoticeBlock.vue"));
        assertTrue(containsPath(files, "src/views/pages/book_catalog/blocks/carousel_001/CarouselBlock.vue"));
        assertTrue(containsPath(files, "src/api/business-blocks/notice_001.js"));
        assertTrue(containsPath(files, "src/api/business-blocks/carousel_001.js"));
    }

    @Test
    public void renderFileUsesExactNormalPageDesignContext()
    {
        GenProject project = embeddedBlocksProject();
        GeneratedBusinessBlockFile pageFile = findPath(service.listFiles(project, "frontend"),
                "src/views/pages/book_catalog/index.vue");

        String pageContent = service.renderFile(project, pageFile);

        assertTrue(pageContent.contains("portal-page"));
        assertTrue(pageContent.contains("import Notice001Block from \"@/views/pages/book_catalog/blocks/notice_001/NoticeBlock.vue\""));
        assertTrue(pageContent.contains("embedded-business-block__item--span-12"));
        assertTrue(pageContent.contains("<Notice001Block />"));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void putEmbeddedBlockVelocityContextExposesTopAndBottomBlockImports()
    {
        GenProject project = embeddedBlocksProject();
        VelocityContext context = new VelocityContext();

        service.putEmbeddedBlockVelocityContext(context, project, project.getPageDesigns().get(0));

        List<Map<String, Object>> topBlocks = (List<Map<String, Object>>) context.get("pageDesignTopBusinessBlocks");
        List<Map<String, Object>> bottomBlocks = (List<Map<String, Object>>) context.get("pageDesignBottomBusinessBlocks");

        assertEquals(1, topBlocks.size());
        assertEquals(1, bottomBlocks.size());
        assertEquals("Notice001Block", topBlocks.get(0).get("componentName"));
        assertEquals("@/views/pages/book_catalog/blocks/notice_001/NoticeBlock.vue",
                topBlocks.get(0).get("relativeImportPath"));
        assertEquals("Carousel001Block", bottomBlocks.get(0).get("componentName"));
        assertEquals(Boolean.TRUE, context.get("hasPageDesignEmbeddedBusinessBlocks"));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void pageDesignBusinessActionsIncludeRenderMetadata() throws Exception
    {
        GenProject project = new GenProject();
        GenTable book = table(301L, "library_book", "LibraryBook");
        GenTableColumn dueDate = column("due_date", "dueDate", "0");
        dueDate.setColumnComment("Due date");
        dueDate.setHtmlType("datetime");
        book.setColumns(Arrays.asList(column("id", "id", "1"), column("user_id", "userId", "0"), dueDate));
        project.setTables(Arrays.asList(book));

        BusinessActionDesign action = new BusinessActionDesign();
        action.setCode("borrow_book");
        action.setName("Borrow");
        action.setOwnerTable("library_book");
        action.setRequestFields(Arrays.asList("id", "user_id", "due_date"));
        BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
        Map<String, String> values = new LinkedHashMap<String, String>();
        values.put("user_id", "${current_user.id}");
        effect.setValues(values);
        action.setEffects(Arrays.asList(effect));
        project.setBusinessActions(Arrays.asList(action));

        VelocityContext context = new VelocityContext();
        context.put("currentPageDesign", new FrontProjectPageDesign());
        context.put("pageDesignToolbarActionCodes", Arrays.asList("business:borrow_book"));
        context.put("pageDesignRowActionCodes", Arrays.asList());

        Method method = BusinessBlockGenerationService.class.getDeclaredMethod(
                "putPageDesignBusinessActions", VelocityContext.class, GenProject.class);
        method.setAccessible(true);
        method.invoke(service, context, project);

        List<BusinessActionDesign> actions =
                (List<BusinessActionDesign>) context.get("pageDesignBusinessActions");
        assertEquals(Arrays.asList("user_id"), actions.get(0).getCurrentUserFields());
        assertEquals("due_date", actions.get(0).getInputFields().get(1).getField());
        assertEquals("Due date", actions.get(0).getInputFields().get(1).getLabel());
        assertEquals("datetime", actions.get(0).getInputFields().get(1).getHtmlType());
    }

    @Test
    public void findFileMatchesCategoryOrPath()
    {
        GenProject project = project();
        GeneratedBusinessBlockFile file = findPath(service.listFiles(project, "frontend"),
                "src/api/business-blocks/cart_001.js");

        assertEquals(file.getOutputPath(), service.findFile(project, "frontend", file.getCategory()).getOutputPath());
        assertEquals(file.getCategory(), service.findFile(project, "frontend", file.getOutputPath()).getCategory());
    }

    @Test
    public void chartBlockUsesSharedTemplatesAndAdminApiContext() throws Exception
    {
        GenProject project = chartProject();
        List<GeneratedBusinessBlockFile> files = service.listFiles(project, "admin_frontend");
        GeneratedBusinessBlockFile component = findBlockFile(
                files, "admin_line_chart_001", "frontendComponent");

        assertEquals("business-blocks/chart/templates/frontend/AdminChartBlock.vue.vm",
                component.getTemplateResource());
        assertEquals(Integer.valueOf(6), component.getInstance().getSpan());

        VelocityContext context = new VelocityContext();
        Method method = BusinessBlockGenerationService.class.getDeclaredMethod(
                "putFileContext", VelocityContext.class, GenProject.class, GeneratedBusinessBlockFile.class);
        method.setAccessible(true);
        method.invoke(service, context, project, component);

        assertEquals("/admin/business-blocks/admin_line_chart_001", context.get("apiBasePath"));
        assertEquals("line", context.get("chartType"));
        ChartQueryRenderModel chartQuery = (ChartQueryRenderModel) context.get("chartQuery");
        assertEquals("DATE_FORMAT(t.create_time, '%Y-%m-%d')", chartQuery.getDimensionSql());
        assertTrue(chartQuery.getWhereSql().contains("t.status = #{filter0}"));
    }

    @Test
    public void chartProjectEmitsOneSharedRuntime()
    {
        List<GeneratedBusinessBlockFile> files = service.listFiles(chartProject(), "admin_frontend");

        assertEquals(1, countOutput(files, "src/utils/chartRuntime.js"));
        GeneratedBusinessBlockFile runtime = findPath(files, "src/utils/chartRuntime.js");
        assertEquals("business-block:admin_frontend:shared:chartRuntime", runtime.getCategory());
        assertEquals("business-blocks/chart/templates/frontend/chartRuntime.js.vm",
                runtime.getTemplateResource());
    }

    @Test
    public void adminChartGeneratesFrontendAndBackendFiles()
    {
        assertTrue(hasBlockFile(service.listFiles(chartProject(), "admin_frontend"),
                "admin_line_chart_001", "frontendComponent"));
        assertTrue(hasBlockFile(service.listFiles(chartProject(), "backend"),
                "admin_line_chart_001", "mapperXml"));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void chartPageViewModelExposesConfiguredSpan() throws Exception
    {
        GenProject project = chartProject();
        GeneratedBusinessBlockFile page = findPath(service.listFiles(project, "admin_frontend"),
                "src/views/pages/sales_dashboard/index.vue");
        VelocityContext context = new VelocityContext();
        Method method = BusinessBlockGenerationService.class.getDeclaredMethod(
                "putFileContext", VelocityContext.class, GenProject.class, GeneratedBusinessBlockFile.class);
        method.setAccessible(true);
        method.invoke(service, context, project, page);

        List<Map<String, Object>> blocks = (List<Map<String, Object>>) context.get("businessBlocks");

        assertEquals(Integer.valueOf(6), blocks.get(0).get("span"));
        assertEquals(Integer.valueOf(4), blocks.get(1).get("span"));
    }

    @Test
    public void generatedChartBackendUsesBoundParametersAndUnifiedResponse()
    {
        Map<String, String> rendered = renderChartFiles(chartProject());

        assertTrue(rendered.get("mapper").contains(
                "List<Map<String, Object>> selectData(Map<String, Object> params)"));
        assertTrue(rendered.get("mapperXml").contains("t.status = #{filter0}"));
        assertTrue(rendered.get("mapperXml").contains("t.create_time >= #{dateStart}"));
        assertTrue(rendered.get("mapperXml").contains("t.create_time < #{dateEnd}"));
        assertFalse(rendered.get("mapperXml").contains("PAID"));
        assertTrue(rendered.get("mapperXml").contains("limit 100"));
        assertTrue(rendered.get("serviceImpl").contains("params.put(\"filter0\", \"PAID\")"));
        assertTrue(rendered.get("serviceImpl").contains("today.minusDays(29).atStartOfDay()"));
        assertTrue(rendered.get("serviceImpl").contains("seriesItem(\"Sales\", \"salesAmount\")"));
        assertTrue(rendered.get("serviceImpl").contains("\"dimensions\""));
        assertTrue(rendered.get("serviceImpl").contains("\"series\""));
        assertTrue(rendered.get("serviceImpl").contains("\"summary\""));
        assertTrue(rendered.get("controller").contains(
                "@RequestMapping(\"/admin/business-blocks/admin_line_chart_001\")"));
        assertTrue(rendered.get("controller").contains("@GetMapping(\"/data\")"));
    }

    @Test
    public void generatedChartComponentHasBlockLevelStatesAndCleanup()
    {
        GenProject project = chartProject();
        String component = renderChartFrontendFile(project, "frontendComponent");
        String api = renderChartFrontendFile(project, "frontendApi");
        String runtime = service.renderFile(project,
                findPath(service.listFiles(project, "admin_frontend"), "src/utils/chartRuntime.js"));

        assertTrue(component.contains("el-skeleton"));
        assertTrue(component.contains("el-empty"));
        assertTrue(component.contains("retry"));
        assertTrue(component.contains("disposeChart"));
        assertTrue(component.contains("window.removeEventListener"));
        assertTrue(component.contains("chartType: \"line\""));
        assertTrue(api.contains("url: \"/admin/business-blocks/admin_line_chart_001/data\""));
        assertTrue(runtime.contains("import * as echarts from \"echarts\""));
        assertTrue(runtime.contains("buildChartOption"));
        assertTrue(runtime.contains("hasChartData"));
    }

    @Test
    public void businessBlockPageUsesTwelveColumnSpanClasses()
    {
        GenProject project = chartProject();
        GeneratedBusinessBlockFile pageFile = findPath(service.listFiles(project, "admin_frontend"),
                "src/views/pages/sales_dashboard/index.vue");

        String page = service.renderFile(project, pageFile);

        assertTrue(page.contains("business-block-page__item--span-6"));
        assertTrue(page.contains("business-block-page__item--span-4"));
        assertTrue(page.contains("grid-template-columns: repeat(12, minmax(0, 1fr))"));
    }

    private void setField(String name, Object value) throws Exception
    {
        Field field = BusinessBlockGenerationService.class.getDeclaredField(name);
        field.setAccessible(true);
        field.set(service, value);
    }

    private boolean containsPath(List<GeneratedBusinessBlockFile> files, String outputPath)
    {
        return findPath(files, outputPath) != null;
    }

    private GeneratedBusinessBlockFile findPath(List<GeneratedBusinessBlockFile> files, String outputPath)
    {
        for (GeneratedBusinessBlockFile file : files)
        {
            if (outputPath.equals(file.getOutputPath()))
            {
                return file;
            }
        }
        return null;
    }

    private GeneratedBusinessBlockFile findBlockFile(List<GeneratedBusinessBlockFile> files,
            String instanceCode, String templateKey)
    {
        for (GeneratedBusinessBlockFile file : files)
        {
            if (instanceCode.equals(file.getInstanceCode()) && templateKey.equals(file.getTemplateKey()))
            {
                return file;
            }
        }
        throw new AssertionError("Missing block file " + instanceCode + ":" + templateKey);
    }

    private boolean hasBlockFile(List<GeneratedBusinessBlockFile> files, String instanceCode, String templateKey)
    {
        for (GeneratedBusinessBlockFile file : files)
        {
            if (instanceCode.equals(file.getInstanceCode()) && templateKey.equals(file.getTemplateKey()))
            {
                return true;
            }
        }
        return false;
    }

    private int countOutput(List<GeneratedBusinessBlockFile> files, String outputPath)
    {
        int count = 0;
        for (GeneratedBusinessBlockFile file : files)
        {
            if (outputPath.equals(file.getOutputPath()))
            {
                count++;
            }
        }
        return count;
    }

    private Map<String, String> renderChartFiles(GenProject project)
    {
        Map<String, String> rendered = new LinkedHashMap<String, String>();
        for (GeneratedBusinessBlockFile file : service.listFiles(project, "backend"))
        {
            if ("admin_line_chart_001".equals(file.getInstanceCode()))
            {
                rendered.put(file.getTemplateKey(), service.renderFile(project, file));
            }
        }
        return rendered;
    }

    private String renderChartFrontendFile(GenProject project, String templateKey)
    {
        GeneratedBusinessBlockFile file = findBlockFile(service.listFiles(project, "admin_frontend"),
                "admin_line_chart_001", templateKey);
        return service.renderFile(project, file);
    }

    private void assertRenderedFiles(GenProject project, List<GeneratedBusinessBlockFile> files)
    {
        for (GeneratedBusinessBlockFile file : files)
        {
            String content = service.renderFile(project, file);
            assertTrue(file.getOutputPath(), content.trim().length() > 0);
        }
    }

    private GenProject project()
    {
        GenProject project = new GenProject();
        project.setProjectId(10L);
        project.setProjectName("Demo");
        project.setProjectFileName("demo");
        project.setPackageName("com.example.demo");
        project.setTables(Arrays.asList(cartTable(), productTable()));
        project.setPageDesigns(Arrays.asList(cartPageDesign()));
        return project;
    }

    private GenProject businessBlocksProject()
    {
        GenProject project = new GenProject();
        project.setProjectId(11L);
        project.setProjectName("BusinessBlocks");
        project.setProjectFileName("business-blocks");
        project.setPackageName("com.example.demo");
        project.setTables(Arrays.asList(
                noticeTable(),
                carouselTable(),
                orderTable(),
                orderItemTable(),
                productTable(),
                masterTable(),
                detailTable()));
        project.setPageDesigns(Arrays.asList(businessBlocksPageDesign()));
        return project;
    }

    private GenProject embeddedBlocksProject()
    {
        GenProject project = new GenProject();
        project.setProjectId(12L);
        project.setProjectName("EmbeddedBlocks");
        project.setProjectFileName("embedded-blocks");
        project.setPackageName("com.example.demo");
        project.setTables(Arrays.asList(noticeTable(), carouselTable(), masterTable()));
        project.setPageDesigns(Arrays.asList(embeddedBlocksPageDesign()));
        return project;
    }

    private GenProject chartProject()
    {
        GenProject project = new GenProject();
        project.setProjectId(13L);
        project.setProjectName("ChartBlocks");
        project.setProjectFileName("chart-blocks");
        project.setPackageName("com.example.demo");
        project.setTables(Arrays.asList(orderTable()));
        project.setPageDesigns(Arrays.asList(chartPageDesign()));
        return project;
    }

    private FrontProjectPageDesign cartPageDesign()
    {
        FrontProjectPageDesign design = new FrontProjectPageDesign();
        design.setPageCode("cart_page");
        design.setPageName("Cart");
        design.setPageScope("frontend");
        design.setPageType("business_block");
        design.setRoutePath("/cart");
        design.setTableName("shop_cart");
        design.setLayoutJson("{\"canvas\":\"business-blocks-v1\",\"blocks\":[{\"id\":\"cart_001\",\"blockCode\":\"cart\",\"config\":{"
                + "\"cartTable\":\"shop_cart\","
                + "\"productTable\":\"shop_product\","
                + "\"cartProductId\":\"productId\","
                + "\"productId\":\"id\","
                + "\"productName\":\"name\","
                + "\"price\":\"price\","
                + "\"quantity\":\"quantity\","
                + "\"userId\":\"userId\","
                + "\"stock\":\"stock\""
                + "}}]}");
        return design;
    }

    private FrontProjectPageDesign businessBlocksPageDesign()
    {
        FrontProjectPageDesign design = new FrontProjectPageDesign();
        design.setPageCode("business_page");
        design.setPageName("Business Blocks");
        design.setPageScope("frontend");
        design.setPageType("business_block");
        design.setRoutePath("/business");
        design.setLayoutJson("{\"canvas\":\"business-blocks-v1\",\"blocks\":["
                + "{\"id\":\"notice_001\",\"blockCode\":\"notice\",\"config\":{"
                + "\"noticeTable\":\"sys_notice\",\"title\":\"noticeTitle\",\"content\":\"noticeContent\",\"status\":\"status\",\"publishTime\":\"createTime\"}},"
                + "{\"id\":\"carousel_001\",\"blockCode\":\"carousel\",\"config\":{"
                + "\"carouselTable\":\"home_banner\",\"image\":\"imageUrl\",\"title\":\"title\",\"link\":\"linkUrl\",\"sort\":\"sortNo\",\"status\":\"status\"}},"
                + "{\"id\":\"order_001\",\"blockCode\":\"order\",\"config\":{"
                + "\"orderTable\":\"shop_order\",\"orderItemTable\":\"shop_order_item\",\"productTable\":\"shop_product\","
                + "\"orderUserId\":\"userId\",\"orderNo\":\"orderNo\",\"orderAmount\":\"totalAmount\",\"orderStatus\":\"status\",\"orderCreateTime\":\"createTime\","
                + "\"itemOrderId\":\"orderId\",\"itemProductId\":\"productId\",\"itemQuantity\":\"quantity\",\"itemPrice\":\"price\","
                + "\"productId\":\"id\",\"productName\":\"name\",\"productImage\":\"imageUrl\",\"productPrice\":\"price\"}},"
                + "{\"id\":\"master_detail_001\",\"blockCode\":\"master_detail\",\"config\":{"
                + "\"masterTable\":\"library_book\",\"detailTable\":\"library_borrowing\","
                + "\"masterId\":\"id\",\"detailMasterId\":\"bookId\",\"masterTitle\":\"bookName\",\"masterDescription\":\"description\","
                + "\"detailTitle\":\"borrowerName\",\"detailValue\":\"status\",\"detailTime\":\"createTime\"}}"
                + "]}");
        return design;
    }

    private FrontProjectPageDesign embeddedBlocksPageDesign()
    {
        FrontProjectPageDesign design = new FrontProjectPageDesign();
        design.setPageCode("book_catalog");
        design.setPageName("Book Catalog");
        design.setPageScope("frontend");
        design.setPageType("list");
        design.setRoutePath("/books");
        design.setTableName("library_book");
        design.setLayoutJson("{\"canvas\":\"frontend-list-v1\",\"regions\":[{\"id\":\"table\",\"type\":\"table\",\"fields\":[\"bookName\"]}],"
                + "\"embeddedBlocks\":{\"top\":[{\"id\":\"notice_001\",\"blockCode\":\"notice\",\"config\":{"
                + "\"noticeTable\":\"sys_notice\",\"title\":\"noticeTitle\",\"content\":\"noticeContent\",\"publishTime\":\"createTime\"}}],"
                + "\"bottom\":[{\"id\":\"carousel_001\",\"blockCode\":\"carousel\",\"config\":{"
                + "\"carouselTable\":\"home_banner\",\"image\":\"imageUrl\",\"title\":\"title\",\"link\":\"linkUrl\"}}]}}");
        return design;
    }

    private FrontProjectPageDesign chartPageDesign()
    {
        FrontProjectPageDesign design = new FrontProjectPageDesign();
        design.setPageCode("sales_dashboard");
        design.setPageName("Sales Dashboard");
        design.setPageScope("admin");
        design.setPageType("business_block");
        design.setRoutePath("/admin/sales-dashboard");
        design.setLayoutJson("{\"canvas\":\"business-blocks-v1\",\"blocks\":["
                + "{\"id\":\"admin_line_chart_001\",\"blockCode\":\"admin_line_chart\",\"name\":\"Sales Trend\",\"span\":6,"
                + "\"dataset\":{\"schema\":\"single-table-aggregate-v1\",\"table\":\"shop_order\","
                + "\"dimension\":{\"field\":\"createTime\",\"timeUnit\":\"day\"},"
                + "\"metrics\":[{\"field\":\"totalAmount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\",\"label\":\"Sales\"}],"
                + "\"filters\":[{\"field\":\"status\",\"operator\":\"eq\",\"value\":\"PAID\"}],"
                + "\"dateRange\":{\"field\":\"createTime\",\"preset\":\"last_30_days\"},"
                + "\"sort\":{\"by\":\"dimension\",\"order\":\"asc\"},\"limit\":100},"
                + "\"display\":{\"title\":\"Sales Trend\",\"smooth\":true}},"
                + "{\"id\":\"admin_metric_chart_001\",\"blockCode\":\"admin_metric_chart\",\"name\":\"Order Count\",\"span\":4,"
                + "\"dataset\":{\"schema\":\"single-table-aggregate-v1\",\"table\":\"shop_order\","
                + "\"metrics\":[{\"field\":\"id\",\"aggregate\":\"count\",\"alias\":\"orderCount\",\"label\":\"Orders\"}],"
                + "\"filters\":[],\"limit\":1},\"display\":{\"title\":\"Order Count\"}}"
                + "]}");
        return design;
    }

    private GenTable cartTable()
    {
        GenTable table = table(201L, "shop_cart", "ShopCart");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("product_id", "productId", "0"),
                column("quantity", "quantity", "0"),
                column("user_id", "userId", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable productTable()
    {
        GenTable table = table(202L, "shop_product", "ShopProduct");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("name", "name", "0"),
                column("image_url", "imageUrl", "0"),
                column("price", "price", "0"),
                column("stock", "stock", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable noticeTable()
    {
        GenTable table = table(203L, "sys_notice", "SysNotice");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("notice_title", "noticeTitle", "0"),
                column("notice_content", "noticeContent", "0"),
                column("status", "status", "0"),
                column("create_time", "createTime", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable carouselTable()
    {
        GenTable table = table(204L, "home_banner", "HomeBanner");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("image_url", "imageUrl", "0"),
                column("title", "title", "0"),
                column("link_url", "linkUrl", "0"),
                column("sort_no", "sortNo", "0"),
                column("status", "status", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable orderTable()
    {
        GenTable table = table(205L, "shop_order", "ShopOrder");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("user_id", "userId", "0"),
                column("order_no", "orderNo", "0"),
                column("total_amount", "totalAmount", "0"),
                column("status", "status", "0"),
                column("create_time", "createTime", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable orderItemTable()
    {
        GenTable table = table(206L, "shop_order_item", "ShopOrderItem");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("order_id", "orderId", "0"),
                column("product_id", "productId", "0"),
                column("quantity", "quantity", "0"),
                column("price", "price", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable masterTable()
    {
        GenTable table = table(207L, "library_book", "LibraryBook");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("book_name", "bookName", "0"),
                column("description", "description", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable detailTable()
    {
        GenTable table = table(208L, "library_borrowing", "LibraryBorrowing");
        table.setColumns(Arrays.asList(
                column("id", "id", "1"),
                column("book_id", "bookId", "0"),
                column("borrower_name", "borrowerName", "0"),
                column("status", "status", "0"),
                column("create_time", "createTime", "0")));
        table.setPkColumn(table.getColumns().get(0));
        return table;
    }

    private GenTable table(Long tableId, String tableName, String className)
    {
        GenTable table = new GenTable();
        table.setTableId(tableId);
        table.setTableName(tableName);
        table.setClassName(className);
        return table;
    }

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

    private GenTableColumn findColumn(GenProject project, String tableName, String columnName)
    {
        for (GenTable table : project.getTables())
        {
            if (!tableName.equals(table.getTableName()))
            {
                continue;
            }
            for (GenTableColumn column : table.getColumns())
            {
                if (columnName.equals(column.getColumnName()))
                {
                    return column;
                }
            }
        }
        throw new AssertionError("Missing column " + tableName + "." + columnName);
    }
}
