package org.stm.metric.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.stm.metric.entity.MetricMonitorSet;
import org.stm.metric.entity.MetricReport;
import org.stm.metric.mapper.MetricReportMapper;
import org.stm.metric.pojo.MetricQueryCond;
import org.stm.metric.service.IMetricMonitorSetService;
import org.stm.metric.service.IMetricReportService;
import org.stm.utils.CommonUtil;
import org.stm.utils.MessageParam;
import org.stm.utils.SendMessageUtil;

import java.util.*;

@Service
public class MetricReportServiceImpl extends ServiceImpl<MetricReportMapper, MetricReport> implements IMetricReportService {

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

    @Autowired
    private IMetricMonitorSetService metricMonitorSetService;

    @Autowired
    private ISysBaseAPI sysBaseAPI;

    @Override
    @Transactional
    public void caluculatFormula(MetricMonitorSet metricMonitorSet) {
        try {
            String strParams = metricMonitorSet.getParams();
            String strFormulas = metricMonitorSet.getFormulas();

            List<String> metricNoList = new ArrayList<>();
            strFormulas = parseParams(strParams, metricNoList, strFormulas);

            QueryWrapper<MetricReport> queryReportWrapper = new QueryWrapper<>();
            queryReportWrapper.in("mtrc_no", metricNoList);

            List<MetricReport> metricReportList = this.list(queryReportWrapper);
            int frequency = metricMonitorSet.getCollFreq();
            Map<Date, List<MetricReport>> metricMaps = groupDataByFrequency(frequency, metricReportList);

            List<MetricReport> reportList = new ArrayList<>();
            LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

            for (Map.Entry<Date, List<MetricReport>> entry : metricMaps.entrySet()) {
                MetricReport newReport = createMetricReport(entry.getKey(), entry.getValue(), strFormulas, metricMonitorSet, sysUser);
                if (newReport != null) {
                    reportList.add(newReport);
                }
            }

            this.saveBatch(reportList);
        } catch (Exception e) {
            logger.error("Error calculating formula for metric monitor set: {}", metricMonitorSet.getMtrcNo(), e);
        }
    }

    @Override
    public void sendMsg(MetricReport metricReport) {
        try {
            int riskLevel = metricReport.getRiskLevel();
            if (riskLevel == 0) return;

            MetricMonitorSet monitorSet = getMetricMonitorSet(metricReport.getMtrcNo());
            if (monitorSet == null) return;

            String riskStr = getRiskLevelString(riskLevel);
            MessageParam messageParam = createMessageParam(riskStr, metricReport, monitorSet);

            sendMessageBasedOnRiskLevel(riskLevel, monitorSet, messageParam);
        } catch (Exception e) {
            logger.error("Error sending message for metric report: {}", metricReport.getMtrcNo(), e);
        }
    }

    private String parseParams(String params, List<String> metricCodeList, String formulas) {
        if (StringUtils.isEmpty(params)) {
            return formulas;
        }

        String[] paramArray = params.split(",");
        for (String param : paramArray) {
            String[] parts = param.split(":");
            if (parts.length == 2) {
                metricCodeList.add(parts[1]);
                formulas = formulas.replaceAll(parts[0], parts[1]);
            }
        }
        return formulas;
    }

    private Map<Date, List<MetricReport>> groupDataByFrequency(int frequency, List<MetricReport> dataList) {
        Map<Date, List<MetricReport>> resultMap = new HashMap<>();
        for (MetricReport report : dataList) {
            Date fillTime = report.getFillTime();
            Date groupedDate = getGroupedDateByFrequency(frequency, fillTime);
            resultMap.computeIfAbsent(groupedDate, k -> new ArrayList<>()).add(report);
        }
        return resultMap;
    }

    private Date getGroupedDateByFrequency(int frequency, Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        switch (frequency) {
            case 2:
                calendar.setFirstDayOfWeek(Calendar.MONDAY);
                calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
                break;
            case 3:
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                break;
            case 4:
                int month = calendar.get(Calendar.MONTH);
                int quarter = (month / 3) + 1;
                calendar.set(Calendar.MONTH, (quarter - 1) * 3);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                break;
            case 5:
                calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) < 6 ? 0 : 6);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                break;
            case 6:
                calendar.set(Calendar.MONTH, 0);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                break;
        }
        return calendar.getTime();
    }

    private MetricReport createMetricReport(Date fillTime, List<MetricReport> reports, String formulas, MetricMonitorSet monitorSet, LoginUser user) {
        String newFormulas = formulas;
        for (MetricReport report : reports) {
            if (report.getFillVals() != null) {
                newFormulas = newFormulas.replaceAll(report.getMtrcNo(), report.getFillVals());
            }
        }

        Double resultValue = CommonUtil.jsEavl(newFormulas);
        if (resultValue == null) return null;

        MetricReport newReport = new MetricReport();
        newReport.setFillTime(fillTime);
        newReport.setReporter(user.getId());
        newReport.setFillVals(CommonUtil.DoubleToStr(resultValue, 4));
        newReport.setMtrcNo(monitorSet.getMtrcNo());
        newReport.setRiskLevel(CommonUtil.computeRiskLevel(resultValue, monitorSet));
        return newReport;
    }

    private MetricMonitorSet getMetricMonitorSet(String metricNo) {
        QueryWrapper<MetricMonitorSet> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("metric_no",metricNo)
                .orderByDesc("id")
                .last("LIMIT 1");

        return metricMonitorSetService.getOne(queryWrapper);
    }

    private String getRiskLevelString(int riskLevel) {
        switch (riskLevel) {
            case 1:
                return "低";
            case 2:
                return "中";
            case 3:
                return "高";
            default:
                return "";
        }
    }

    private MessageParam createMessageParam(String riskStr, MetricReport metricReport, MetricMonitorSet monitorSet) {
        MessageParam messageParam = new MessageParam();
        messageParam.setMsgTitle(riskStr + "风险指标提醒");

        StringBuffer messageContent = new StringBuffer();
        messageContent.append("<div style='margin:10px;line-height:50px'>");
        messageContent.append("<b>").append(riskStr).append("风险指标，请注意检查</b>");
        messageContent.append("<br><hr><br>");
        messageContent.append("指标名称：<a href='/metric/MetricMonitor'>").append(monitorSet.getMtrcName()).append("</a>");
        messageContent.append("<br><hr>");
        messageContent.append("指标编号：").append(metricReport.getMtrcNo());
        messageContent.append("</div>");

        // 获取 riskLevel 并传递给 appendRiskValues
        int riskLevel = metricReport.getRiskLevel();
        appendRiskValues(messageContent, riskLevel, monitorSet);

        messageContent.append("<br><hr>");
        messageContent.append("当前值：").append(metricReport.getFillVals());
        if (monitorSet.getUnit() != null) {
            messageContent.append(monitorSet.getUnit());
        }

        messageParam.setContent(messageContent.toString());
        messageParam.setTemplateCode("sys_ts_note");
        messageParam.setMsgType("system");
        return messageParam;
    }

    private void appendRiskValues(StringBuffer messageContent, int riskLevel, MetricMonitorSet monitorSet) {
        Float stTolVal = monitorSet.getStTolVal();
        Float tolVal = monitorSet.getTolVal();
        Float warnVal = monitorSet.getWarnVal();
        Float intvVal = monitorSet.getIntvVal();
        String unit = monitorSet.getUnit() != null ? monitorSet.getUnit() : "";

        if (tolVal < warnVal) {
            appendRiskValuesForAscendingOrder(messageContent, riskLevel, stTolVal, tolVal, warnVal, intvVal, unit);
        } else {
            appendRiskValuesForDescendingOrder(messageContent, riskLevel, stTolVal, tolVal, warnVal, intvVal, unit);
        }
    }

    private void appendRiskValuesForAscendingOrder(StringBuffer messageContent, int riskLevel, Float stTolVal, Float tolVal, Float warnVal, Float intvVal, String unit) {
        switch (riskLevel) {
            case 3:
                messageContent.append("高风险值：大于等于").append(warnVal).append(unit);
                if (intvVal != null) {
                    messageContent.append(",小于").append(intvVal).append(unit);
                }
                break;
            case 2:
                messageContent.append("中风险值：大于").append(tolVal).append(unit)
                        .append(",小于").append(warnVal).append(unit);
                break;
            case 1:
                messageContent.append("低风险值：小于等于").append(tolVal).append(unit);
                if (stTolVal != null) {
                    messageContent.append(",大于").append(stTolVal).append(unit);
                }
                break;
        }
    }

    private void appendRiskValuesForDescendingOrder(StringBuffer messageContent, int riskLevel, Float stTolVal, Float tolVal, Float warnVal, Float intvVal, String unit) {
        switch (riskLevel) {
            case 3:
                messageContent.append("高风险值：小于等于").append(warnVal).append(unit);
                if (intvVal != null) {
                    messageContent.append(",大于").append(intvVal).append(unit);
                }
                break;
            case 2:
                messageContent.append("中风险值：小于").append(tolVal).append(unit)
                        .append(",大于").append(warnVal).append(unit);
                break;
            case 1:
                messageContent.append("低风险值：");
                if (stTolVal != null) {
                    messageContent.append("小于").append(stTolVal).append(unit).append(",");
                }
                messageContent.append("大于等于").append(tolVal).append(unit);
                break;
        }
    }

    private void sendMessageBasedOnRiskLevel(int riskLevel, MetricMonitorSet monitorSet, MessageParam messageParam) {
        SendMessageUtil sendMessageUtil = new SendMessageUtil(sysBaseAPI);
        String[] usersToNotify = getUsersToNotify(riskLevel, monitorSet);
        if (usersToNotify != null) {
            for (String user : usersToNotify) {
                messageParam.setReceiver(user);
                sendMessageUtil.sendMessage(messageParam);
            }
        }
    }

    private String[] getUsersToNotify(int riskLevel, MetricMonitorSet monitorSet) {
        switch (riskLevel) {
            case 3:
                return monitorSet.getNoticeReceiveHight() != null ? monitorSet.getNoticeReceiveHight().split(",") : null;
            case 2:
                return monitorSet.getNoticeReceiveMiddle() != null ? monitorSet.getNoticeReceiveMiddle().split(",") : null;
            case 1:
                return monitorSet.getNoticeReceiveLow() != null ? monitorSet.getNoticeReceiveLow().split(",") : null;
            default:
                return null;
        }
    }

    @Override
    public List<Map<String,Object>> queryMetricReportData(MetricQueryCond metricQueryCond){
        List<Map<String,Object>> list = this.baseMapper.queryMetricReportData(metricQueryCond);
        return  list;
    }
}