package net.educoder.ecsonar.services;

import cn.hutool.core.util.RandomUtil;
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.DbIssues;
import net.educoder.ecsonar.task.GraduationProjectQualityInspectRunnable;
import net.educoder.ecsonar.utils.IdUtils;
import net.educoder.ecsonar.utils.SystemUtil;
import org.apache.commons.collections4.CollectionUtils;
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.List;
import java.util.concurrent.ExecutorService;

/**
 * @Author: youys
 * @Date: 2023/8/31
 * @Description: 毕设质量分析service
 */
@Service
public class GraduationProjectQualityInspectService {

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

    @Autowired
    private GraduationProjectTaskInfoDao graduationProjectTaskInfoDao;

    @Autowired
    private GraduationProjectTaskInfoDetailDao graduationProjectTaskInfoDetailDao;

    @Autowired
    private DbOperateService dbOperateService;

    @Autowired
    private SonarService sonarService;

    @Autowired
    private ReportService reportService;

    @Autowired
    private IssuesDao issuesDao;

    @Autowired
    private ProjectDao projectDao;


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

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


    public QualityInspect graduationProjectQualityInspect(List<GraduationProjectQualityInspectVO> qualityInspectVOList) {


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

        GraduationProjectTaskInfo taskInfo = new GraduationProjectTaskInfo();
        taskInfo.setId(IdUtils.nextStrId());
        taskInfo.setPeopleNumber(qualityInspectVOList.size());

        graduationProjectTaskInfoDao.insertTaskInfo(taskInfo);
        LOGGER.info("taskId:{}, {}个人提交了代码", taskInfo.getId(), qualityInspectVOList.size());

        for (GraduationProjectQualityInspectVO qualityInspectVO : qualityInspectVOList) {

            GraduationProjectTaskInfoDetail graduationProjectTaskInfoDetail = new GraduationProjectTaskInfoDetail();
            graduationProjectTaskInfoDetail.setId(IdUtils.nextStrId());
            graduationProjectTaskInfoDetail.setTaskId(taskInfo.getId());
            graduationProjectTaskInfoDetail.setProjectName(String.format("%s-%s", qualityInspectVO.getStudentId(), RandomUtil.randomString(8)));
            graduationProjectTaskInfoDetail.setName(qualityInspectVO.getName());
            graduationProjectTaskInfoDetail.setStudentId(qualityInspectVO.getStudentId());
            graduationProjectTaskInfoDetail.setUserId(qualityInspectVO.getUserId());
            graduationProjectTaskInfoDetail.setGitUrl(qualityInspectVO.getGitUrl());
            graduationProjectTaskInfoDetail.setLanguage(qualityInspectVO.getLanguage());

            graduationProjectTaskInfoDetailDao.insertTaskInfoDetail(graduationProjectTaskInfoDetail);

            // 提交一个sonar扫描任务
            GraduationProjectQualityInspectRunnable runnable = new GraduationProjectQualityInspectRunnable(taskInfo.getId(),
                    qualityInspectVO.getLanguage(), graduationProjectTaskInfoDetail, sonarQueryResultPool);
            runnable.setDbOperateService(dbOperateService);
            runnable.setSonarService(sonarService);

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


    public RollPage<QualityInspectResultData> qualityInspectResultQuery(GraduationQualityInspectResultQueryVO queryVO) {
        int pageSize = queryVO.getPageSize();
        int pageNum = queryVO.getCurrentPage();

        RollPage<QualityInspectResultData> rollPage = new RollPage<>();
        rollPage.setCurrentPage(pageNum);
        rollPage.setPageSize(pageSize);

        int offset = (pageNum - 1) * queryVO.getPageSize();
        Integer totalCount = graduationProjectTaskInfoDetailDao.
                selectGraduationProjectTaskInfoDetailPageCount(queryVO.getTaskId(), pageSize, offset);
        rollPage.setRecordSum(totalCount);


        if (totalCount == null || totalCount == 0) {
            rollPage.setRecordList(new ArrayList<>());
        } else {
            List<GraduationProjectTaskInfoDetail> taskInfoDetails = graduationProjectTaskInfoDetailDao.
                    selectGraduationProjectTaskInfoDetailPageList(queryVO.getTaskId(), pageSize, offset);

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

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

                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());

                resultDataList.add(resultData);
            }

            int maxBugNumber = resultDataList.stream().mapToInt((QualityInspectResultData resultData) -> {
                Quality bug = resultData.getBug();
                return bug.getMinor() + bug.getBlocker() + bug.getMajor() + bug.getCritical();
            }).max().getAsInt();

            int maxVulnerabilityNumber = resultDataList.stream().mapToInt((QualityInspectResultData resultData) -> {
                Quality vulnerability = resultData.getVulnerability();
                return vulnerability.getMinor() + vulnerability.getBlocker() + vulnerability.getMajor() + vulnerability.getCritical();
            }).max().getAsInt();

            int maxCodeSmallNumber = resultDataList.stream().mapToInt((QualityInspectResultData resultData) -> {
                Quality codeSmall = resultData.getCodeSmall();
                return codeSmall.getMinor() + codeSmall.getBlocker() + codeSmall.getMajor() + codeSmall.getCritical();
            }).max().getAsInt();


            // 计算得分
            for (QualityInspectResultData resultData : resultDataList) {
                BigDecimal finalScore = calcQualityScore(resultData, queryVO.getBugRate(), maxBugNumber,
                        queryVO.getVulnerabilityRate(), maxVulnerabilityNumber,
                        queryVO.getCodeSmallRate(), maxCodeSmallNumber);
                resultData.setQualityScore(finalScore);
            }
        }

        return rollPage;
    }

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

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

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

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

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

        return new QualityInspectIsCompleted(taskId, completed);
    }


    /**
     * 代码质量分 =50*(x-a)/x + 30*(y-b)/y + 20*(z-c)/c
     * 1、缺陷最大值为x，实际值为a。
     * 2、漏洞最大值为y，实际值为b。
     * 3、代码规范性最大值为z，实际值为c。
     * （具体占比需要根据老师配置的比例进行计算）
     *
     * @param resultData             用户数据
     * @param bugRate                bug得分占比率
     * @param maxBugNumber           最大bug数
     * @param vulnerabilityRate      漏洞得分占比率
     * @param maxVulnerabilityNumber 最大漏洞数量
     * @param codeSmallRate          代码规范得分占比
     * @param maxCodeSmallNumber     最大代码规范数量
     * @return score
     */
    private BigDecimal calcQualityScore(QualityInspectResultData resultData,
                                        int bugRate,
                                        int maxBugNumber,
                                        int vulnerabilityRate,
                                        int maxVulnerabilityNumber,
                                        int codeSmallRate,
                                        int maxCodeSmallNumber) {
        if (resultData.getTotalRowNumber() <= 0) {
            return BigDecimal.ZERO;
        }

        Quality bug = resultData.getBug();
        BigDecimal bugScore = BigDecimal.valueOf(bugRate)
                .multiply(BigDecimal.valueOf(maxBugNumber - (bug.getBlocker() + bug.getCritical() + bug.getMajor() + bug.getMinor())))
                .divide(BigDecimal.valueOf(maxBugNumber), 2, RoundingMode.HALF_UP);

        Quality vulnerability = resultData.getVulnerability();
        BigDecimal vulnerabilityScore = BigDecimal.valueOf(vulnerabilityRate)
                .multiply(BigDecimal.valueOf(maxVulnerabilityNumber - (vulnerability.getBlocker() + vulnerability.getCritical() + vulnerability.getMajor() + vulnerability.getMinor())))
                .divide(BigDecimal.valueOf(maxVulnerabilityNumber), 2, RoundingMode.HALF_UP);

        Quality codeSmall = resultData.getCodeSmall();
        BigDecimal codeSmallScore = BigDecimal.valueOf(codeSmallRate)
                .multiply(BigDecimal.valueOf(maxCodeSmallNumber - (codeSmall.getBlocker() + codeSmall.getCritical() + codeSmall.getMajor() + codeSmall.getMinor())))
                .divide(BigDecimal.valueOf(maxCodeSmallNumber), 2, RoundingMode.HALF_UP);

        BigDecimal score = bugScore.add(vulnerabilityScore).add(codeSmallScore);
        return score;
    }


    /**
     * 分析详情
     *
     * @param taskDetailId
     * @return
     */
    public AnalyseDetailDTO getAnalyseDetail(String taskDetailId) {
        // 作业id+学号
        GraduationProjectTaskInfoDetail graduationProjectTaskInfoDetail = graduationProjectTaskInfoDetailDao.selectById(taskDetailId);
        if (graduationProjectTaskInfoDetail == null) {
            throw new BusinessException(-1, "找不到分析记录");
        }

        Project project = projectDao.findByName(graduationProjectTaskInfoDetail.getProjectName());
        if (project == null) {
            throw new BusinessException(-1, String.format("找不到分析记录detailId:%s", taskDetailId));
        }

        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(GraduationProjectAnalyseDetailListVO analyseDetailListVO) {
        RollPage<AnalyseDetailListDTO> 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+学号
        GraduationProjectTaskInfoDetail graduationProjectTaskInfoDetail = graduationProjectTaskInfoDetailDao.selectById(analyseDetailListVO.getTaskDetailId());
        if (graduationProjectTaskInfoDetail == null) {
            throw new BusinessException(-1, String.format("找不到分析记录detailId:%s", analyseDetailListVO.getTaskDetailId()));
        }

        Project project = projectDao.findByName(graduationProjectTaskInfoDetail.getProjectName());
        if (project == null) {
            throw new BusinessException(-1, String.format("找不到分析记录detailId:%s", analyseDetailListVO.getTaskDetailId()));
        }

        // 分析类型
        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;
    }


    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);
    }


}
