package com.ruoyi.generator.service.front;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.generator.config.DeepSeekProperties;
import com.ruoyi.generator.domain.front.FrontProject;
import com.ruoyi.generator.domain.front.FrontProjectColumn;
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
import com.ruoyi.generator.domain.front.FrontProjectTable;
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionRuleDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
import com.ruoyi.generator.domain.front.dto.GenerateBusinessBlueprintRequest;
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;

public class AiGenerateServiceImplTest
{
    private AiGenerateServiceImpl service;

    @Mock
    private IDeepSeekClient deepSeekClient;
    @Mock
    private FrontProjectMapper frontProjectMapper;
    @Mock
    private FrontProjectTableMapper frontProjectTableMapper;
    @Mock
    private FrontProjectColumnMapper frontProjectColumnMapper;
    @Mock
    private FrontProjectGenerationMapper frontProjectGenerationMapper;
    private AiGenerationRecordService generationRecordService;

    @Before
    public void setUp() throws Exception
    {
        MockitoAnnotations.initMocks(this);
        service = new AiGenerateServiceImpl();
        generationRecordService = new AiGenerationRecordService();
        setField(generationRecordService, "frontProjectGenerationMapper", frontProjectGenerationMapper);
        setField("deepSeekClient", deepSeekClient);
        setField("frontProjectMapper", frontProjectMapper);
        setField("frontProjectTableMapper", frontProjectTableMapper);
        setField("frontProjectColumnMapper", frontProjectColumnMapper);
        setField("generationRecordService", generationRecordService);
        setField("validator", new AiDatabaseSchemaValidator());
        setField("businessBlueprintValidator", new AiBusinessBlueprintValidator());
        setField("ddlBuilder", new MysqlDdlBuilder());
        setField("deepSeekProperties", new DeepSeekProperties());
    }

    @Test
    public void generateDatabasePersistsTablesColumnsAndGenerationRecord()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.<FrontProjectTable>emptyList());
        when(deepSeekClient.chat(org.mockito.ArgumentMatchers.anyString())).thenReturn(mockResponse());

        DatabaseDesignResponse response = service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<FrontProjectTable> tableCaptor = ArgumentCaptor.forClass(FrontProjectTable.class);
        ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
        ArgumentCaptor<FrontProjectGeneration> generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class);
        verify(frontProjectTableMapper).insertFrontProjectTable(tableCaptor.capture());
        verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
        verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture());
        assertEquals("car_info", tableCaptor.getValue().getTableName());
        assertTrue(tableCaptor.getValue().getCreateTableSql().contains("CREATE TABLE `car_info`"));
        assertEquals(2, columnCaptor.getAllValues().size());
        assertEquals("1", generationCaptor.getValue().getSuccess());
        assertTrue(response.getSql().contains("CREATE TABLE `car_info`"));
        assertTrue(response.getBusinessActions().isEmpty());
        ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
        verify(deepSeekClient).chat(promptCaptor.capture());
        assertFalse(promptCaptor.getValue().contains("\"businessActions\""));
        assertFalse(promptCaptor.getValue().contains("conditionFields"));
        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
        assertEquals("1", projectCaptor.getValue().getGenerateStatus());
        assertEquals("0", projectCaptor.getValue().getPreviewStatus());
        assertEquals("[]", projectCaptor.getValue().getBusinessBlueprint());
    }

    @Test
    public void generateAppBlueprintNormalizesAndPersistsStaticMenus()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenReturn(appBlueprintResponse());

        AppBlueprintDesign blueprint = service.generateAppBlueprint(7L, 10L, appBlueprintRequest());

        ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
        verify(deepSeekClient).chat(promptCaptor.capture());
        assertTrue(promptCaptor.getValue().contains("role.name"));
        assertTrue(promptCaptor.getValue().contains("Simplified Chinese"));

        assertEquals(2, blueprint.getRoles().size());
        assertEquals("reader", blueprint.getRoles().get(0).getCode());
        assertEquals("admin", blueprint.getRoles().get(1).getCode());
        assertEquals("my_borrows", blueprint.getFrontendMenus().get(0).getCode());
        assertEquals(Boolean.TRUE, blueprint.getFrontendMenus().get(0).getRequiresLogin());
        assertEquals("CURRENT_USER", blueprint.getFrontendMenus().get(0).getDataScope());
        assertEquals("reader", blueprint.getFrontendMenus().get(0).getVisibleRoles().get(0));
        assertEquals("book_admin", blueprint.getAdminMenus().get(0).getCode());
        assertEquals("ROLE", blueprint.getAdminMenus().get(0).getDataScope());

        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
        assertTrue(projectCaptor.getValue().getAppBlueprint().contains("my_borrows"));
        assertEquals("0", projectCaptor.getValue().getGenerateStatus());
        assertEquals("0", projectCaptor.getValue().getPreviewStatus());

        ArgumentCaptor<FrontProjectGeneration> generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class);
        verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture());
        assertEquals("app_blueprint", generationCaptor.getValue().getGenerateType());
        assertEquals("1", generationCaptor.getValue().getSuccess());
    }

    @Test
    public void generateDatabasePersistsRequirementKeywordFromRequest()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.<FrontProjectTable>emptyList());
        when(deepSeekClient.chat(anyString())).thenReturn(mockResponse());
        GenerateDatabaseRequest request = request();
        request.setExtraRequirements("library borrow return keyword");

        service.generateDatabase(7L, 10L, request);

        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertEquals("library borrow return keyword", projectCaptor.getValue().getRequirementKeyword());
    }

    @Test
    public void generateBusinessBlueprintUsesSavedDatabaseAndPersistsActions()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedLibraryTables();
        when(deepSeekClient.chat(anyString())).thenReturn(businessBlueprintOnlyResponse());

        List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());

        assertEquals(1, actions.size());
        BusinessActionDesign action = actions.get(0);
        assertEquals("borrow_book", action.getCode());
        assertEquals("book_borrow", action.getOwnerTable());
        assertEquals("book_id", action.getRequestFields().get(0));
        assertEquals("UPDATE_FIELD", action.getEffects().get(0).getType());
        assertEquals("stock", action.getEffects().get(0).getTargetField());

        ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
        verify(deepSeekClient).chat(promptCaptor.capture());
        String prompt = promptCaptor.getValue();
        assertTrue(prompt.contains("Saved database schema"));
        assertTrue(prompt.contains("book_info"));
        assertTrue(prompt.contains("stock"));
        assertTrue(prompt.contains("Do not generate CRUD"));
        assertTrue(prompt.contains("Do not generate create, read, update, delete"));
        assertTrue(prompt.contains("businessActions.name"));
        assertTrue(prompt.contains("rules"));
        assertTrue(prompt.contains("effects.description"));
        assertTrue(prompt.contains("Simplified Chinese"));
        assertTrue(prompt.contains("Keep API paths and request parameter fields as technical identifiers"));

        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
        assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("borrow_book"));
        assertEquals("0", projectCaptor.getValue().getPreviewStatus());

        ArgumentCaptor<FrontProjectGeneration> generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class);
        verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture());
        assertEquals("business_blueprint", generationCaptor.getValue().getGenerateType());
        assertEquals("1", generationCaptor.getValue().getSuccess());
    }

    @Test
    public void generateBusinessBlueprintParsesExecutableRulesAndWorkflowEffects()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedLibraryTables();
        when(deepSeekClient.chat(anyString())).thenReturn(executableBusinessBlueprintResponse());

        List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());

        assertEquals(1, actions.size());
        BusinessActionDesign action = actions.get(0);
        assertEquals("borrow_book", action.getCode());
        assertEquals(2, action.getRuleChecks().size());

        BusinessActionRuleDesign existsRule = action.getRuleChecks().get(0);
        assertEquals("EXISTS", existsRule.getType());
        assertEquals("book_info", existsRule.getTargetTable());
        assertEquals("book_id", existsRule.getConditionFields().get(0));

        BusinessActionRuleDesign stockRule = action.getRuleChecks().get(1);
        assertEquals("NUMBER_GTE", stockRule.getType());
        assertEquals("stock", stockRule.getTargetField());
        assertEquals("1", stockRule.getCompareValue());

        BusinessActionEffectDesign insertEffect = action.getEffects().get(0);
        assertEquals("INSERT_ROW", insertEffect.getType());
        assertEquals("book_borrow", insertEffect.getTargetTable());
        assertEquals("${param.book_id}", insertEffect.getValues().get("book_id"));
        assertEquals("borrowed", insertEffect.getValues().get("borrow_status"));

        BusinessActionEffectDesign decreaseEffect = action.getEffects().get(1);
        assertEquals("DECREASE_NUMBER", decreaseEffect.getType());
        assertEquals("book_info", decreaseEffect.getTargetTable());
        assertEquals("stock", decreaseEffect.getTargetField());
        assertEquals("1", decreaseEffect.getAmount());
        assertEquals("0", decreaseEffect.getMinValue());

        ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
        verify(deepSeekClient).chat(promptCaptor.capture());
        assertTrue(promptCaptor.getValue().contains("ruleChecks"));
        assertTrue(promptCaptor.getValue().contains("INSERT_ROW"));
        assertTrue(promptCaptor.getValue().contains("DECREASE_NUMBER"));

        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        String persisted = projectCaptor.getValue().getBusinessBlueprint();
        assertTrue(persisted.contains("ruleChecks"));
        assertTrue(persisted.contains("INSERT_ROW"));
        assertTrue(persisted.contains("DECREASE_NUMBER"));
    }

    @Test
    public void generateBusinessBlueprintDropsCrudActionsReturnedByAi()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedLibraryTables();
        when(deepSeekClient.chat(anyString())).thenReturn(businessBlueprintWithCrudActionsResponse());

        List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());

        assertEquals(1, actions.size());
        assertEquals("borrow_book", actions.get(0).getCode());
        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("borrow_book"));
        assertFalse(projectCaptor.getValue().getBusinessBlueprint().contains("create_book"));
    }

    @Test
    public void generateDatabaseIncludesConfirmedAppBlueprintConstraintsButNoBusinessActions()
    {
        FrontProject project = project();
        project.setAppBlueprint("{\"frontendMenus\":[{\"code\":\"my_borrows\",\"requiresLogin\":true,\"dataScope\":\"CURRENT_USER\"}],\"adminMenus\":[]}");
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project);
        when(deepSeekClient.chat(anyString())).thenReturn(mockResponse());

        service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
        verify(deepSeekClient).chat(promptCaptor.capture());
        String prompt = promptCaptor.getValue();
        assertTrue(prompt.contains("Confirmed app blueprint JSON"));
        assertTrue(prompt.contains("CURRENT_USER"));
        assertTrue(prompt.contains("Do not generate menu or permission tables"));
        assertTrue(prompt.contains("user_id"));
        assertFalse(prompt.contains("\"businessActions\""));
        assertFalse(prompt.contains("conditionFields"));
        assertFalse(prompt.contains("Business actions must align"));
    }

    @Test
    public void generateDatabaseAddsIdPrimaryKeyWhenAiOmitsPrimaryKey()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithoutPrimaryKey());

        DatabaseDesignResponse response = service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
        verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
        FrontProjectColumn idColumn = columnCaptor.getAllValues().get(0);
        assertEquals("id", idColumn.getColumnName());
        assertEquals("1", idColumn.getIsPk());
        assertEquals("1", idColumn.getIsIncrement());
        assertTrue(response.getSql().contains("PRIMARY KEY (`id`)"));
    }

    @Test
    public void generateDatabaseAcceptsPrimaryKeyAliasFromAi()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithPrimaryKeyAlias());

        service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
        verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
        FrontProjectColumn orderId = columnCaptor.getAllValues().get(0);
        assertEquals("order_id", orderId.getColumnName());
        assertEquals("1", orderId.getIsPk());
        assertEquals("1", orderId.getIsIncrement());
    }

    @Test
    public void generateDatabaseKeepsOnlyFirstPrimaryKeyWhenAiReturnsMultiple()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithMultiplePrimaryKeys());

        service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
        verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
        assertEquals("1", columnCaptor.getAllValues().get(0).getIsPk());
        assertEquals("0", columnCaptor.getAllValues().get(1).getIsPk());
    }

    @Test
    public void generateDatabaseNormalizesUnsafeAiColumnNamesBeforeValidation()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithUnsafeColumnNames());

        service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
        verify(frontProjectColumnMapper, org.mockito.Mockito.times(4)).insertFrontProjectColumn(columnCaptor.capture());
        assertEquals("order_id", columnCaptor.getAllValues().get(0).getColumnName());
        assertEquals("customer_name", columnCaptor.getAllValues().get(1).getColumnName());
        assertEquals("field_3", columnCaptor.getAllValues().get(2).getColumnName());
        assertEquals("customer_name_2", columnCaptor.getAllValues().get(3).getColumnName());
    }

    @Test
    public void generateDatabaseNormalizesUnsupportedAiColumnTypesBeforeValidation()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithUnsupportedColumnTypes());

        service.generateDatabase(7L, 10L, request());

        ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
        verify(frontProjectColumnMapper, org.mockito.Mockito.times(8)).insertFrontProjectColumn(columnCaptor.capture());
        assertEquals("bigint(20)", columnCaptor.getAllValues().get(0).getColumnType());
        assertEquals("int(11)", columnCaptor.getAllValues().get(1).getColumnType());
        assertEquals("varchar(255)", columnCaptor.getAllValues().get(2).getColumnType());
        assertEquals("int(11)", columnCaptor.getAllValues().get(3).getColumnType());
        assertEquals("datetime", columnCaptor.getAllValues().get(4).getColumnType());
        assertEquals("text", columnCaptor.getAllValues().get(5).getColumnType());
        assertEquals("decimal(10,2)", columnCaptor.getAllValues().get(6).getColumnType());
        assertEquals("varchar(1000)", columnCaptor.getAllValues().get(7).getColumnType());
    }

    @Test
    public void generateDatabasePersistsFailureGenerationRecordBeforePropagatingClientFailure()
    {
        RuntimeException failure = new RuntimeException("DeepSeek unavailable\n" + repeated("x", 1200));
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(deepSeekClient.chat(anyString())).thenThrow(failure);

        try
        {
            service.generateDatabase(7L, 10L, request());
            fail("Expected client failure");
        }
        catch (RuntimeException e)
        {
            assertSame(failure, e);
        }

        ArgumentCaptor<FrontProjectGeneration> generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class);
        verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture());
        assertEquals("0", generationCaptor.getValue().getSuccess());
        assertTrue(generationCaptor.getValue().getErrorMessage().length() <= 1000);
        assertFalse(generationCaptor.getValue().getErrorMessage().contains("\n"));
        verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(10L);
        verify(frontProjectTableMapper, never()).deleteTablesByProjectId(10L);
    }

    @Test
    public void generateBusinessBlueprintParsesNormalizesAndPersistsBusinessActionDraft()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedLibraryTables();
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithBusinessActions());

        List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());

        assertEquals(1, actions.size());
        BusinessActionDesign action = actions.get(0);
        assertEquals("borrow_book", action.getCode());
        assertEquals("借书", action.getName());
        assertEquals("book_borrow", action.getOwnerTable());
        assertEquals("POST", action.getMethod());
        assertEquals("/library/borrow/borrow", action.getPath());
        assertEquals(Boolean.TRUE, action.getTransaction());
        assertEquals("book_id", action.getRequestFields().get(0));
        assertEquals("读者状态必须正常", action.getRules().get(0));
        BusinessActionEffectDesign effect = action.getEffects().get(0);
        assertEquals("UPDATE_FIELD", effect.getType());
        assertEquals("book_info", effect.getTargetTable());
        assertEquals("stock", effect.getTargetField());
        assertEquals("book_id", effect.getConditionFields().get(0));

        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("borrow_book"));
    }

    @Test
    public void generateBusinessBlueprintDropsMissingBusinessActionRequestFields()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedFineTables();
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithMissingBusinessActionRequestField());

        List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());

        List<String> requestFields = actions.get(0).getRequestFields();
        assertEquals(1, requestFields.size());
        assertEquals("reader_id", requestFields.get(0));
        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertFalse(projectCaptor.getValue().getBusinessBlueprint().contains("fine_record_id"));
    }

    @Test
    public void generateBusinessBlueprintRepairsMissingBusinessEffectTargetField()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedEffectRepairTables();
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithMissingBusinessEffectTargetField());

        List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());

        BusinessActionEffectDesign effect = actions.get(0).getEffects().get(0);
        assertEquals("stock", effect.getTargetField());
        assertEquals("stock - 1", effect.getValue());

        ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
        verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
        assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("\"targetField\":\"stock\""));
        assertFalse(projectCaptor.getValue().getBusinessBlueprint().contains("available_stock"));
    }

    @Test
    public void generateBusinessBlueprintRejectsBusinessActionReferencingMissingTable()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedBookInfoOnly();
        when(deepSeekClient.chat(anyString())).thenReturn(responseWithMissingBusinessActionTable());

        try
        {
            service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
            fail("Expected invalid business action table");
        }
        catch (ServiceException e)
        {
            assertEquals("业务动作关联表不存在：missing_table", e.getMessage());
        }

        verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(10L);
        verify(frontProjectTableMapper, never()).deleteTablesByProjectId(10L);
    }

    @Test
    public void generateBusinessBlueprintRejectsRuleCheckWithMissingTargetField()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedLibraryTables();
        when(deepSeekClient.chat(anyString())).thenReturn(ruleCheckWithMissingTargetFieldResponse());

        try
        {
            service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
            fail("Expected missing rule target field to be rejected");
        }
        catch (ServiceException e)
        {
            assertEquals("Business rule target field is required: NUMBER_GTE", e.getMessage());
        }
    }

    @Test
    public void generateBusinessBlueprintRejectsEffectValueWithUnknownParam()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        mockSavedLibraryTables();
        when(deepSeekClient.chat(anyString())).thenReturn(effectValueWithUnknownParamResponse());

        try
        {
            service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
            fail("Expected unknown value placeholder to be rejected");
        }
        catch (ServiceException e)
        {
            assertEquals("Business effect value references missing field: missing_reader_id", e.getMessage());
        }
    }

    @Test
    public void generationRecordInsertUsesRequiresNewTransaction() throws Exception
    {
        Method method = AiGenerationRecordService.class.getDeclaredMethod("insert", FrontProjectGeneration.class);
        Transactional transactional = method.getAnnotation(Transactional.class);

        assertNotNull(transactional);
        assertEquals(Propagation.REQUIRES_NEW, transactional.propagation());
    }

    private void setField(String name, Object value) throws Exception
    {
        setField(service, name, value);
    }

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

    private FrontProject project()
    {
        FrontProject project = new FrontProject();
        project.setProjectId(10L);
        project.setUserId(7L);
        project.setProjectName("车辆管理系统");
        project.setProjectDesc("管理车辆档案");
        project.setIndustryTemplate("fleet");
        return project;
    }

    private GenerateDatabaseRequest request()
    {
        GenerateDatabaseRequest request = new GenerateDatabaseRequest();
        request.setProjectName("车辆管理系统");
        request.setProjectDesc("管理车辆档案");
        request.setIndustryTemplate("fleet");
        request.setExtraRequirements("车辆基础信息");
        return request;
    }

    private GenerateAppBlueprintRequest appBlueprintRequest()
    {
        GenerateAppBlueprintRequest request = new GenerateAppBlueprintRequest();
        request.setProjectName("Library");
        request.setProjectDesc("Readers borrow and return books");
        request.setIndustryTemplate("library");
        request.setExtraRequirements("Need portal and admin menus");
        return request;
    }

    private GenerateBusinessBlueprintRequest businessBlueprintRequest()
    {
        GenerateBusinessBlueprintRequest request = new GenerateBusinessBlueprintRequest();
        request.setProjectName("Library");
        request.setProjectDesc("Readers borrow and return books");
        request.setIndustryTemplate("library");
        request.setExtraRequirements("Need borrow and return workflows, not CRUD");
        return request;
    }

    private FrontProjectTable savedTable(Long tableId, String tableName, String tableComment)
    {
        FrontProjectTable table = new FrontProjectTable();
        table.setTableId(tableId);
        table.setProjectId(10L);
        table.setTableName(tableName);
        table.setTableComment(tableComment);
        return table;
    }

    private FrontProjectColumn savedColumn(String columnName, String columnType, String columnComment)
    {
        FrontProjectColumn column = new FrontProjectColumn();
        column.setProjectId(10L);
        column.setColumnName(columnName);
        column.setColumnType(columnType);
        column.setColumnComment(columnComment);
        return column;
    }

    private void mockSavedLibraryTables()
    {
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
                savedTable(101L, "book_info", "Book info"),
                savedTable(102L, "reader_info", "Reader info"),
                savedTable(103L, "book_borrow", "Book borrow")));
        when(frontProjectColumnMapper.selectColumnsByTableId(101L)).thenReturn(Arrays.asList(
                savedColumn("book_id", "bigint(20)", "Book ID"),
                savedColumn("stock", "int(11)", "Stock")));
        when(frontProjectColumnMapper.selectColumnsByTableId(102L)).thenReturn(Arrays.asList(
                savedColumn("reader_id", "bigint(20)", "Reader ID"),
                savedColumn("status", "varchar(20)", "Status")));
        when(frontProjectColumnMapper.selectColumnsByTableId(103L)).thenReturn(Arrays.asList(
                savedColumn("borrow_id", "bigint(20)", "Borrow ID"),
                savedColumn("book_id", "bigint(20)", "Book ID"),
                savedColumn("reader_id", "bigint(20)", "Reader ID"),
                savedColumn("borrow_status", "varchar(20)", "Borrow status")));
    }

    private void mockSavedFineTables()
    {
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
                savedTable(201L, "fine_record", "Fine record")));
        when(frontProjectColumnMapper.selectColumnsByTableId(201L)).thenReturn(Arrays.asList(
                savedColumn("id", "bigint(20)", "ID"),
                savedColumn("reader_id", "bigint(20)", "Reader ID"),
                savedColumn("fine_amount", "decimal(10,2)", "Fine amount")));
    }

    private void mockSavedEffectRepairTables()
    {
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
                savedTable(301L, "book_info", "Book info"),
                savedTable(302L, "borrow_record", "Borrow record")));
        when(frontProjectColumnMapper.selectColumnsByTableId(301L)).thenReturn(Arrays.asList(
                savedColumn("book_id", "bigint(20)", "Book ID"),
                savedColumn("stock", "int(11)", "Stock")));
        when(frontProjectColumnMapper.selectColumnsByTableId(302L)).thenReturn(Arrays.asList(
                savedColumn("borrow_id", "bigint(20)", "Borrow ID"),
                savedColumn("book_id", "bigint(20)", "Book ID")));
    }

    private void mockSavedBookInfoOnly()
    {
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
                savedTable(401L, "book_info", "Book info")));
        when(frontProjectColumnMapper.selectColumnsByTableId(401L)).thenReturn(Arrays.asList(
                savedColumn("book_id", "bigint(20)", "Book ID")));
    }

    private String repeated(String value, int count)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < count; i++)
        {
            builder.append(value);
        }
        return builder.toString();
    }

    private String mockResponse()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\n"
                + "      \"tableName\": \"car_info\",\n"
                + "      \"tableComment\": \"车辆信息表\",\n"
                + "      \"columns\": [\n"
                + "        {\n"
                + "          \"columnName\": \"car_id\",\n"
                + "          \"columnType\": \"bigint(20)\",\n"
                + "          \"javaType\": \"Long\",\n"
                + "          \"javaField\": \"carId\",\n"
                + "          \"isPk\": \"1\",\n"
                + "          \"isIncrement\": \"1\",\n"
                + "          \"isRequired\": \"1\",\n"
                + "          \"columnComment\": \"车辆ID\"\n"
                + "        },\n"
                + "        {\n"
                + "          \"columnName\": \"car_no\",\n"
                + "          \"columnType\": \"varchar(30)\",\n"
                + "          \"javaType\": \"String\",\n"
                + "          \"javaField\": \"carNo\",\n"
                + "          \"isPk\": \"0\",\n"
                + "          \"isIncrement\": \"0\",\n"
                + "          \"isRequired\": \"1\",\n"
                + "          \"columnComment\": \"车牌号\"\n"
                + "        }\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String appBlueprintResponse()
    {
        return "{\n"
                + "  \"roles\": [\n"
                + "    {\"code\": \"Reader\", \"name\": \"Reader\", \"description\": \"Portal reader\"},\n"
                + "    {\"code\": \"admin\", \"name\": \"Admin\", \"description\": \"Back office\"}\n"
                + "  ],\n"
                + "  \"frontendMenus\": [\n"
                + "    {\"code\": \"My Borrows\", \"name\": \"My Borrows\", \"path\": \"my-borrows\", \"requiresLogin\": true, \"visibleRoles\": [\"Reader\"], \"dataScope\": \"current_user\"},\n"
                + "    {\"code\": \"Catalog\", \"name\": \"Catalog\", \"path\": \"/catalog\", \"requiresLogin\": false, \"visibleRoles\": []}\n"
                + "  ],\n"
                + "  \"adminMenus\": [\n"
                + "    {\"code\": \"Book Admin\", \"name\": \"Books\", \"path\": \"/books\", \"requiresLogin\": true, \"visibleRoles\": [\"admin\"], \"dataScope\": \"role\"}\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithoutPrimaryKey()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\n"
                + "      \"tableName\": \"customer_info\",\n"
                + "      \"tableComment\": \"Customer info\",\n"
                + "      \"columns\": [\n"
                + "        {\n"
                + "          \"columnName\": \"customer_name\",\n"
                + "          \"columnType\": \"varchar(64)\",\n"
                + "          \"isRequired\": \"1\",\n"
                + "          \"columnComment\": \"Customer name\"\n"
                + "        }\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithPrimaryKeyAlias()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\n"
                + "      \"tableName\": \"order_info\",\n"
                + "      \"tableComment\": \"Order info\",\n"
                + "      \"columns\": [\n"
                + "        {\n"
                + "          \"columnName\": \"order_id\",\n"
                + "          \"columnType\": \"bigint(20)\",\n"
                + "          \"primaryKey\": true,\n"
                + "          \"autoIncrement\": true,\n"
                + "          \"columnComment\": \"Order ID\"\n"
                + "        },\n"
                + "        {\n"
                + "          \"columnName\": \"order_no\",\n"
                + "          \"columnType\": \"varchar(64)\",\n"
                + "          \"columnComment\": \"Order no\"\n"
                + "        }\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithMultiplePrimaryKeys()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\n"
                + "      \"tableName\": \"product_info\",\n"
                + "      \"tableComment\": \"Product info\",\n"
                + "      \"columns\": [\n"
                + "        {\n"
                + "          \"columnName\": \"product_id\",\n"
                + "          \"columnType\": \"bigint(20)\",\n"
                + "          \"isPk\": \"1\",\n"
                + "          \"columnComment\": \"Product ID\"\n"
                + "        },\n"
                + "        {\n"
                + "          \"columnName\": \"sku_id\",\n"
                + "          \"columnType\": \"bigint(20)\",\n"
                + "          \"isPk\": \"1\",\n"
                + "          \"columnComment\": \"SKU ID\"\n"
                + "        }\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithUnsafeColumnNames()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\n"
                + "      \"tableName\": \"order_info\",\n"
                + "      \"tableComment\": \"Order info\",\n"
                + "      \"columns\": [\n"
                + "        {\n"
                + "          \"columnName\": \"OrderID\",\n"
                + "          \"columnType\": \"bigint(20)\",\n"
                + "          \"primaryKey\": true,\n"
                + "          \"autoIncrement\": true,\n"
                + "          \"columnComment\": \"Order ID\"\n"
                + "        },\n"
                + "        {\n"
                + "          \"columnName\": \"Customer Name\",\n"
                + "          \"columnType\": \"varchar(64)\",\n"
                + "          \"columnComment\": \"Customer name\"\n"
                + "        },\n"
                + "        {\n"
                + "          \"columnName\": \"客户名称\",\n"
                + "          \"columnType\": \"varchar(64)\",\n"
                + "          \"columnComment\": \"Customer name cn\"\n"
                + "        },\n"
                + "        {\n"
                + "          \"columnName\": \"customer-name\",\n"
                + "          \"columnType\": \"varchar(64)\",\n"
                + "          \"columnComment\": \"Customer name duplicate\"\n"
                + "        }\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithUnsupportedColumnTypes()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\n"
                + "      \"tableName\": \"order_info\",\n"
                + "      \"tableComment\": \"Order info\",\n"
                + "      \"columns\": [\n"
                + "        {\"columnName\": \"order_id\", \"columnType\": \"BIGINT unsigned\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"Order ID\"},\n"
                + "        {\"columnName\": \"order_status\", \"columnType\": \"INTEGER\", \"columnComment\": \"Order status\"},\n"
                + "        {\"columnName\": \"customer_name\", \"columnType\": \"string\", \"columnComment\": \"Customer name\"},\n"
                + "        {\"columnName\": \"enabled\", \"columnType\": \"boolean\", \"columnComment\": \"Enabled\"},\n"
                + "        {\"columnName\": \"created_at\", \"columnType\": \"timestamp\", \"columnComment\": \"Created time\"},\n"
                + "        {\"columnName\": \"extra_data\", \"columnType\": \"json\", \"columnComment\": \"Extra data\"},\n"
                + "        {\"columnName\": \"amount\", \"columnType\": \"float\", \"columnComment\": \"Amount\"},\n"
                + "        {\"columnName\": \"remark\", \"columnType\": \"varchar(5000) not null\", \"columnComment\": \"Remark\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithBusinessActions()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\"tableName\": \"book_info\", \"tableComment\": \"图书信息表\", \"columns\": [\n"
                + "      {\"columnName\": \"book_id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"图书ID\"},\n"
                + "      {\"columnName\": \"stock\", \"columnType\": \"int(11)\", \"columnComment\": \"库存\"}\n"
                + "    ]},\n"
                + "    {\"tableName\": \"reader_info\", \"tableComment\": \"读者信息表\", \"columns\": [\n"
                + "      {\"columnName\": \"reader_id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"读者ID\"},\n"
                + "      {\"columnName\": \"status\", \"columnType\": \"varchar(20)\", \"columnComment\": \"读者状态\"}\n"
                + "    ]},\n"
                + "    {\"tableName\": \"book_borrow\", \"tableComment\": \"借阅记录表\", \"columns\": [\n"
                + "      {\"columnName\": \"borrow_id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"借阅ID\"},\n"
                + "      {\"columnName\": \"book_id\", \"columnType\": \"bigint(20)\", \"columnComment\": \"图书ID\"},\n"
                + "      {\"columnName\": \"reader_id\", \"columnType\": \"bigint(20)\", \"columnComment\": \"读者ID\"},\n"
                + "      {\"columnName\": \"borrow_status\", \"columnType\": \"varchar(20)\", \"columnComment\": \"借阅状态\"}\n"
                + "    ]}\n"
                + "  ],\n"
                + "  \"businessActions\": [\n"
                + "    {\n"
                + "      \"code\": \"Borrow Book\",\n"
                + "      \"name\": \"借书\",\n"
                + "      \"ownerTable\": \"book_borrow\",\n"
                + "      \"method\": \"post\",\n"
                + "      \"path\": \"/library/borrow/borrow\",\n"
                + "      \"transaction\": true,\n"
                + "      \"requestFields\": [\"book_id\", \"reader_id\"],\n"
                + "      \"rules\": [\"读者状态必须正常\", \"图书库存必须大于0\"],\n"
                + "      \"effects\": [\n"
                + "        {\"type\": \"update_field\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"图书库存减一\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String businessBlueprintOnlyResponse()
    {
        return "{\n"
                + "  \"businessActions\": [\n"
                + "    {\n"
                + "      \"code\": \"Borrow Book\",\n"
                + "      \"name\": \"Borrow Book\",\n"
                + "      \"ownerTable\": \"book_borrow\",\n"
                + "      \"method\": \"post\",\n"
                + "      \"path\": \"/library/borrow/borrow\",\n"
                + "      \"transaction\": true,\n"
                + "      \"requestFields\": [\"book_id\", \"reader_id\"],\n"
                + "      \"rules\": [\"Book stock must be greater than zero\"],\n"
                + "      \"effects\": [\n"
                + "        {\"type\": \"update_field\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"Decrease stock\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String businessBlueprintWithCrudActionsResponse()
    {
        return "{\n"
                + "  \"businessActions\": [\n"
                + "    {\"code\": \"create_book\", \"name\": \"Create Book\", \"ownerTable\": \"book_info\", \"method\": \"POST\", \"path\": \"/book/create\", \"requestFields\": [\"book_id\"]},\n"
                + "    {\n"
                + "      \"code\": \"borrow_book\",\n"
                + "      \"name\": \"Borrow Book\",\n"
                + "      \"ownerTable\": \"book_borrow\",\n"
                + "      \"method\": \"POST\",\n"
                + "      \"path\": \"/library/borrow/borrow\",\n"
                + "      \"requestFields\": [\"book_id\", \"reader_id\"],\n"
                + "      \"effects\": [\n"
                + "        {\"type\": \"UPDATE_FIELD\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"Decrease stock\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String executableBusinessBlueprintResponse()
    {
        return "{\n"
                + "  \"businessActions\": [\n"
                + "    {\n"
                + "      \"code\": \"Borrow Book\",\n"
                + "      \"name\": \"Borrow Book\",\n"
                + "      \"ownerTable\": \"book_borrow\",\n"
                + "      \"method\": \"post\",\n"
                + "      \"path\": \"/library/borrow/borrow\",\n"
                + "      \"transaction\": true,\n"
                + "      \"requestFields\": [\"book_id\", \"reader_id\"],\n"
                + "      \"rules\": [\"Book must exist\", \"Stock must be enough\"],\n"
                + "      \"ruleChecks\": [\n"
                + "        {\"type\": \"exists\", \"targetTable\": \"book_info\", \"conditionFields\": [\"book_id\"], \"message\": \"Book not found\"},\n"
                + "        {\"type\": \"number_gte\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"compareValue\": \"1\", \"conditionFields\": [\"book_id\"], \"message\": \"Stock is not enough\"}\n"
                + "      ],\n"
                + "      \"effects\": [\n"
                + "        {\"type\": \"insert_row\", \"targetTable\": \"book_borrow\", \"values\": {\"book_id\": \"${param.book_id}\", \"reader_id\": \"${param.reader_id}\", \"borrow_status\": \"borrowed\"}, \"description\": \"Create borrow record\"},\n"
                + "        {\"type\": \"decrease_number\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"amount\": \"1\", \"minValue\": \"0\", \"conditionFields\": [\"book_id\"], \"message\": \"Stock decrease failed\", \"description\": \"Decrease stock\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithMissingBusinessActionRequestField()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\"tableName\": \"fine_record\", \"tableComment\": \"罚金记录表\", \"columns\": [\n"
                + "      {\"columnName\": \"id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"ID\"},\n"
                + "      {\"columnName\": \"reader_id\", \"columnType\": \"bigint(20)\", \"columnComment\": \"读者ID\"},\n"
                + "      {\"columnName\": \"fine_amount\", \"columnType\": \"decimal(10,2)\", \"columnComment\": \"罚金金额\"}\n"
                + "    ]}\n"
                + "  ],\n"
                + "  \"businessActions\": [\n"
                + "    {\"code\": \"pay_fine\", \"name\": \"缴纳罚金\", \"ownerTable\": \"fine_record\", \"method\": \"POST\", \"path\": \"/library/fine/pay\", \"requestFields\": [\"fine_record_id\", \"reader_id\"]}\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithMissingBusinessEffectTargetField()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\"tableName\": \"book_info\", \"tableComment\": \"Book info\", \"columns\": [\n"
                + "      {\"columnName\": \"book_id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"Book ID\"},\n"
                + "      {\"columnName\": \"stock\", \"columnType\": \"int(11)\", \"columnComment\": \"Stock\"}\n"
                + "    ]},\n"
                + "    {\"tableName\": \"borrow_record\", \"tableComment\": \"Borrow record\", \"columns\": [\n"
                + "      {\"columnName\": \"borrow_id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"Borrow ID\"},\n"
                + "      {\"columnName\": \"book_id\", \"columnType\": \"bigint(20)\", \"columnComment\": \"Book ID\"}\n"
                + "    ]}\n"
                + "  ],\n"
                + "  \"businessActions\": [\n"
                + "    {\"code\": \"borrow_book\", \"name\": \"Borrow Book\", \"ownerTable\": \"borrow_record\", \"method\": \"POST\", \"path\": \"/borrow\", \"requestFields\": [\"book_id\"], \"effects\": [\n"
                + "      {\"type\": \"UPDATE_FIELD\", \"targetTable\": \"book_info\", \"targetField\": \"available_stock\", \"value\": \"available_stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"Decrease available stock\"}\n"
                + "    ]}\n"
                + "  ]\n"
                + "}";
    }

    private String responseWithMissingBusinessActionTable()
    {
        return "{\n"
                + "  \"tables\": [\n"
                + "    {\"tableName\": \"book_info\", \"tableComment\": \"图书信息表\", \"columns\": [\n"
                + "      {\"columnName\": \"book_id\", \"columnType\": \"bigint(20)\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"图书ID\"}\n"
                + "    ]}\n"
                + "  ],\n"
                + "  \"businessActions\": [\n"
                + "    {\"code\": \"borrow\", \"name\": \"借书\", \"ownerTable\": \"missing_table\", \"method\": \"POST\", \"path\": \"/library/borrow\", \"requestFields\": [\"book_id\"]}\n"
                + "  ]\n"
                + "}";
    }

    private String ruleCheckWithMissingTargetFieldResponse()
    {
        return "{\n"
                + "  \"businessActions\": [\n"
                + "    {\n"
                + "      \"code\": \"borrow_book\",\n"
                + "      \"name\": \"Borrow Book\",\n"
                + "      \"ownerTable\": \"book_borrow\",\n"
                + "      \"method\": \"POST\",\n"
                + "      \"path\": \"/library/borrow/borrow\",\n"
                + "      \"requestFields\": [\"book_id\", \"reader_id\"],\n"
                + "      \"ruleChecks\": [\n"
                + "        {\"type\": \"NUMBER_GTE\", \"targetTable\": \"book_info\", \"targetField\": \"missing_stock\", \"compareValue\": \"1\", \"conditionFields\": [\"book_id\"], \"message\": \"Stock is not enough\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }

    private String effectValueWithUnknownParamResponse()
    {
        return "{\n"
                + "  \"businessActions\": [\n"
                + "    {\n"
                + "      \"code\": \"borrow_book\",\n"
                + "      \"name\": \"Borrow Book\",\n"
                + "      \"ownerTable\": \"book_borrow\",\n"
                + "      \"method\": \"POST\",\n"
                + "      \"path\": \"/library/borrow/borrow\",\n"
                + "      \"requestFields\": [\"book_id\", \"reader_id\"],\n"
                + "      \"effects\": [\n"
                + "        {\"type\": \"INSERT_ROW\", \"targetTable\": \"book_borrow\", \"values\": {\"book_id\": \"${param.book_id}\", \"reader_id\": \"${param.missing_reader_id}\"}, \"description\": \"Create borrow record\"}\n"
                + "      ]\n"
                + "    }\n"
                + "  ]\n"
                + "}";
    }
}
