package com.ruoyi.generator.service;

import com.ruoyi.generator.domain.GenProject;
import com.ruoyi.generator.domain.GenTable;
import com.ruoyi.generator.domain.GenTableColumn;
import com.ruoyi.generator.domain.SysCodeSnippet;
import com.ruoyi.generator.domain.SysProjectModule;
import com.ruoyi.generator.domain.SysProjectStructure;
import com.ruoyi.generator.domain.SysProjectTemplate;
import com.ruoyi.generator.domain.TemplateFile;
import com.ruoyi.generator.mapper.GenProjectMapper;
import com.ruoyi.generator.mapper.GenTableMapper;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

public class GenProjectServiceImplTest {
    private GenProjectServiceImpl service;

    @Mock
    private GenProjectMapper genProjectMapper;
    @Mock
    private GenTableMapper genTableMapper;
    @Mock
    private ITemplateFileService templateFileService;
    @Mock
    private ISysCodeSnippetService codeSnippetService;
    @Mock
    private ISysProjectModuleService sysProjectModuleService;
    @Mock
    private ISysProjectTemplateService sysProjectTemplateService;
    @Mock
    private ISysProjectStructureService sysProjectStructureService;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        service = new GenProjectServiceImpl();
        setField("genProjectMapper", genProjectMapper);
        setField("genTableMapper", genTableMapper);
        setField("templateFileService", templateFileService);
        setField("codeSnippetService", codeSnippetService);
        setField("sysProjectModuleService", sysProjectModuleService);
        setField("sysProjectTemplateService", sysProjectTemplateService);
        setField("sysProjectStructureService", sysProjectStructureService);
    }

    @Test
    public void getProjectStructureAttachesTableScopedChildrenToEachGeneratedTableFolder() {
        GenProject project = project();
        GenTable userTable = table(11L, "sys_user", "User");
        GenTable roleTable = table(12L, "sys_role", "Role");
        SysProjectTemplate projectTemplate = projectTemplate(100L);

        when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project);
        when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(userTable, roleTable));
        when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(projectTemplate);
        when(sysProjectStructureService.selectProjectStructureListByModule(1L, 100L)).thenReturn(Arrays.asList(
                structure(1L, 0L, "{projectName}", "folder", null, null),
                structure(2L, 1L, "src", "folder", null, null),
                structure(3L, 2L, "{ClassName}", "folder", 0L, null),
                structure(4L, 3L, "{ClassName}Controller.java", "file", 0L, "controller.java.vm")
        ));

        List<Map<String, Object>> structure = service.getProjectStructure(1L, "backend");

        Map<String, Object> root = structure.get(0);
        assertEquals("demo-app", root.get("name"));
        Map<String, Object> src = child(root, 0);
        Map<String, Object> userFolder = child(src, 0);
        Map<String, Object> roleFolder = child(src, 1);
        assertEquals("User", userFolder.get("name"));
        assertEquals("Role", roleFolder.get("name"));
        assertEquals("UserController.java", child(userFolder, 0).get("name"));
        assertEquals("RoleController.java", child(roleFolder, 0).get("name"));
    }

    @Test
    public void previewCodeUsesExactTemplateFileNameMatch() {
        GenProject project = project();
        GenTable table = table(11L, "sys_user", "User");

        when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project);
        when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table));
        when(genTableMapper.selectGenTableById(11L)).thenReturn(table);
        when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList(projectModule(1L)));
        when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList());
        when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(projectTemplate(100L));
        when(templateFileService.selectTemplateFilesByTemplateId(100L)).thenReturn(Arrays.asList(
                templateFile("controller.java.vm", "controller"),
                templateFile("my-controller.java.vm", "wrong")
        ));

        Map<String, String> preview = service.previewCode(1L, 11L, "controller.java.vm", "backend");

        assertEquals(1, preview.size());
        assertTrue(preview.containsKey("controller.java.vm"));
        assertFalse(preview.containsKey("my-controller.java.vm"));
    }

    @Test
    public void previewCodeAppliesOnlyMatchingActiveSnippetToTemplateInsertPoint() {
        GenProject project = project();
        GenTable table = table(11L, "sys_user", "User");
        SysCodeSnippet matching = snippet(1L, "sys_user", "controller.java.vm", "controllerExtraMethods", "public void audit() {}");
        SysCodeSnippet unrelatedTable = snippet(1L, "sys_role", "controller.java.vm", "controllerExtraMethods", "public void wrong() {}");

        when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project);
        when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table));
        when(genTableMapper.selectGenTableById(11L)).thenReturn(table);
        when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList(projectModule(1L)));
        when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList(matching, unrelatedTable));
        when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(projectTemplate(100L));
        when(templateFileService.selectTemplateFilesByTemplateId(100L)).thenReturn(Arrays.asList(
                templateFile("controller.java.vm", "class UserController { $controllerExtraMethods }")
        ));

        Map<String, String> preview = service.previewCode(1L, 11L, "controller.java.vm", "backend");

        String content = preview.get("controller.java.vm");
        assertTrue(content.contains("public void audit() {}"));
        assertFalse(content.contains("public void wrong() {}"));
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> child(Map<String, Object> node, int index) {
        return ((List<Map<String, Object>>) node.get("children")).get(index);
    }

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

    private GenProject project() {
        GenProject project = new GenProject();
        project.setProjectId(1L);
        project.setProjectName("Demo");
        project.setProjectFileName("demo-app");
        project.setPackageName("com.example.demo");
        project.setTables(Arrays.asList(table(11L, "sys_user", "User")));
        return project;
    }

    private GenTable table(Long tableId, String tableName, String className) {
        GenTable table = new GenTable();
        table.setTableId(tableId);
        table.setTableName(tableName);
        table.setTableComment(className);
        table.setClassName(className);
        table.setModuleName("system");
        table.setBusinessName(className.toLowerCase());
        table.setFunctionName(className);
        table.setFunctionAuthor("tester");
        table.setTplCategory("crud");
        table.setOptions("{}");
        table.setColumns(Arrays.asList(pkColumn(tableId)));
        return table;
    }

    private GenTableColumn pkColumn(Long tableId) {
        GenTableColumn column = new GenTableColumn();
        column.setTableId(tableId);
        column.setColumnName("id");
        column.setColumnType("bigint");
        column.setJavaType("Long");
        column.setJavaField("id");
        column.setIsPk("1");
        return column;
    }

    private SysProjectTemplate projectTemplate(Long templateId) {
        SysProjectTemplate projectTemplate = new SysProjectTemplate();
        projectTemplate.setProjectId(1L);
        projectTemplate.setTemplateId(templateId);
        projectTemplate.setTemplateType("backend");
        return projectTemplate;
    }

    private SysProjectModule projectModule(Long moduleId) {
        SysProjectModule module = new SysProjectModule();
        module.setProjectId(1L);
        module.setModuleId(moduleId);
        module.setStatus("0");
        return module;
    }

    private SysProjectStructure structure(Long nodeId, Long parentId, String name, String type, Long tableId, String category) {
        SysProjectStructure structure = new SysProjectStructure();
        structure.setNodeId(nodeId);
        structure.setParentId(parentId);
        structure.setNodeName(name);
        structure.setNodeType(type);
        structure.setTemplateId(100L);
        structure.setTableId(tableId);
        structure.setCategory(category);
        structure.setStatus("0");
        return structure;
    }

    private TemplateFile templateFile(String fileName, String fileContent) {
        TemplateFile templateFile = new TemplateFile();
        templateFile.setTemplateId(100L);
        templateFile.setFileName(fileName);
        templateFile.setFileContent(fileContent);
        return templateFile;
    }

    private SysCodeSnippet snippet(Long moduleId, String relationTableName, String templateFileName, String insertPoint, String content) {
        SysCodeSnippet snippet = new SysCodeSnippet();
        snippet.setModuleId(moduleId);
        snippet.setRelationTableName(relationTableName);
        snippet.setTemplateFileName(templateFileName);
        snippet.setInsertPoint(insertPoint);
        snippet.setSnippetContent(content);
        snippet.setStatus("0");
        snippet.setDelFlag("0");
        return snippet;
    }
}
