package com.ruoyi.generator.service.front;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.domain.SysModule;
import com.ruoyi.generator.domain.SysProjectModule;
import com.ruoyi.generator.domain.front.FrontProject;
import com.ruoyi.generator.domain.front.FrontProjectColumn;
import com.ruoyi.generator.domain.front.FrontProjectModule;
import com.ruoyi.generator.domain.front.FrontProjectTable;
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.domain.front.dto.FrontProjectUpdateRequest;
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.FrontProjectModuleMapper;
import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
import com.ruoyi.generator.service.ISysModuleService;

@Service
public class FrontProjectServiceImpl implements IFrontProjectService
{
    private static final Pattern SAFE_PROJECT_FILE_NAME = Pattern.compile("^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$");
    private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
    private static final Pattern BIGINT_TYPE_PATTERN = Pattern.compile("^bigint(\\(20\\))?$");
    private static final Pattern INT_TYPE_PATTERN = Pattern.compile("^int(\\(11\\))?$");
    private static final Pattern VARCHAR_TYPE_PATTERN = Pattern.compile("^varchar\\((\\d{1,5})\\)$");
    private static final Pattern CHAR_TYPE_PATTERN = Pattern.compile("^char\\((\\d{1,3})\\)$");
    private static final Pattern DECIMAL_TYPE_PATTERN = Pattern.compile("^decimal\\((\\d{1,2}),(\\d{1,2})\\)$");
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

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

    @Override
    @Transactional
    public FrontProject createProject(Long userId, FrontProjectCreateRequest request)
    {
        assertFrontUserLoggedIn(userId);
        if (request == null || StringUtils.isEmpty(request.getProjectName()))
        {
            throw new ServiceException("项目名称不能为空");
        }
        FrontProject project = new FrontProject();
        project.setUserId(userId);
        project.setProjectName(request.getProjectName());
        project.setProjectDesc(request.getProjectDesc());
        project.setRequirementKeyword(request.getRequirementKeyword());
        project.setIndustryTemplate(request.getIndustryTemplate());
        project.setPackageName(StringUtils.defaultIfEmpty(request.getPackageName(), "com.easycode.generated"));
        project.setVersion(StringUtils.defaultIfEmpty(request.getVersion(), "1.0.0"));
        project.setProjectFileName(toProjectFileName(request.getProjectName()));
        project.setFrontFramework("Vue3 + Element Plus");
        project.setBackFramework("Spring Boot + MyBatis");
        project.setFrontendEnabled(toFrontendEnabled(request.getFrontendEnabled()));
        project.setGenerateStatus("0");
        project.setPreviewStatus("0");
        project.setStatus("0");
        frontProjectMapper.insertFrontProject(project);
        saveProjectModules(project.getProjectId(), request.getModuleIds());
        attachProjectModules(project);
        return project;
    }

    @Override
    public List<FrontProject> listProjects(Long userId)
    {
        assertFrontUserLoggedIn(userId);
        FrontProject query = new FrontProject();
        query.setUserId(userId);
        List<FrontProject> projects = frontProjectMapper.selectFrontProjectList(query);
        if (projects != null)
        {
            for (FrontProject project : projects)
            {
                attachProjectModules(project);
            }
        }
        return projects;
    }

    @Override
    public FrontProject getProject(Long userId, Long projectId)
    {
        assertFrontUserLoggedIn(userId);
        FrontProject project = frontProjectMapper.selectFrontProjectByUserAndId(userId, projectId);
        if (project == null)
        {
            throw new ServiceException("项目不存在或无权限");
        }
        attachProjectModules(project);
        return project;
    }

    @Override
    @Transactional
    public int updateProject(Long userId, Long projectId, FrontProjectUpdateRequest request)
    {
        assertFrontUserLoggedIn(userId);
        if (request == null)
        {
            throw new ServiceException("项目参数不能为空");
        }
        getProject(userId, projectId);
        FrontProject project = new FrontProject();
        project.setProjectId(projectId);
        project.setUserId(userId);
        project.setProjectName(request.getProjectName());
        project.setProjectDesc(request.getProjectDesc());
        project.setRequirementKeyword(request.getRequirementKeyword());
        project.setIndustryTemplate(request.getIndustryTemplate());
        project.setPackageName(request.getPackageName());
        project.setVersion(request.getVersion());
        project.setFrontendEnabled(toNullableFrontendEnabled(request.getFrontendEnabled()));
        int rows = frontProjectMapper.updateFrontProject(project);
        if (request.getModuleIds() != null)
        {
            saveProjectModules(projectId, request.getModuleIds());
        }
        return rows;
    }

    @Override
    @Transactional
    public int deleteProject(Long userId, Long projectId)
    {
        assertFrontUserLoggedIn(userId);
        getProject(userId, projectId);
        frontProjectFileMapper.deleteFilesByProjectId(projectId);
        frontProjectGenerationMapper.deleteGenerationsByProjectId(projectId);
        frontProjectModuleMapper.deleteModulesByProjectId(projectId);
        frontProjectColumnMapper.deleteColumnsByProjectId(projectId);
        frontProjectTableMapper.deleteTablesByProjectId(projectId);
        return frontProjectMapper.deleteFrontProjectByUserAndId(userId, projectId);
    }

    @Override
    public DatabaseDesignResponse getDatabase(Long userId, Long projectId)
    {
        assertFrontUserLoggedIn(userId);
        FrontProject project = getProject(userId, projectId);
        DatabaseDesignResponse response = new DatabaseDesignResponse();
        response.setProjectId(projectId);
        List<DatabaseTableDesign> tables = new ArrayList<DatabaseTableDesign>();
        List<FrontProjectTable> projectTables = frontProjectTableMapper.selectTablesByProjectId(projectId);
        if (projectTables == null)
        {
            projectTables = new ArrayList<FrontProjectTable>();
        }
        for (FrontProjectTable table : projectTables)
        {
            List<FrontProjectColumn> columns = frontProjectColumnMapper.selectColumnsByTableId(table.getTableId());
            table.setColumns(columns);
            tables.add(toTableDesign(table));
        }
        response.setTables(tables);
        response.setSql(joinCreateTableSql(tables));
        response.setErDiagram(readJson(project.getErDiagram()));
        response.setBusinessActions(readBusinessActions(project.getBusinessBlueprint()));
        return response;
    }

    @Override
    @Transactional
    public DatabaseDesignResponse saveDatabase(Long userId, Long projectId, DatabaseDesignResponse request)
    {
        assertFrontUserLoggedIn(userId);
        getProject(userId, projectId);
        validateDatabaseDesign(request);
        frontProjectColumnMapper.deleteColumnsByProjectId(projectId);
        frontProjectTableMapper.deleteTablesByProjectId(projectId);
        for (DatabaseTableDesign tableDesign : request.getTables())
        {
            FrontProjectTable table = toFrontProjectTable(projectId, userId, tableDesign);
            frontProjectTableMapper.insertFrontProjectTable(table);
            for (DatabaseColumnDesign columnDesign : tableDesign.getColumns())
            {
                FrontProjectColumn column = toFrontProjectColumn(projectId, table.getTableId(), columnDesign);
                frontProjectColumnMapper.insertFrontProjectColumn(column);
            }
        }
        markDatabaseGenerated(projectId, writeJson(request.getErDiagram()), writeBusinessActions(request.getBusinessActions()));
        return getDatabase(userId, projectId);
    }

    @Override
    public List<SysModule> listAvailableModules()
    {
        SysModule query = new SysModule();
        query.setStatus("0");
        query.setDelFlag("0");
        return sysModuleService.selectSysModuleList(query);
    }

    private void assertFrontUserLoggedIn(Long userId)
    {
        if (userId == null)
        {
            throw new ServiceException("前台用户未登录");
        }
    }

    private String toProjectFileName(String projectName)
    {
        String fileName = StringUtils.defaultString(projectName).trim()
                .replaceAll("[^A-Za-z0-9]+", "-")
                .replaceAll("^-+|-+$", "")
                .replaceAll("-{2,}", "-")
                .toLowerCase(Locale.ENGLISH);
        if (!SAFE_PROJECT_FILE_NAME.matcher(fileName).matches())
        {
            return "easycode-project-" + System.currentTimeMillis();
        }
        return fileName;
    }

    private String toFrontendEnabled(Boolean frontendEnabled)
    {
        return Boolean.FALSE.equals(frontendEnabled) ? "0" : "1";
    }

    private String toNullableFrontendEnabled(Boolean frontendEnabled)
    {
        return frontendEnabled == null ? null : toFrontendEnabled(frontendEnabled);
    }

    private void attachProjectModules(FrontProject project)
    {
        if (project == null || project.getProjectId() == null || frontProjectModuleMapper == null)
        {
            return;
        }
        List<SysProjectModule> modules = frontProjectModuleMapper.selectSysProjectModulesByProjectId(project.getProjectId());
        project.setModules(modules);
        List<Long> moduleIds = new ArrayList<Long>();
        if (modules != null)
        {
            for (SysProjectModule module : modules)
            {
                if (module != null && module.getModuleId() != null)
                {
                    moduleIds.add(module.getModuleId());
                }
            }
        }
        project.setModuleIds(moduleIds);
    }

    private void saveProjectModules(Long projectId, List<Long> moduleIds)
    {
        if (projectId == null)
        {
            return;
        }
        frontProjectModuleMapper.deleteModulesByProjectId(projectId);
        if (moduleIds == null || moduleIds.isEmpty())
        {
            return;
        }
        Set<Long> uniqueModuleIds = new LinkedHashSet<Long>(moduleIds);
        for (Long moduleId : uniqueModuleIds)
        {
            if (moduleId == null)
            {
                continue;
            }
            FrontProjectModule module = new FrontProjectModule();
            module.setProjectId(projectId);
            module.setModuleId(moduleId);
            module.setStatus("0");
            module.setCreateTime(DateUtils.getNowDate());
            frontProjectModuleMapper.insertFrontProjectModule(module);
        }
    }

    private void validateDatabaseDesign(DatabaseDesignResponse request)
    {
        if (request == null || request.getTables() == null || request.getTables().isEmpty())
        {
            throw new ServiceException("至少需要一张数据表");
        }
        for (DatabaseTableDesign table : request.getTables())
        {
            if (table == null || !isValidDatabaseName(table.getTableName()))
            {
                throw new ServiceException("表名只能包含小写字母、数字和下划线");
            }
            if (table.getColumns() == null || table.getColumns().isEmpty())
            {
                throw new ServiceException("每张表至少需要一个字段");
            }
            int primaryKeyCount = 0;
            for (DatabaseColumnDesign column : table.getColumns())
            {
                if (column == null || !isValidDatabaseName(column.getColumnName()))
                {
                    throw new ServiceException("字段名只能包含小写字母、数字和下划线");
                }
                if (!isAllowedColumnType(column.getColumnType()))
                {
                    throw new ServiceException("字段类型不在允许范围内");
                }
                if ("1".equals(column.getIsPk()))
                {
                    primaryKeyCount++;
                }
            }
            if (primaryKeyCount != 1)
            {
                throw new ServiceException("每张表必须且只能有一个主键");
            }
        }
    }

    private boolean isValidDatabaseName(String name)
    {
        return StringUtils.isNotEmpty(name) && DB_NAME_PATTERN.matcher(name).matches();
    }

    private boolean isAllowedColumnType(String columnType)
    {
        if (StringUtils.isEmpty(columnType))
        {
            return false;
        }
        String normalizedType = columnType.trim().toLowerCase(Locale.ENGLISH);
        if ("datetime".equals(normalizedType) || "date".equals(normalizedType)
                || "text".equals(normalizedType) || "longtext".equals(normalizedType))
        {
            return true;
        }
        if (BIGINT_TYPE_PATTERN.matcher(normalizedType).matches()
                || INT_TYPE_PATTERN.matcher(normalizedType).matches())
        {
            return true;
        }
        Matcher varcharMatcher = VARCHAR_TYPE_PATTERN.matcher(normalizedType);
        if (varcharMatcher.matches())
        {
            int length = Integer.parseInt(varcharMatcher.group(1));
            return length >= 1 && length <= 65535;
        }
        Matcher charMatcher = CHAR_TYPE_PATTERN.matcher(normalizedType);
        if (charMatcher.matches())
        {
            int length = Integer.parseInt(charMatcher.group(1));
            return length >= 1 && length <= 255;
        }
        Matcher decimalMatcher = DECIMAL_TYPE_PATTERN.matcher(normalizedType);
        if (decimalMatcher.matches())
        {
            int precision = Integer.parseInt(decimalMatcher.group(1));
            int scale = Integer.parseInt(decimalMatcher.group(2));
            return precision >= 1 && precision <= 65 && scale >= 0 && scale <= 30 && scale <= precision;
        }
        return false;
    }

    private String joinCreateTableSql(List<DatabaseTableDesign> tables)
    {
        List<String> sqlList = new ArrayList<String>();
        for (DatabaseTableDesign table : tables)
        {
            if (StringUtils.isNotEmpty(table.getCreateTableSql()))
            {
                sqlList.add(table.getCreateTableSql());
            }
        }
        return StringUtils.join(sqlList, "\n\n");
    }

    private void markDatabaseGenerated(Long projectId, String erDiagram, String businessBlueprint)
    {
        FrontProject project = new FrontProject();
        project.setProjectId(projectId);
        project.setGenerateStatus("1");
        project.setPreviewStatus("0");
        project.setErDiagram(erDiagram);
        project.setBusinessBlueprint(businessBlueprint);
        frontProjectMapper.updateFrontProject(project);
    }

    private Object readJson(String json)
    {
        if (StringUtils.isEmpty(json))
        {
            return null;
        }
        try
        {
            return OBJECT_MAPPER.readValue(json, Object.class);
        }
        catch (Exception e)
        {
            return null;
        }
    }

    private String writeJson(Object value)
    {
        if (value == null)
        {
            return null;
        }
        try
        {
            return value instanceof String ? (String) value : OBJECT_MAPPER.writeValueAsString(value);
        }
        catch (Exception e)
        {
            throw new ServiceException("ER图草稿保存失败");
        }
    }

    private List<BusinessActionDesign> readBusinessActions(String json)
    {
        if (StringUtils.isEmpty(json))
        {
            return new ArrayList<BusinessActionDesign>();
        }
        try
        {
            return OBJECT_MAPPER.readValue(json, new TypeReference<List<BusinessActionDesign>>() { });
        }
        catch (Exception e)
        {
            return new ArrayList<BusinessActionDesign>();
        }
    }

    private String writeBusinessActions(List<BusinessActionDesign> actions)
    {
        try
        {
            return OBJECT_MAPPER.writeValueAsString(actions == null ? new ArrayList<BusinessActionDesign>() : actions);
        }
        catch (Exception e)
        {
            throw new ServiceException("业务蓝图草稿保存失败");
        }
    }

    private FrontProjectTable toFrontProjectTable(Long projectId, Long userId, DatabaseTableDesign source)
    {
        FrontProjectTable table = new FrontProjectTable();
        table.setProjectId(projectId);
        table.setUserId(userId);
        table.setTableName(source.getTableName());
        table.setTableComment(source.getTableComment());
        table.setClassName(source.getClassName());
        table.setModuleName(source.getModuleName());
        table.setBusinessName(source.getBusinessName());
        table.setFunctionName(source.getFunctionName());
        table.setTplCategory(StringUtils.defaultIfEmpty(source.getTplCategory(), "crud"));
        table.setTplWebType(StringUtils.defaultIfEmpty(source.getTplWebType(), "element-plus"));
        table.setOptions(source.getOptions());
        table.setCreateTableSql(source.getCreateTableSql());
        table.setSort(source.getSort());
        table.setStatus("0");
        return table;
    }

    private FrontProjectColumn toFrontProjectColumn(Long projectId, Long tableId, DatabaseColumnDesign source)
    {
        FrontProjectColumn column = new FrontProjectColumn();
        column.setProjectId(projectId);
        column.setTableId(tableId);
        column.setColumnName(source.getColumnName());
        column.setColumnComment(source.getColumnComment());
        column.setColumnType(source.getColumnType());
        column.setJavaType(source.getJavaType());
        column.setJavaField(source.getJavaField());
        column.setIsPk(source.getIsPk());
        column.setIsIncrement(source.getIsIncrement());
        column.setIsRequired(source.getIsRequired());
        column.setIsInsert(source.getIsInsert());
        column.setIsEdit(source.getIsEdit());
        column.setIsList(source.getIsList());
        column.setIsQuery(source.getIsQuery());
        column.setQueryType(source.getQueryType());
        column.setHtmlType(source.getHtmlType());
        column.setDictType(source.getDictType());
        column.setDefaultValue(source.getDefaultValue());
        column.setSort(source.getSort());
        return column;
    }

    private DatabaseTableDesign toTableDesign(FrontProjectTable source)
    {
        DatabaseTableDesign table = new DatabaseTableDesign();
        table.setTableId(source.getTableId());
        table.setTableName(source.getTableName());
        table.setTableComment(source.getTableComment());
        table.setClassName(source.getClassName());
        table.setModuleName(source.getModuleName());
        table.setBusinessName(source.getBusinessName());
        table.setFunctionName(source.getFunctionName());
        table.setTplCategory(source.getTplCategory());
        table.setTplWebType(source.getTplWebType());
        table.setOptions(source.getOptions());
        table.setCreateTableSql(source.getCreateTableSql());
        table.setSort(source.getSort());
        List<DatabaseColumnDesign> columns = new ArrayList<DatabaseColumnDesign>();
        if (source.getColumns() != null)
        {
            for (FrontProjectColumn column : source.getColumns())
            {
                columns.add(toColumnDesign(column));
            }
        }
        table.setColumns(columns);
        return table;
    }

    private DatabaseColumnDesign toColumnDesign(FrontProjectColumn source)
    {
        DatabaseColumnDesign column = new DatabaseColumnDesign();
        column.setColumnId(source.getColumnId());
        column.setTableId(source.getTableId());
        column.setColumnName(source.getColumnName());
        column.setColumnComment(source.getColumnComment());
        column.setColumnType(source.getColumnType());
        column.setJavaType(source.getJavaType());
        column.setJavaField(source.getJavaField());
        column.setIsPk(source.getIsPk());
        column.setIsIncrement(source.getIsIncrement());
        column.setIsRequired(source.getIsRequired());
        column.setIsInsert(source.getIsInsert());
        column.setIsEdit(source.getIsEdit());
        column.setIsList(source.getIsList());
        column.setIsQuery(source.getIsQuery());
        column.setQueryType(source.getQueryType());
        column.setHtmlType(source.getHtmlType());
        column.setDictType(source.getDictType());
        column.setDefaultValue(source.getDefaultValue());
        column.setSort(source.getSort());
        return column;
    }
}
