package com.salpa.subject.service.impl;

import com.alibaba.fastjson2.JSON;
import com.salpa.common.core.domain.AjaxResult;
import com.salpa.common.core.domain.entity.SysUser;
import com.salpa.common.utils.SecurityUtils;
import com.salpa.subject.domain.*;
import com.salpa.subject.domain.data.DatabaseDocumentResponse;
import com.salpa.subject.domain.data.DatabaseDocumentSimpleResponse;
import com.salpa.subject.domain.data.ProjectSyncTaskStatus;
import com.salpa.subject.domain.data.TableDocumentResponse;
import com.salpa.subject.domain.diff.DatabaseDocDiff;
import com.salpa.subject.domain.diff.DiffTypePredictor;
import com.salpa.subject.domain.diff.DocumentDiffChecker;
import com.salpa.subject.domain.diff.TableDocDiff;
import com.salpa.subject.domain.diff.data.DiffType;
import com.salpa.subject.domain.generator.DocumentFileGenerator;
import com.salpa.subject.domain.generator.DocumentFileType;
import com.salpa.subject.mapper.*;
import com.salpa.subject.service.DocumentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class DocumentServiceImpl implements DocumentService {

    @Autowired
    private DocumentMapper documentMapper;

    @Autowired
    private ProjectMapper projectMapper;

    @Autowired
    private DocumentDescriptionMapper documentDescriptionMapper;

    @Autowired
    private DocumentDiscussionMapper documentDiscussionMapper;

    @Autowired
    private TableColumnDocumentMapper tableColumnDocumentMapper;

    @Autowired
    private TableIndexDocumentMapper tableIndexDocumentMapper;

    @Autowired
    private TableForeignKeyDocumentMapper tableForeignKeyDocumentMapper;

    @Autowired
    private TableTriggerDocumentMapper tableTriggerDocumentMapper;

    @Autowired
    private DocumentDiffConverter documentDiffConverter;

    @Autowired
    private DocumentResponseConverter documentResponseConverter;

    @Autowired
    private DocumentSimpleResponseConverter documentSimpleResponseConverter;

    @Autowired
    private DocumentDiffChecker documentDiffChecker;

    private final List<DocumentFileGenerator> documentFileGenerators;

    @Autowired
    private OperationLogMapper operationLogMapper;

    public DocumentServiceImpl(List<DocumentFileGenerator> documentFileGenerators) {
        this.documentFileGenerators = documentFileGenerators;
    }

    /**
     * 创建同步任务
     * @param projectId
     * @param userId
     * @param ignoreIfExists
     * @return
     */
    @Override
    public int createSyncTask(Integer projectId,Long userId, boolean ignoreIfExists) {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        MonitorProjectSyncTask projectSyncTask = new MonitorProjectSyncTask();
        projectSyncTask.setProjectId(projectId);
        projectSyncTask.setStatus(ProjectSyncTaskStatus.NEW.toString());
        projectSyncTask.setUserId(user.getUserId());
        documentMapper.createSyncTask(projectSyncTask);
        /*添加日志*/
        MonitorOperationLog monitorOperationLog = MonitorOperationLog.builder()
                .operatorUserId(user.getUserId())
                .operatorNickname(user.getNickName())
                .operatorUsername(user.getUserName())
                .operatorUserId(user.getUserId())
                .operationName("创建同步任务")
                .operationCode("createSyncTask")
                .operationModule("project")
                .operationResponse(JSON.toJSONString(AjaxResult.success(projectSyncTask.getId())))
                .success(true)
                .involvedProjectId(projectId)
                .build();
        operationLogMapper.addOperationLog(monitorOperationLog);
        return projectSyncTask.getId();
    }

    /**
     * 获取版本号
     * @param projectId
     * @return
     */
    @Override
    public List<Map<String ,Object>> getVersionsByProjectId(Integer projectId) {
        List<Map<String ,Object>> list = documentMapper.getVersionsByProjectId(projectId);
        for (Map<String, Object> map : list) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String createAt1 = dateFormat.format(map.get("createAt"));
            map.put("createAt",createAt1);
        }
        return list;
    }

    /**
     * 获取文档信息
     * @param projectId
     * @param version
     * @return
     */
    @Override
    public DatabaseDocumentSimpleResponse getSimpleByProjectId(Integer projectId, Long version, String search, Long originalVersion) {
        Integer count = documentMapper.uniqueDocumentByProjectId(projectId);
        if (count>0) {
            String projectName = projectMapper.selectById(projectId).getProjectName();
            MonitorDatabaseDocument monitorDatabaseDocument;
            if (version == null) {
                version = documentMapper.getMaxVersion(projectId);
                monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,version);
            } else {
                monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,version);
            }
            List<Map<String, Object>> discussionList = documentDiscussionMapper.discussionByProjectId(projectId);
            Map<String, Integer> discussionCountMapByTableName = new HashMap<>();
            for (Map<String, Object> map : discussionList) {
                discussionCountMapByTableName.put(map.get("tableName").toString(),Integer.parseInt(map.get("count").toString()));
            }
            List<Map<String, String>> descriptionList = documentDescriptionMapper.selectById(projectId);
            Map<String, String> descriptionMapByTableName = new HashMap<>();
            for (Map<String, String> map : descriptionList) {
                descriptionMapByTableName.put(map.get("tableName"),map.get("content"));
            }
            if (originalVersion == null) {
                List<MonitorTableDocument> list = documentMapper.getTableDocument(monitorDatabaseDocument.getId(),search);
                var tableMetas = documentSimpleResponseConverter.of(
                        list,
                        discussionCountMapByTableName,
                        descriptionMapByTableName
                );
                DatabaseDocumentSimpleResponse databaseDocumentSimpleResponse = documentSimpleResponseConverter.of(monitorDatabaseDocument, tableMetas, DiffType.NONE, projectName);
                List<DatabaseDocumentSimpleResponse.TableData> tables = databaseDocumentSimpleResponse.getTables();
                for (DatabaseDocumentSimpleResponse.TableData table : tables) {
                    table.setColumns(tableColumnDocumentMapper.getTableColumnByTableId(table.getId()));
                }
                return databaseDocumentSimpleResponse;
            } else {
                var diffResults = tableDiffs(projectId, originalVersion, version);
                var tableMetas = documentSimpleResponseConverter.ofDiff(
                        diffResults, discussionCountMapByTableName, descriptionMapByTableName);
                DiffType diffType = DiffTypePredictor.predict(tableMetas);
                DatabaseDocumentSimpleResponse documentSimpleResponse = documentSimpleResponseConverter.of(monitorDatabaseDocument, tableMetas, diffType, projectName);
                List<DatabaseDocumentSimpleResponse.TableData> tables = documentSimpleResponse.getTables();
                for (DatabaseDocumentSimpleResponse.TableData table : tables) {
                    table.setColumns(tableColumnDocumentMapper.getTableColumnByTableId(table.getId()));
                }
                return documentSimpleResponse;
            }
        } else {
            return new DatabaseDocumentSimpleResponse();
        }
    }

    /**
     * 获取表格详情
     * @param id
     * @param projectId
     * @param version
     * @param originalVersion
     * @return
     */
    @Override
    public TableDocumentResponse getTableDetail(Integer id,Integer projectId,Long version,Long originalVersion) {
        if (version == null) {
            version = documentMapper.getMaxVersion(projectId);
        }
        if (originalVersion != null) {
            List<TableDocDiff> diffResults =
                    this.tableDiffs(projectId, originalVersion, version);
            TableDocDiff diff = TableDocDiff.builder().build();
            for (TableDocDiff diffResult : diffResults) {
                if (diffResult.getId().equals(id)) {
                    diff = diffResult;
                }
            }
            // discussion
            List<Map<String, Object>> discussionList = documentDiscussionMapper.countDiscussionByTableName(projectId,diff.getName());
            Map<String, Integer> discussionCountMapByJoinName = new HashMap<>();
            for (Map<String, Object> map : discussionList) {
                discussionCountMapByJoinName.put(map.get("columnName").toString(),Integer.parseInt(map.get("count").toString()));
            }
            // description
            List<Map<String, String>> descriptionList = documentDescriptionMapper.selectDescriptionByTableName(projectId,diff.getName());
            Map<String, String> descriptionMapByJoinName = new HashMap<>();
            for (Map<String, String> map : descriptionList) {
                descriptionMapByJoinName.put(map.get("columnName"),map.get("content"));
            }
            return documentResponseConverter.ofDiff(diff, discussionCountMapByJoinName, descriptionMapByJoinName);
        } else {
            MonitorTableDocument monitorTableDocument = documentMapper.getTableById(id);
            List<MonitorTableColumnDocument> columns = tableColumnDocumentMapper.getTableColumnByTableId(monitorTableDocument.getId());
            List<MonitorTableIndexDocument> indexes = tableIndexDocumentMapper.getTableIndexByTableId(monitorTableDocument.getId());
            List<MonitorTableForeignKeyDocument> foreignKeys = tableForeignKeyDocumentMapper.getTableForeignKeyByTableId(monitorTableDocument.getId());
            List<MonitorTableTriggerDocument> triggers = tableTriggerDocumentMapper.getTableTriggerByTableId(monitorTableDocument.getId());
            // discussion
            List<Map<String, Object>> discussionList = documentDiscussionMapper.countDiscussionByTableName(projectId,monitorTableDocument.getName());
            Map<String, Integer> discussionCountMapByJoinName = new HashMap<>();
            for (Map<String, Object> map : discussionList) {
                discussionCountMapByJoinName.put(map.get("columnName").toString(),Integer.parseInt(map.get("count").toString()));
            }
            // description
            List<Map<String, String>> descriptionList = documentDescriptionMapper.selectDescriptionByTableName(projectId,monitorTableDocument.getName());
            Map<String, String> descriptionMapByJoinName = new HashMap<>();
            for (Map<String, String> map : descriptionList) {
                descriptionMapByJoinName.put(map.get("columnName"),map.get("content"));
            }
            return documentResponseConverter.of(monitorTableDocument,discussionCountMapByJoinName,descriptionMapByJoinName,columns,indexes,foreignKeys,triggers);
        }
    }

    /**
     * 修改同步任务
     * @param syncTask
     */
    @Override
    public void updateSyncTask(int syncTask) {
        documentMapper.updateSyncTask(syncTask);
    }

    /**
     * 获取同步任务状态
     * @param taskId
     * @return
     */
    @Override
    public String getSyncTaskStatus(Integer taskId) {
        return documentMapper.getSyncTaskStatus(taskId);
    }

    /**
     * 版本对比
     * @param projectId
     * @param originalVersion
     * @param currentVersion
     * @return
     */
    public List<TableDocDiff> tableDiffs(Integer projectId, Long originalVersion, Long currentVersion) {
        var original = documentMapper.getSimpleByProjectId(projectId, originalVersion);
        MonitorDatabaseDocument current = documentMapper.getSimpleByProjectId(projectId, currentVersion);
        DatabaseDocDiff currentDiff = retrieveDatabaseDocumentDiff(current);
        DatabaseDocDiff originalDiff = retrieveDatabaseDocumentDiff(original);
        return documentDiffChecker.diff(originalDiff.getTables(), currentDiff.getTables());
    }

    /**
     * 检索数据库文档差异
     * @param databaseDocument
     * @return
     */
    private DatabaseDocDiff retrieveDatabaseDocumentDiff(MonitorDatabaseDocument databaseDocument) {
        Integer docId = databaseDocument.getId();
        List<MonitorTableDocument> tables = documentMapper.getTableDocument(docId,null);
        List<MonitorTableColumnDocument> columns = tableColumnDocumentMapper.getTableColumn(docId);
        List<MonitorTableIndexDocument> indexes = tableIndexDocumentMapper.getTableIndex(docId);
        List<MonitorTableForeignKeyDocument> triggers = tableForeignKeyDocumentMapper.getTableForeignKey(docId);
        List<MonitorTableTriggerDocument> fks = tableTriggerDocumentMapper.getTableTrigger(docId);
        return documentDiffConverter.of(databaseDocument, tables, columns, indexes, fks, triggers);
    }

    /**
     * 获取表名列表
     * @param projectId
     * @param version
     * @param search
     * @return
     */
    @Override
    public List<Map<String ,Object>> getTableDocumentByProjectId(Integer projectId, Long version, String search) {
        MonitorDatabaseDocument monitorDatabaseDocument;
        if (version == null) {
            Long maxVersion = documentMapper.getMaxVersion(projectId);
            monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,maxVersion);
        } else {
            monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,version);
        }
        List<Map<String ,Object>> list = new ArrayList<>();
        if (monitorDatabaseDocument != null) {
            list = documentMapper.getTableDocumentById(monitorDatabaseDocument.getId(),search);
        }
        return list;
    }

    /**
     * 获取同步任务列表
     * @param taskIds
     * @return
     */
    @Override
    public List<MonitorProjectSyncTask> getSyncTaskList(Integer[] taskIds) {
        return documentMapper.getSyncTaskList(taskIds);
    }

    /**
     * 取消同步任务
     * @param taskId
     * @return
     */
    @Override
    public int cancelTask(Integer taskId) {
        return documentMapper.updateStatusAndResultById(taskId);
    }

    /**
     * 导出
     * @param projectId
     * @param version
     * @param fileType
     * @param out
     */
    @Override
    public void export(Integer projectId, Long version, String fileType, OutputStream out) {
        DocumentFileType type = null;
        if ("Markdown".equals(fileType)) {
            type = DocumentFileType.MARKDOWN;
        } else if ("UML SVG".equals(fileType)) {
            type = DocumentFileType.PLANT_UML_ER_SVG;
        } else if ("UML PNG".equals(fileType)) {
            type = DocumentFileType.PLANT_UML_ER_PNG;
        } else if ("Word".equals(fileType)) {
            type = DocumentFileType.WORD;
        }
        DatabaseDocumentResponse databaseDocumentResponse = null;
        if (version == null) {
            Long maxVersion = documentMapper.getMaxVersion(projectId);
            databaseDocumentResponse = documentMapper.getByProjectId(projectId,maxVersion);
        } else {
            databaseDocumentResponse = documentMapper.getByProjectId(projectId,version);
        }
        List<TableDocumentResponse> list = new ArrayList<>();
        if (databaseDocumentResponse != null) {
            list = documentMapper.getTableDetails(databaseDocumentResponse.getId());
            if (list != null) {
                for (TableDocumentResponse tableDocumentResponse : list) {
                    List<MonitorTableColumnDocument> list1 = tableColumnDocumentMapper.getTableColumnByTableId(tableDocumentResponse.getId());
                    List<MonitorTableIndexDocument> list2 = tableIndexDocumentMapper.getTableIndexByTableId(tableDocumentResponse.getId());
                    List<MonitorTableForeignKeyDocument> list3 = tableForeignKeyDocumentMapper.getTableForeignKeyByTableId(tableDocumentResponse.getId());
                    List<MonitorTableTriggerDocument> list4 = tableTriggerDocumentMapper.getTableTriggerByTableId(tableDocumentResponse.getId());
                    /*实体转换*/
                    List<TableDocumentResponse.ColumnDocumentResponse> columnList = list1.stream().map(a ->{
                        //在此转把A转换为B
                        TableDocumentResponse.ColumnDocumentResponse columnDocumentResponse = new TableDocumentResponse.ColumnDocumentResponse();
                        columnDocumentResponse.setId(a.getId());
                        columnDocumentResponse.setName(a.getName());
                        columnDocumentResponse.setType(a.getType());
                        columnDocumentResponse.setSize(a.getSize());
                        columnDocumentResponse.setDecimalDigits(a.getDecimalDigits());
                        columnDocumentResponse.setComment(a.getComment());
                        columnDocumentResponse.setIsPrimaryKey(a.getPrimaryKey());
                        columnDocumentResponse.setNullable(a.getNullable());
                        columnDocumentResponse.setAutoIncrement(a.getAutoIncrement());
                        columnDocumentResponse.setDefaultValue(a.getDefaultValue());
                        columnDocumentResponse.setCreateAt(a.getCreateAt());
                        return columnDocumentResponse;
                    }).collect(Collectors.toList());
                    tableDocumentResponse.setColumns(columnList);

                    List<TableDocumentResponse.IndexDocumentResponse> indexList = list2.stream().map(a ->{
                        //在此转把A转换为B
                        TableDocumentResponse.IndexDocumentResponse indexDocumentResponse = new TableDocumentResponse.IndexDocumentResponse();
                        indexDocumentResponse.setId(a.getId());
                        indexDocumentResponse.setName(a.getName());
                        indexDocumentResponse.setIsUnique(a.getUnique());
                        indexDocumentResponse.setCreateAt(a.getCreateAt());
                        return indexDocumentResponse;
                    }).collect(Collectors.toList());
                    tableDocumentResponse.setIndexes(indexList);

                    List<TableDocumentResponse.ForeignKeyDocumentResponse> foreignList = list3.stream().map(a ->{
                        //在此转把A转换为B
                        TableDocumentResponse.ForeignKeyDocumentResponse foreignKeyDocumentResponse = new TableDocumentResponse.ForeignKeyDocumentResponse();
                        foreignKeyDocumentResponse.setId(a.getId());
                        foreignKeyDocumentResponse.setFkName(a.getFkName());
                        foreignKeyDocumentResponse.setFkTableName(a.getFkTableName());
                        foreignKeyDocumentResponse.setFkColumnName(a.getFkColumnName());
                        foreignKeyDocumentResponse.setKeySeq(a.getKeySeq());
                        foreignKeyDocumentResponse.setPkName(a.getPkName());
                        foreignKeyDocumentResponse.setPkTableName(a.getPkTableName());
                        foreignKeyDocumentResponse.setPkColumnName(a.getPkColumnName());
                        foreignKeyDocumentResponse.setUpdateRule(a.getUpdateRule());
                        foreignKeyDocumentResponse.setDeleteRule(a.getDeleteRule());
                        foreignKeyDocumentResponse.setCreateAt(a.getCreateAt());
                        return foreignKeyDocumentResponse;
                    }).collect(Collectors.toList());
                    tableDocumentResponse.setForeignKeys(foreignList);

                    List<TableDocumentResponse.TriggerDocumentResponse> triggerList = list4.stream().map(a ->{
                        //在此转把A转换为B
                        TableDocumentResponse.TriggerDocumentResponse triggerDocumentResponse = new TableDocumentResponse.TriggerDocumentResponse();
                        triggerDocumentResponse.setId(a.getId());
                        triggerDocumentResponse.setName(a.getName());
                        triggerDocumentResponse.setTiming(a.getTiming());
                        triggerDocumentResponse.setManipulation(a.getManipulation());
                        triggerDocumentResponse.setStatement(a.getStatement());
                        triggerDocumentResponse.setTriggerCreateAt(a.getTriggerCreateAt());
                        triggerDocumentResponse.setCreateAt(a.getCreateAt());
                        return triggerDocumentResponse;
                    }).collect(Collectors.toList());
                    tableDocumentResponse.setTriggers(triggerList);
                }
            }
        }
        databaseDocumentResponse.setTables(list);
        var context = DocumentFileGenerator.DocumentFileGenerateContext.builder()
                .documentFileType(type)
                .databaseDocument(databaseDocumentResponse)
                .build();
        DocumentFileType finalType = type;
        documentFileGenerators.stream()
                .filter(g -> g.support(finalType))
                .findFirst()
                .ifPresent(generator -> generator.generate(context, out));
    }
}
