package net.educoder.ecsonar.services;

import cn.hutool.core.date.DateUtil;
import com.google.protobuf.InvalidProtocolBufferException;
import net.educoder.ecsonar.dao.*;
import net.educoder.ecsonar.enums.AnalyseTypeEnum;
import net.educoder.ecsonar.enums.DegreeEnum;
import net.educoder.ecsonar.exception.BusinessException;
import net.educoder.ecsonar.model.*;
import net.educoder.ecsonar.model.api.Quality;
import net.educoder.ecsonar.model.api.QualityInspect;
import net.educoder.ecsonar.model.api.QualityInspectIsCompleted;
import net.educoder.ecsonar.model.api.QualityInspectResultData;
import net.educoder.ecsonar.model.dto.*;
import net.educoder.ecsonar.model.vo.*;
import net.educoder.ecsonar.protobuf.DbFileSources;
import net.educoder.ecsonar.protobuf.DbIssues;
import net.educoder.ecsonar.task.QualityInspectRunnable;
import net.educoder.ecsonar.utils.IdUtils;
import net.educoder.ecsonar.utils.SystemUtil;
import net.educoder.ecsonar.utils.html.HtmlSourceDecorator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * @Author: youys
 * @Date: 2022/1/17
 * @Description:
 */
@Service
public class QualityInspectService {

    private static final Logger LOGGER = LoggerFactory.getLogger(QualityInspectService.class);

    @Autowired
    private TaskInfoDao taskInfoDao;

    @Autowired
    private TaskInfoDetailDao taskInfoDetailDao;

    @Autowired
    private DbOperateService dbOperateService;

    @Autowired
    private SonarService sonarService;

    @Autowired
    private ReportService reportService;

    @Autowired
    private IssuesDao issuesDao;

    @Autowired
    private ProjectDao projectDao;

    @Autowired
    private FileSourceDao fileSourceDao;


    @Autowired
    @Qualifier("sonarScannerPool")
    private ExecutorService sonarScannerPool;

    @Autowired
    @Qualifier("sonarQueryResultPool")
    private ExecutorService sonarQueryResultPool;

    private static final Logger logger = LoggerFactory.getLogger(QualityInspectService.class);


    public QualityInspect qualityInspect(QualityInspectVO qualityInspectVO) {

        List<QualityInspectUserDataVO> userDatas = qualityInspectVO.getUserDatas();
        LOGGER.info("homeworkId:{}，{}个人提交了代码", qualityInspectVO.getHomeworkId(), userDatas.size());

        if (CollectionUtils.isEmpty(userDatas)) {
            throw new BusinessException(2, "用户数据不能为空");
        }

        TaskInfo taskInfo = new TaskInfo();
        taskInfo.setId(IdUtils.nextStrId());
        taskInfo.setHomeworkId(qualityInspectVO.getHomeworkId());
        taskInfo.setLanguage(qualityInspectVO.getLanguage());
        taskInfo.setPeopleNumber(qualityInspectVO.getUserDatas().size());

        taskInfoDao.insertTaskInfo(taskInfo);

        for (QualityInspectUserDataVO userData : userDatas) {

            TaskInfoDetail taskInfoDetail = new TaskInfoDetail();
            taskInfoDetail.setId(IdUtils.nextStrId());
            taskInfoDetail.setTaskId(taskInfo.getId());
            taskInfoDetail.setProjectName(String.format("%s-%s", qualityInspectVO.getHomeworkId(), userData.getStudentId()));
            taskInfoDetail.setName(userData.getName());
            taskInfoDetail.setStudentId(userData.getStudentId());
            taskInfoDetail.setUserId(userData.getUserId());
            taskInfoDetail.setCodeIds(userData.getCodeIds().toString());

            taskInfoDetailDao.insertTaskInfoDetail(taskInfoDetail);

            QualityInspectRunnable runnable = new QualityInspectRunnable(taskInfo.getId(), qualityInspectVO.getLanguage(),
                    qualityInspectVO.getHomeworkId(), taskInfoDetail, userData, sonarQueryResultPool);
            runnable.setDbOperateService(dbOperateService);
            runnable.setSonarService(sonarService);

            // 提交任务
            sonarScannerPool.execute(runnable);
        }
        // 提交之后返回taskId
        return new QualityInspect(taskInfo.getId());
    }


    public RollPage<QualityInspectResultData> qualityInspectResultQuery(Integer pageNum, Integer pageSize, String taskId) {
        RollPage<QualityInspectResultData> rollPage = new RollPage();
        rollPage.setCurrentPage(pageNum);
        rollPage.setPageSize(pageSize);


        int offset = (pageNum - 1) * pageSize;
        Integer totalCount = taskInfoDetailDao.selectTaskInfoDetailPageCount(taskId, pageSize, offset);
        rollPage.setRecordSum(totalCount);


        if (totalCount == null || totalCount == 0) {
            rollPage.setRecordList(new ArrayList<>());
        } else {
            List<TaskInfoDetail> taskInfoDetails = taskInfoDetailDao.selectTaskInfoDetailPageList(taskId, pageSize, offset);

            List<QualityInspectResultData> resultDataList = new ArrayList<>(taskInfoDetails.size());
            rollPage.setRecordList(resultDataList);

            // 组装数据
            for (TaskInfoDetail taskInfoDetail : taskInfoDetails) {
                QualityInspectResultData resultData = new QualityInspectResultData();
                resultData.setName(taskInfoDetail.getName());
                resultData.setStudentId(taskInfoDetail.getStudentId());
                resultData.setUserId(taskInfoDetail.getUserId());

                Metrics metrics = reportService.getMetrics(taskInfoDetail.getProjectName());

                resultData.setDiscern(!metrics.getBugs().equals("-"));
                resultData.setBug(new Quality(metrics.getBlock_bugs(), metrics.getMajor_bugs(), metrics.getMinor_bugs(), metrics.getCritical_bugs(), metrics.getBugs()));
                resultData.setVulnerability(new Quality(metrics.getBlock_violations(), metrics.getMajor_violations(), metrics.getMinor_violations(), metrics.getCritical_violations(), metrics.getViolations()));
                resultData.setCodeSmall(new Quality(metrics.getBlock_code_smells(), metrics.getMajor_code_smells(), metrics.getMinor_code_smells(), metrics.getCritical_code_smells(), metrics.getCode_smells()));

                resultData.setComplexity(metrics.getComplexity());
                resultData.setTotalRowNumber(metrics.getLines());
                resultData.setQualityScore(calcQualityScore(resultData));

                resultDataList.add(resultData);
            }
        }

        return rollPage;
    }

    public QualityInspectIsCompleted qualityInspectIsCompleted(String taskId) {
        TaskInfo taskInfo = taskInfoDao.selectById(taskId);
        if (taskInfo == null) {
            throw new BusinessException(2, "任务id不存在");
        }

        if (taskInfo.getStatus() == 1) {
            return new QualityInspectIsCompleted(taskId, taskInfo.getStatus());
        }

        Integer taskNumber = taskInfoDetailDao.selectCountByTaskId(taskInfo.getId());
        int completed = taskInfo.getPeopleNumber().equals(taskNumber) ? 1 : 0;

        // 数据库未更新状态，但sonar任务已经全部执行完，更新task_info状态
        if (taskInfo.getStatus() == 0 && completed == 1) {
            taskInfoDao.updateTaskInfoStatus(taskInfo.getId(), 1);

            // 并且删除文件 /tmp/$taskId  /tmp/homeworkId/*
            String command = String.format("rm -rf /tmp/%s && rm -rf /tmp/%s/*", taskId, taskInfo.getHomeworkId());
            SystemUtil.executeAndGetExitStatus(command);
        }

        QualityInspectIsCompleted qualityInspectIsCompleted = new QualityInspectIsCompleted(taskId, completed);
        return qualityInspectIsCompleted;
    }


    /**
     * 阻断	严重	主要	次要
     * 10	5	    3	     1
     *
     * @param resultData
     * @return
     */
    private BigDecimal calcQualityScore(QualityInspectResultData resultData) {

        if (resultData.getTotalRowNumber() <= 0) {
            return BigDecimal.ZERO;
        }

        // 100 -（阻断 * 10 + 严重 * 5 + 主要* 3 + 次要 * 1）/ 总行数 * 100
        Quality bug = resultData.getBug();
        BigDecimal bugScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(bug.getBlocker() * 10 + bug.getCritical() * 5 + bug.getMajor() * 3 + bug.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));

        Quality vulnerability = resultData.getVulnerability();
        BigDecimal vulnerabilityScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(vulnerability.getBlocker() * 10 + vulnerability.getCritical() * 5 + vulnerability.getMajor() * 3 + vulnerability.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));

        Quality codeSmall = resultData.getCodeSmall();
        BigDecimal codeSmallScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(codeSmall.getBlocker() * 10 + codeSmall.getCritical() * 5 + codeSmall.getMajor() * 3 + codeSmall.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));


        // 总得分= bugScore * 0.5 + vulnerabilityScore * 0.3 + codeSmallScore * 0.2
        BigDecimal score = bugScore.multiply(BigDecimal.valueOf(0.5)).add(vulnerabilityScore.multiply(BigDecimal.valueOf(0.3))).add(codeSmallScore.multiply(BigDecimal.valueOf(0.2))).setScale(2, RoundingMode.HALF_UP);
        return score;
    }


    /**
     * 分析详情
     *
     * @param analyseDetailVO
     * @return
     */
    public AnalyseDetailDTO getAnalyseDetail(AnalyseDetailVO analyseDetailVO) {
        // 作业id+学号
        String projectName = String.format("%s-%s", analyseDetailVO.getHomeworkId(), analyseDetailVO.getStudentNo());

        Project project = projectDao.findByName(projectName);
        if (project == null) {
            throw new BusinessException(-1, String.format("找不到分析记录homeworkId:%s,studentNo:%s", analyseDetailVO.getHomeworkId(), analyseDetailVO.getStudentNo()));
        }

        DegreeDTO codeSmall = issuesDao.queryDegree(project.getProject_uuid(), AnalyseTypeEnum.CodeSmell.getType(), AnalyseTypeEnum.CodeSmell.getMetricId());
        DegreeDTO bug = issuesDao.queryDegree(project.getProject_uuid(), AnalyseTypeEnum.BUG.getType(), AnalyseTypeEnum.BUG.getMetricId());
        DegreeDTO vulnerability = issuesDao.queryDegree(project.getProject_uuid(), AnalyseTypeEnum.Vulnerability.getType(), AnalyseTypeEnum.Vulnerability.getMetricId());

        AnalyseDetailDTO analyseDetail = new AnalyseDetailDTO();
        analyseDetail.setBug(bug == null ? new DegreeDTO() : bug);
        analyseDetail.setVulnerability(vulnerability == null ? new DegreeDTO() : vulnerability);
        analyseDetail.setCodeSmall(codeSmall == null ? new DegreeDTO() : codeSmall);
        return analyseDetail;
    }


    /**
     * 风险详情列表
     *
     * @param analyseDetailListVO
     * @return
     */
    public RollPage<AnalyseDetailListDTO> getAnalyseDetailList(AnalyseDetailListVO analyseDetailListVO) {

        RollPage rollPage = new RollPage();

        if (analyseDetailListVO.getCurrentPage() <= 0) {
            analyseDetailListVO.setCurrentPage(1);
        }

        if (analyseDetailListVO.getPageSize() >= 10000) {
            analyseDetailListVO.setPageSize(10000);
        }

        rollPage.setCurrentPage(analyseDetailListVO.getCurrentPage());
        rollPage.setPageSize(analyseDetailListVO.getPageSize());

        // 作业id+学号
        String projectName = String.format("%s-%s", analyseDetailListVO.getHomeworkId(), analyseDetailListVO.getStudentNo());

        Project project = projectDao.findByName(projectName);
        if (project == null) {
            throw new BusinessException(-1, "找不到分析记录");
        }

        // 分析类型
        AnalyseTypeEnum analyseTypeEnum = AnalyseTypeEnum.getAnalyseTypeEnum(analyseDetailListVO.getType());

        // 严重程度
        DegreeEnum degreeEnum = DegreeEnum.getDegreeEnum(analyseDetailListVO.getDegree());
        String severity = null;
        if (degreeEnum != DegreeEnum.All) {
            severity = degreeEnum.getValue();
        }

        Integer pageIssuesCount = issuesDao.getPageIssuesCount(project.getProject_uuid(), analyseTypeEnum.getType(), severity);
        rollPage.setRecordSum(pageIssuesCount);
        if (pageIssuesCount > 0) {
            int start = (analyseDetailListVO.getCurrentPage() - 1) * analyseDetailListVO.getPageSize();
            List<Issues> pageIssues = issuesDao.getPageIssues(project.getProject_uuid(), analyseTypeEnum.getType(), severity, start, analyseDetailListVO.getPageSize());
            processPageIssues(pageIssues, rollPage);
        } else {
            rollPage.setRecordList(new ArrayList(0));
        }

        return rollPage;
    }

    public ProblemAnalysisDTO getProblemAnalysis(Integer ruleId) {
        Rule rule = projectDao.findRuleById(ruleId);
        ProblemAnalysisDTO problemAnalysisDTO = new ProblemAnalysisDTO();
        if (rule != null) {
            String example = rule.getDescription().replaceAll("<p>", "<p style=\"color: #d50000;font-size: 17px;\">")
                    .replaceAll("Noncompliant Code Example", "错误代码示范")
                    .replaceAll("Compliant Solution", "正确代码示范")
                    .replaceAll("Exceptions", "异常代码")
                    .replaceAll("See", "链接");


            problemAnalysisDTO.setTitle(rule.getName());
            problemAnalysisDTO.setExample(example);
            problemAnalysisDTO.setLanguage("py".equalsIgnoreCase(rule.getLanguage()) ? "python" : rule.getLanguage());
            problemAnalysisDTO.setConstantIssue(rule.getDefRemediationBaseEffort());

            if (StringUtils.isBlank(rule.getTags())) {
                problemAnalysisDTO.setTags(new ArrayList<>());
            } else {
                problemAnalysisDTO.setTags(Arrays.asList(rule.getTags().split(",")));
            }

            problemAnalysisDTO.setCreateTime(DateUtil.formatDateTime(new Date(rule.getCreateTime())));
        } else {
            problemAnalysisDTO.setTitle("");
            problemAnalysisDTO.setExample("");
            problemAnalysisDTO.setLanguage("");
            problemAnalysisDTO.setConstantIssue("");
            problemAnalysisDTO.setTags(new ArrayList<>());
            problemAnalysisDTO.setCreateTime(DateUtil.formatTime(new Date()));
        }

        return problemAnalysisDTO;
    }

    /**
     * 代码详情
     *
     * @param codeDetailVO
     * @return
     */
    public CodeDetailDTO getCodeDetail(CodeDetailVO codeDetailVO) {

        FileSource fileSource = fileSourceDao.findFileSourceFileUuid(codeDetailVO.getUuid());

        DbFileSources.Data data = fileSource.decodeSourceData(fileSource.getBinaryData());
        List<DbFileSources.Line> linesList = data.getLinesList();

        List<FileSourceDTO> fileSourceDTOList = new ArrayList<>(linesList.size());
        for (DbFileSources.Line line : linesList) {
            FileSourceDTO fileSourceDTO = new FileSourceDTO();
            fileSourceDTO.setRowNumber(line.getLine());
            fileSourceDTO.setCode(HtmlSourceDecorator.getInstance().getDecoratedSourceAsHtml(line.getSource(), line.getHighlighting(), line.getSymbols()));
            fileSourceDTOList.add(fileSourceDTO);
        }

        CodeDetailDTO codeDetail = new CodeDetailDTO();
        codeDetail.setCodes(fileSourceDTOList);

        Issues issues = issuesDao.queryById(codeDetailVO.getIssueId());

        ProblemAnalysisDTO problemAnalysis = getProblemAnalysis(codeDetailVO.getRuleId());
        codeDetail.setExample(problemAnalysis.getExample());
        codeDetail.setTitle(problemAnalysis.getTitle());
        codeDetail.setErrMessage(issues.getMessage());


        return codeDetail;
    }


    private void processPageIssues(List<Issues> pageIssues, RollPage<AnalyseDetailListDTO> rollPage) {
        List<AnalyseDetailListDTO> analyseDetailLists = new ArrayList<>(pageIssues.size());

        for (Issues pageIssue : pageIssues) {
            AnalyseDetailListDTO detailListDTO = new AnalyseDetailListDTO();
            analyseDetailLists.add(detailListDTO);

            detailListDTO.setName(pageIssue.getName());
            detailListDTO.setDescription(pageIssue.getMessage());

            detailListDTO.setIssueId(pageIssue.getId());
            detailListDTO.setUuid(pageIssue.getUuid());
            detailListDTO.setRuleId(pageIssue.getRuleId());
            detailListDTO.setFilePath(pageIssue.getPath());
            detailListDTO.setLanguage(pageIssue.getLanguage());
            detailListDTO.setLevel(DegreeEnum.getDegreeEnumByValue(pageIssue.getSeverity()).getDesc());
            try {
                DbIssues.Locations locations = DbIssues.Locations.parseFrom(pageIssue.getLocations());
                detailListDTO.setRowNumber(locations.getTextRange().getStartLine());
            } catch (InvalidProtocolBufferException e) {
                detailListDTO.setRowNumber(0);
                logger.error("Fail to read ISSUES.LOCATIONS [KEE=%s]", e);
            }
        }

        rollPage.setRecordList(analyseDetailLists);
    }


}
