package com.ruoyi.generator.service.front;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
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.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.generator.domain.front.FrontProject;
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
import com.ruoyi.generator.domain.front.dto.FrontProjectCreateRequest;
import com.ruoyi.generator.mapper.front.FrontProjectModuleMapper;
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
import com.ruoyi.generator.mapper.front.FrontProjectFileMapper;
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
import com.ruoyi.generator.service.ISysModuleService;

public class FrontProjectServiceImplTest
{
    private FrontProjectServiceImpl service;

    @Mock
    private FrontProjectMapper frontProjectMapper;
    @Mock
    private FrontProjectTableMapper frontProjectTableMapper;
    @Mock
    private FrontProjectColumnMapper frontProjectColumnMapper;
    @Mock
    private FrontProjectFileMapper frontProjectFileMapper;
    @Mock
    private FrontProjectGenerationMapper frontProjectGenerationMapper;
    @Mock
    private FrontProjectModuleMapper frontProjectModuleMapper;
    @Mock
    private ISysModuleService sysModuleService;

    @Before
    public void setUp() throws Exception
    {
        MockitoAnnotations.initMocks(this);
        service = new FrontProjectServiceImpl();
        setField("frontProjectMapper", frontProjectMapper);
        setField("frontProjectTableMapper", frontProjectTableMapper);
        setField("frontProjectColumnMapper", frontProjectColumnMapper);
        setField("frontProjectFileMapper", frontProjectFileMapper);
        setField("frontProjectGenerationMapper", frontProjectGenerationMapper);
        setField("frontProjectModuleMapper", frontProjectModuleMapper);
        setField("sysModuleService", sysModuleService);
    }

    @Test
    public void createProjectUsesSafeDefaults()
    {
        FrontProjectCreateRequest request = new FrontProjectCreateRequest();
        request.setProjectName("车辆管理系统");

        request.setRequirementKeyword("borrow return books");

        FrontProject project = service.createProject(7L, request);

        assertEquals(Long.valueOf(7L), project.getUserId());
        assertEquals("borrow return books", project.getRequirementKeyword());
        assertEquals("com.easycode.generated", project.getPackageName());
        assertEquals("1.0.0", project.getVersion());
        assertEquals("Vue3 + Element Plus", project.getFrontFramework());
        assertEquals("Spring Boot + MyBatis", project.getBackFramework());
        assertEquals("1", project.getFrontendEnabled());
        assertEquals("0", project.getGenerateStatus());
        assertEquals("0", project.getPreviewStatus());
        assertEquals("0", project.getStatus());
        assertTrue(project.getProjectFileName().startsWith("easycode-project-"));
        verify(frontProjectMapper).insertFrontProject(project);
    }

    @Test
    public void createProjectPersistsFrontendSwitchAndSelectedModules()
    {
        org.mockito.Mockito.doAnswer(invocation -> {
            FrontProject inserted = invocation.getArgument(0);
            inserted.setProjectId(88L);
            return 1;
        }).when(frontProjectMapper).insertFrontProject(any(FrontProject.class));
        FrontProjectCreateRequest request = new FrontProjectCreateRequest();
        request.setProjectName("客户中心");
        request.setFrontendEnabled(Boolean.FALSE);
        request.setModuleIds(Arrays.asList(11L, 22L));

        FrontProject project = service.createProject(7L, request);

        assertEquals("0", project.getFrontendEnabled());
        verify(frontProjectMapper).insertFrontProject(project);
        verify(frontProjectModuleMapper).deleteModulesByProjectId(88L);
        ArgumentCaptor<com.ruoyi.generator.domain.front.FrontProjectModule> moduleCaptor =
                ArgumentCaptor.forClass(com.ruoyi.generator.domain.front.FrontProjectModule.class);
        verify(frontProjectModuleMapper, org.mockito.Mockito.times(2)).insertFrontProjectModule(moduleCaptor.capture());
        assertEquals(Long.valueOf(11L), moduleCaptor.getAllValues().get(0).getModuleId());
        assertEquals(Long.valueOf(22L), moduleCaptor.getAllValues().get(1).getModuleId());
        assertEquals("0", moduleCaptor.getAllValues().get(0).getStatus());
    }

    @Test
    public void saveDatabaseRejectsInvalidTableName()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.saveDatabase(7L, 10L, database(table("Sys_User", pkColumn())));
            }
        });

        assertEquals("表名只能包含小写字母、数字和下划线", exception.getMessage());
        verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(any(Long.class));
        verify(frontProjectTableMapper, never()).deleteTablesByProjectId(any(Long.class));
    }

    @Test
    public void saveDatabaseRejectsMissingPrimaryKey()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.saveDatabase(7L, 10L, database(table("sys_user", column("user_name", "varchar(64)", "0"))));
            }
        });

        assertEquals("每张表必须且只能有一个主键", exception.getMessage());
        verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(any(Long.class));
        verify(frontProjectTableMapper, never()).deleteTablesByProjectId(any(Long.class));
    }

    @Test
    public void saveDatabaseRejectsColumnTypeWithSuffix()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.saveDatabase(7L, 10L, database(table("sys_user", column("id", "varchar(64) not null", "1"))));
            }
        });

        assertEquals("字段类型不在允许范围内", exception.getMessage());
        verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(any(Long.class));
        verify(frontProjectTableMapper, never()).deleteTablesByProjectId(any(Long.class));
    }

    @Test
    public void saveDatabaseRejectsJsonColumnType()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());

        ServiceException exception = expectServiceException(new ThrowingRunnable()
        {
            @Override
            public void run()
            {
                service.saveDatabase(7L, 10L, database(table("sys_user", column("id", "json", "1"))));
            }
        });

        assertEquals("字段类型不在允许范围内", exception.getMessage());
        verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(any(Long.class));
        verify(frontProjectTableMapper, never()).deleteTablesByProjectId(any(Long.class));
    }

    @Test
    public void saveDatabasePersistsValidDraft()
    {
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.emptyList());
        DatabaseDesignResponse request = database(table("sys_user", pkColumn(), column("user_name", "varchar(64)", "0")));
        request.setErDiagram(Collections.singletonMap("relations", Collections.singletonList("student_department")));
        request.setBusinessActions(Collections.singletonList(businessAction()));

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

        ArgumentCaptor<com.ruoyi.generator.domain.front.FrontProjectTable> tableCaptor =
                ArgumentCaptor.forClass(com.ruoyi.generator.domain.front.FrontProjectTable.class);
        verify(frontProjectTableMapper).insertFrontProjectTable(tableCaptor.capture());
        assertEquals(Long.valueOf(7L), tableCaptor.getValue().getUserId());
        assertEquals(Long.valueOf(10L), tableCaptor.getValue().getProjectId());
        verify(frontProjectColumnMapper).deleteColumnsByProjectId(10L);
        verify(frontProjectTableMapper).deleteTablesByProjectId(10L);
        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());
        assertTrue(projectCaptor.getValue().getErDiagram().contains("student_department"));
        assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("borrow_book"));
    }

    @Test
    public void getDatabaseReturnsErDiagramDraft()
    {
        FrontProject project = project();
        project.setErDiagram("{\"positions\":{\"department\":{\"x\":24,\"y\":48}}}");
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project);
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.emptyList());

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

        @SuppressWarnings("unchecked")
        Map<String, Object> erDiagram = (Map<String, Object>) response.getErDiagram();
        assertTrue(erDiagram.containsKey("positions"));
    }

    @Test
    public void getDatabaseReturnsBusinessActionDraft()
    {
        FrontProject project = project();
        project.setBusinessBlueprint("[{\"code\":\"borrow_book\",\"name\":\"借书\",\"ownerTable\":\"book_borrow\",\"method\":\"POST\",\"path\":\"/library/borrow/borrow\"}]");
        when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project);
        when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.emptyList());

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

        List<BusinessActionDesign> actions = response.getBusinessActions();
        assertEquals(1, actions.size());
        assertEquals("borrow_book", actions.get(0).getCode());
        assertEquals("借书", actions.get(0).getName());
    }

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

    private FrontProject project()
    {
        FrontProject project = new FrontProject();
        project.setProjectId(10L);
        project.setUserId(7L);
        project.setProjectName("Demo");
        return project;
    }

    private DatabaseDesignResponse database(DatabaseTableDesign table)
    {
        DatabaseDesignResponse response = new DatabaseDesignResponse();
        response.setTables(Collections.singletonList(table));
        return response;
    }

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

    private DatabaseColumnDesign pkColumn()
    {
        return column("id", "bigint", "1");
    }

    private DatabaseColumnDesign column(String columnName, String columnType, String isPk)
    {
        DatabaseColumnDesign column = new DatabaseColumnDesign();
        column.setColumnName(columnName);
        column.setColumnType(columnType);
        column.setIsPk(isPk);
        return column;
    }

    private BusinessActionDesign businessAction()
    {
        BusinessActionDesign action = new BusinessActionDesign();
        action.setCode("borrow_book");
        action.setName("借书");
        action.setOwnerTable("sys_user");
        action.setMethod("POST");
        action.setPath("/library/borrow/borrow");
        action.setTransaction(Boolean.TRUE);
        action.setRequestFields(Collections.singletonList("id"));
        action.setRules(Collections.singletonList("用户状态正常"));
        return action;
    }

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

    private interface ThrowingRunnable
    {
        void run();
    }
}
