package service

import (
	"encoding/json"
	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/app/model"
	versionUtil "github.com/1Panel-dev/1Panel/agent/utils/version"
	"github.com/1Panel-dev/1Panel/agent/utils/xpack"
	"math"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/repo"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/global"
	alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
	"github.com/1Panel-dev/1Panel/agent/utils/common"
	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/load"
	"github.com/shirou/gopsutil/v3/mem"
	"github.com/shirou/gopsutil/v3/net"
)

type AlertTaskHelper struct {
	DiskIO chan []disk.IOCountersStat
	NetIO  chan []net.IOCountersStat
}

type IAlertTaskHelper interface {
	StopTask()
	StartTask()
	ResetTask()
	InitTask(alertType string)
}

var cpuLoad1, cpuLoad5, cpuLoad15 []float64
var memoryLoad1, memoryLoad5, memoryLoad15 []float64

const ResourceAlertInterval = 30

var baseTypes = map[string]bool{"ssl": true, "siteEndTime": true, "panelPwdEndTime": true, "panelUpdate": true}
var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "load": true}

func NewIAlertTaskHelper() IAlertTaskHelper {
	return &AlertTaskHelper{}
}
func (m *AlertTaskHelper) StartTask() {
	baseAlert, resourceAlert := handleTask()
	if len(baseAlert) == 0 && len(resourceAlert) == 0 {
		return
	}
	handleBaseAlerts(baseAlert)
	handleResourceAlerts(resourceAlert)
}

func (m *AlertTaskHelper) StopTask() {
	stopBaseJob()
	stopResourceJob()
}

func (m *AlertTaskHelper) ResetTask() {
	m.StopTask()
	m.StartTask()
}

func (m *AlertTaskHelper) InitTask(alertType string) {
	if alertType == "cpu" {
		cpuLoad1 = []float64{}
		cpuLoad5 = []float64{}
		cpuLoad15 = []float64{}
	}
	if alertType == "memory" {
		memoryLoad1 = []float64{}
		memoryLoad5 = []float64{}
		memoryLoad15 = []float64{}
	}
	if baseTypes[alertType] {
		stopBaseJob()
	} else if resourceTypes[alertType] {
		stopResourceJob()
	}
	m.StartTask()
}

func resourceTask(resourceAlert []dto.AlertDTO) {
	for _, alert := range resourceAlert {
		if !alertUtil.CheckSendTimeRange(alert.Type) {
			continue
		}
		switch alert.Type {
		case "cpu":
			loadCPUUsage(alert)
		case "memory":
			loadMemUsage(alert)
		case "load":
			loadLoadInfo(alert)
		case "disk":
			loadDiskUsage(alert)
		default:
		}
	}
}

func baseTask(baseAlert []dto.AlertDTO) {
	for _, alert := range baseAlert {
		if !alertUtil.CheckSendTimeRange(alert.Type) {
			continue
		}
		switch alert.Type {
		case "ssl":
			loadSSLInfo(alert)
		case "siteEndTime":
			loadWebsiteInfo(alert)
		case "panelPwdEndTime":
			if global.IsMaster {
				loadPanelPwd(alert)
			}
		case "panelUpdate":
			if global.IsMaster {
				loadPanelUpdate(alert)
			}
		default:
		}
	}
}

func handleTask() (baseAlert []dto.AlertDTO, resourceAlert []dto.AlertDTO) {
	alertList, _ := NewIAlertService().GetAlerts()
	baseAlert, resourceAlert = classifyAlerts(alertList)
	return baseAlert, resourceAlert
}

func classifyAlerts(alertList []dto.AlertDTO) (baseAlert, resourceAlert []dto.AlertDTO) {
	for _, alert := range alertList {
		if baseTypes[alert.Type] {
			baseAlert = append(baseAlert, alert)
		} else if resourceTypes[alert.Type] {
			resourceAlert = append(resourceAlert, alert)
		}
	}
	return
}

func handleBaseAlerts(baseAlert []dto.AlertDTO) {
	if len(baseAlert) > 0 {
		if global.AlertBaseJobID == 0 {
			baseTask(baseAlert)
			jobID, err := global.Cron.AddFunc("*/30 * * * *", func() {
				baseTask(baseAlert)
			})
			if err != nil {
				global.LOG.Errorf("alert base job start failed: %v", err)
				return
			}
			global.AlertBaseJobID = jobID
			global.LOG.Info("start alert base job")
		}
	} else {
		stopBaseJob()
	}
}

func handleResourceAlerts(resourceAlert []dto.AlertDTO) {
	if len(resourceAlert) > 0 {
		if global.AlertResourceJobID == 0 {
			jobID, err := global.Cron.AddFunc("*/1 * * * *", func() {
				resourceTask(resourceAlert)
			})
			if err != nil {
				global.LOG.Errorf("alert resource job start failed: %v", err)
				return
			}
			global.AlertResourceJobID = jobID
			global.LOG.Info("start alert resource job")
		}
	} else {
		stopResourceJob()
	}
}

func stopBaseJob() {
	if global.AlertBaseJobID != 0 {
		global.Cron.Remove(global.AlertBaseJobID)
		global.AlertBaseJobID = 0
		global.LOG.Info("stop alert base job")
	}
}

func stopResourceJob() {
	if global.AlertResourceJobID != 0 {
		global.Cron.Remove(global.AlertResourceJobID)
		global.AlertResourceJobID = 0
		global.LOG.Info("stop alert resource job")
	}
}

func loadSSLInfo(alert dto.AlertDTO) {
	var opts []repo.DBOption
	if alert.Project != "all" {
		itemID, _ := strconv.Atoi(alert.Project)
		opts = append(opts, repo.WithByID(uint(itemID)))
	}

	sslList, _ := repo.NewISSLRepo().List(opts...)
	currentDate := time.Now()
	daysDifferenceMap := make(map[int][]string)
	projectMap := make(map[uint][]time.Time)
	for _, ssl := range sslList {
		daysDifference := int(ssl.ExpireDate.Sub(currentDate).Hours() / 24)
		if daysDifference > 0 && int(alert.Cycle) >= daysDifference {
			daysDifferenceMap[daysDifference] = append(daysDifferenceMap[daysDifference], ssl.PrimaryDomain)
			projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
		}
	}
	projectJSON := serializeAndSortProjects(projectMap)
	if projectJSON == "" {
		return
	}
	if len(daysDifferenceMap) > 0 {
		for daysDifference, ssl := range daysDifferenceMap {
			primaryDomain := strings.Join(ssl, ",")
			var params []dto.Param
			params = createAlertBaseParams(strconv.Itoa(len(ssl)), strconv.Itoa(daysDifference))
			methods := strings.Split(alert.Method, ",")
			for _, m := range methods {
				m = strings.TrimSpace(m)
				switch m {
				case constant.SMS:
					todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.SMS)
					if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
						continue
					}
					create := dto.AlertLogCreate{
						Status:  constant.AlertSuccess,
						Count:   totalCount + 1,
						AlertId: alert.ID,
						Type:    alert.Type,
					}
					if !alertUtil.CheckTaskFrequency(constant.SMS) {
						continue
					}
					_ = xpack.CreateSMSAlertLog(alert, create, primaryDomain, params, constant.SMS)
					alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.SMS)
				case constant.Email:
					todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.Email)
					if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
						continue
					}
					create := dto.AlertLogCreate{
						Status:  constant.AlertSuccess,
						Count:   totalCount + 1,
						AlertId: alert.ID,
						Type:    alert.Type,
					}
					alertDetail := alertUtil.ProcessAlertDetail(alert, primaryDomain, params, constant.Email)
					alertRule := alertUtil.ProcessAlertRule(alert)
					create.AlertRule = alertRule
					create.AlertDetail = alertDetail
					transport := xpack.LoadRequestTransport()
					_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
					alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.Email)
				default:
				}
			}
		}
		global.LOG.Info("SSL alert push successful")
	}
}

func loadWebsiteInfo(alert dto.AlertDTO) {
	var opts []repo.DBOption
	if alert.Project != "all" {
		itemID, _ := strconv.Atoi(alert.Project)
		opts = append(opts, repo.WithByID(uint(itemID)))
	}

	websiteList, _ := websiteRepo.List(opts...)
	currentDate := time.Now()
	daysDifferenceMap := make(map[int][]string)
	projectMap := make(map[uint][]time.Time)
	for _, website := range websiteList {
		daysDifference := int(website.ExpireDate.Sub(currentDate).Hours() / 24)
		if daysDifference > 0 && int(alert.Cycle) >= daysDifference {
			daysDifferenceMap[daysDifference] = append(daysDifferenceMap[daysDifference], website.PrimaryDomain)
			projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate)
		}
	}
	projectJSON := serializeAndSortProjects(projectMap)
	if projectJSON == "" {
		return
	}
	if len(daysDifferenceMap) > 0 {
		methods := strings.Split(alert.Method, ",")
		for daysDifference, websites := range daysDifferenceMap {
			primaryDomain := strings.Join(websites, ",")
			var params []dto.Param
			params = createAlertBaseParams(strconv.Itoa(len(websites)), strconv.Itoa(daysDifference))
			for _, m := range methods {
				m = strings.TrimSpace(m)
				switch m {
				case constant.SMS:
					if !alertUtil.CheckTaskFrequency(constant.SMS) {
						continue
					}
					todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.SMS)
					if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
						continue
					}
					create := dto.AlertLogCreate{
						Status:  constant.AlertSuccess,
						Count:   totalCount + 1,
						AlertId: alert.ID,
						Type:    alert.Type,
					}
					_ = xpack.CreateSMSAlertLog(alert, create, primaryDomain, params, constant.SMS)
					alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.SMS)
				case constant.Email:
					todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.Email)
					if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
						continue
					}
					create := dto.AlertLogCreate{
						Status:  constant.AlertSuccess,
						Count:   totalCount + 1,
						AlertId: alert.ID,
						Type:    alert.Type,
					}
					alertDetail := alertUtil.ProcessAlertDetail(alert, primaryDomain, params, constant.Email)
					alertRule := alertUtil.ProcessAlertRule(alert)
					create.AlertDetail = alertDetail
					create.AlertRule = alertRule
					transport := xpack.LoadRequestTransport()
					_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
					alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.Email)
				default:
				}
			}
		}
		global.LOG.Info("website expiration alert push successful")
	}
}

func loadPanelPwd(alert dto.AlertDTO) {
	// only master alert
	var expirationDays model.Setting
	if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ExpirationDays").First(&expirationDays).Error; err != nil {
		global.LOG.Errorf("load %s from db setting failed, err: %v", "ExpirationDays", err)
		return
	}
	if expirationDays.Value == "0" {
		global.LOG.Info("panel password expiration setting not enabled, skip")
		return
	}
	var expirationTime model.Setting
	if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ExpirationTime").First(&expirationTime).Error; err != nil {
		global.LOG.Errorf("load %s from db setting failed, err: %v", "ExpirationTime", err)
		return
	}

	var params []dto.Param
	defaultDate, _ := time.Parse(constant.DateTimeLayout, expirationTime.Value)
	daysDifference := calculateDaysDifference(defaultDate)
	if daysDifference >= 0 && int(alert.Cycle) >= daysDifference {
		params = createAlertPwdParams(strconv.Itoa(daysDifference))
		methods := strings.Split(alert.Method, ",")
		for _, m := range methods {
			m = strings.TrimSpace(m)
			switch m {
			case constant.SMS:
				if !alertUtil.CheckTaskFrequency(constant.SMS) {
					continue
				}
				todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, expirationTime.Value, constant.SMS)
				if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
					continue
				}
				create := dto.AlertLogCreate{
					Count:   totalCount + 1,
					AlertId: alert.ID,
					Type:    alert.Type,
				}
				_ = xpack.CreateSMSAlertLog(alert, create, strconv.Itoa(daysDifference), params, constant.SMS)
				alertUtil.CreateNewAlertTask(expirationTime.Value, alert.Type, expirationTime.Value, constant.SMS)
			case constant.Email:
				todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, expirationTime.Value, constant.Email)
				if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
					continue
				}
				create := dto.AlertLogCreate{
					Count:   totalCount + 1,
					AlertId: alert.ID,
					Type:    alert.Type,
				}
				alertDetail := alertUtil.ProcessAlertDetail(alert, strconv.Itoa(daysDifference), params, constant.Email)
				alertRule := alertUtil.ProcessAlertRule(alert)
				create.AlertRule = alertRule
				create.AlertDetail = alertDetail
				transport := xpack.LoadRequestTransport()
				_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
				alertUtil.CreateNewAlertTask(expirationTime.Value, alert.Type, expirationTime.Value, constant.Email)
			default:
			}
		}
		global.LOG.Info("panel password expiration alert push successful")
	}
}

func loadPanelUpdate(alert dto.AlertDTO) {
	// only master alert
	info, err := versionUtil.GetUpgradeVersionInfo()
	if err != nil {
		global.LOG.Errorf("error getting version, err: %s", err)
		return
	}

	// 获取版本信息
	var version string
	// 检查哪个版本字段不为空，并赋值
	if info.NewVersion != "" {
		version = info.NewVersion
	} else if info.TestVersion != "" {
		version = info.TestVersion
	} else if info.LatestVersion != "" {
		version = info.LatestVersion
	}
	if version == "" {
		return
	}

	var params []dto.Param
	methods := strings.Split(alert.Method, ",")
	for _, m := range methods {
		m = strings.TrimSpace(m)
		switch m {
		case constant.SMS:
			if !alertUtil.CheckTaskFrequency(constant.SMS) {
				continue
			}
			todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, version, constant.SMS)
			if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
				continue
			}
			var create = dto.AlertLogCreate{
				Type:    alert.Type,
				AlertId: alert.ID,
				Count:   totalCount + 1,
			}
			_ = xpack.CreateSMSAlertLog(alert, create, version, params, constant.SMS)
			alertUtil.CreateNewAlertTask(version, alert.Type, version, constant.SMS)
		case constant.Email:
			todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, version, constant.Email)
			if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
				continue
			}
			var create = dto.AlertLogCreate{
				Type:    alert.Type,
				AlertId: alert.ID,
				Count:   totalCount + 1,
			}
			alertDetail := alertUtil.ProcessAlertDetail(alert, version, params, constant.Email)
			alertRule := alertUtil.ProcessAlertRule(alert)
			create.AlertRule = alertRule
			create.AlertDetail = alertDetail
			transport := xpack.LoadRequestTransport()
			_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
			alertUtil.CreateNewAlertTask(version, alert.Type, version, constant.Email)
		default:
		}
	}
	global.LOG.Info("panel update alert push successful")
}

// 获取 CPU 使用率数据并发送到通道
func loadCPUUsage(alert dto.AlertDTO) {
	percent, err := cpu.Percent(3*time.Second, false)
	if err != nil {
		global.LOG.Errorf("error getting cpu usage, err: %v", err)
		return
	}

	if len(percent) > 0 {
		var cpuLoad *[]float64
		var threshold int

		switch alert.Cycle {
		case 1:
			cpuLoad = &cpuLoad1
			threshold = 1
		case 5:
			cpuLoad = &cpuLoad5
			threshold = 5
		case 15:
			cpuLoad = &cpuLoad15
			threshold = 15
		default:
			return
		}

		if checkAndSendAlert(alert, percent[0], cpuLoad, threshold) {
			global.LOG.Info("cpu alert push successful")
		}
	}

}

// 获取内存使用情况数据并发送到通道
func loadMemUsage(alert dto.AlertDTO) {

	memStat, err := mem.VirtualMemory()
	if err != nil {
		global.LOG.Errorf("error getting memory usage, err: %v", err)
		return
	}

	percent := memStat.UsedPercent
	var memoryLoad *[]float64
	var threshold int

	switch alert.Cycle {
	case 1:
		memoryLoad = &memoryLoad1
		threshold = 1
	case 5:
		memoryLoad = &memoryLoad5
		threshold = 5
	case 15:
		memoryLoad = &memoryLoad15
		threshold = 15
	default:
		return
	}
	if checkAndSendAlert(alert, percent, memoryLoad, threshold) {
		global.LOG.Info("memory alert push successful")
	}
}

// 获取系统负载数据并发送到通道
func loadLoadInfo(alert dto.AlertDTO) {
	avgStat, err := load.Avg()
	if err != nil {
		global.LOG.Errorf("error getting load usage, err: %v", err)
		return
	}
	var loadValue float64
	CPUTotal, _ := cpu.Counts(true)
	switch alert.Cycle {
	case 1:
		loadValue = avgStat.Load1 / (float64(CPUTotal*2) * 0.75) * 100
	case 5:
		loadValue = avgStat.Load5 / (float64(CPUTotal*2) * 0.75) * 100
	case 15:
		loadValue = avgStat.Load15 / (float64(CPUTotal*2) * 0.75) * 100
	default:
		return
	}
	newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
	if err != nil {
		global.LOG.Errorf("task log record not found, err: %v", err)
	}
	if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
		if loadValue >= float64(alert.Count) {
			global.LOG.Infof("%d minute load: %f,detail: %v", alert.Cycle, loadValue, avgStat)
			createAndLogAlert(alert, loadValue)
			global.LOG.Info("load alert task push successful")
		}
	}
}

// 内存/cpu检查是否需要发送告警并处理相关逻辑
func checkAndSendAlert(alert dto.AlertDTO, currentUsage float64, usageLoad *[]float64, threshold int) bool {
	newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
	if err != nil {
		global.LOG.Errorf("record not found, err: %v", err)
		return false
	}

	*usageLoad = append(*usageLoad, currentUsage)

	if len(*usageLoad) > threshold {
		*usageLoad = (*usageLoad)[1:]
	}

	if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
		if len(*usageLoad) == threshold {
			avgUsage := average(*usageLoad)
			if avgUsage >= float64(alert.Count) {
				global.LOG.Infof("%d minute %s: %f , usage: %v", threshold, alert.Type, avgUsage, usageLoad)
				createAndLogAlert(alert, avgUsage)
				return true
			}
		}
	}
	return false
}

// 检查是否超过今日发送次数限制
func checkTaskFrequency(alertType, quotaType string, sendCount uint, method string) (uint, bool) {
	todayCount, _, err := alertRepo.LoadTaskCount(alertType, quotaType, method)
	if err != nil {
		global.LOG.Errorf("error getting task info, err: %v", err)
		return todayCount, false
	}
	if todayCount >= sendCount {
		return todayCount, false
	}

	return todayCount, true
}

// 创建告警日志和详情
func createAndLogAlert(alert dto.AlertDTO, avgUsage float64) {
	avgUsagePercent := common.FormatPercent(avgUsage)
	params := createAlertAvgParams(strconv.Itoa(int(alert.Cycle)), getModule(alert.Type), avgUsagePercent)
	methods := strings.Split(alert.Method, ",")
	for _, m := range methods {
		m = strings.TrimSpace(m)
		switch m {
		case constant.SMS:
			if !alertUtil.CheckTaskFrequency(constant.SMS) {
				continue
			}
			todayCount, isValid := checkTaskFrequency(alert.Type, strconv.Itoa(int(alert.Cycle)), alert.SendCount, constant.SMS)
			if !isValid {
				continue
			}
			create := dto.AlertLogCreate{
				Status:  constant.AlertSuccess,
				Count:   todayCount + 1,
				AlertId: alert.ID,
				Type:    alert.Type,
			}
			_ = xpack.CreateSMSAlertLog(alert, create, avgUsagePercent, params, constant.SMS)
			alertUtil.CreateNewAlertTask(avgUsagePercent, alert.Type, strconv.Itoa(int(alert.Cycle)), constant.SMS)
		case constant.Email:
			todayCount, isValid := checkTaskFrequency(alert.Type, strconv.Itoa(int(alert.Cycle)), alert.SendCount, constant.Email)
			if !isValid {
				continue
			}
			create := dto.AlertLogCreate{
				Status:  constant.AlertSuccess,
				Count:   todayCount + 1,
				AlertId: alert.ID,
				Type:    alert.Type,
			}
			alertDetail := alertUtil.ProcessAlertDetail(alert, avgUsagePercent, params, constant.Email)
			alertRule := alertUtil.ProcessAlertRule(alert)
			create.AlertRule = alertRule
			create.AlertDetail = alertDetail
			transport := xpack.LoadRequestTransport()
			_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
			alertUtil.CreateNewAlertTask(avgUsagePercent, alert.Type, strconv.Itoa(int(alert.Cycle)), constant.Email)
		default:
		}
	}
}

func getModule(alertType string) string {
	var module string
	switch alertType {
	case "cpu":
		module = " CPU "
	case "memory":
		module = "内存"
	case "load":
		module = "负载"
	default:
	}
	return module
}

func loadDiskUsage(alert dto.AlertDTO) {
	newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
	if err != nil {
		global.LOG.Errorf("record not found, err: %v", err)
	}

	if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
		if strings.Contains(alert.Project, "all") {
			err = processAllDisks(alert)
		} else {
			err = processSingleDisk(alert)
		}
		if err != nil {
			global.LOG.Errorf("error processing disk usage, err: %v", err)
		}
	}
}

func processAllDisks(alert dto.AlertDTO) error {
	diskList, err := NewIAlertService().GetDisks()
	if err != nil {
		global.LOG.Errorf("error getting disk list, err: %v", err)
		return err
	}

	var flag bool
	for _, item := range diskList {
		if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
			flag = true
		}
	}
	if flag {
		global.LOG.Info("all disk alert push successful")
	}
	return nil
}

func processSingleDisk(alert dto.AlertDTO) error {
	success, err := checkAndCreateDiskAlert(alert, alert.Project)
	if err != nil {
		return err
	}
	if success {
		global.LOG.Info("disk alert push successful")
	}
	return nil
}

func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
	usageStat, err := disk.Usage(path)
	if err != nil {
		global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
		return false, err
	}

	usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
	commonTotal := float64(alert.Count)
	if alert.Cycle == 1 {
		commonTotal *= 1024 * 1024 * 1024
	}
	if usedTotal < commonTotal {
		return false, nil
	}
	global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr)
	var params []dto.Param
	params = createAlertDiskParams(path, usedStr)
	methods := strings.Split(alert.Method, ",")
	for _, m := range methods {
		m = strings.TrimSpace(m)
		switch m {
		case constant.SMS:
			if !alertUtil.CheckTaskFrequency(constant.SMS) {
				continue
			}
			todayCount, isValid := checkTaskFrequency(alert.Type, alert.Project, alert.SendCount, constant.SMS)
			if !isValid {
				continue
			}
			create := dto.AlertLogCreate{
				Status:  constant.AlertSuccess,
				Count:   todayCount + 1,
				AlertId: alert.ID,
				Type:    alert.Type,
			}
			_ = xpack.CreateSMSAlertLog(alert, create, path, params, constant.SMS)
			alertUtil.CreateNewAlertTask(strconv.Itoa(int(alert.Cycle)), alert.Type, alert.Project, constant.SMS)
		case constant.Email:
			todayCount, isValid := checkTaskFrequency(alert.Type, alert.Project, alert.SendCount, constant.Email)
			if !isValid {
				continue
			}
			create := dto.AlertLogCreate{
				Status:  constant.AlertSuccess,
				Count:   todayCount + 1,
				AlertId: alert.ID,
				Type:    alert.Type,
			}
			alertDetail := alertUtil.ProcessAlertDetail(alert, path, params, constant.Email)
			alertRule := alertUtil.ProcessAlertRule(alert)
			create.AlertRule = alertRule
			create.AlertDetail = alertDetail
			transport := xpack.LoadRequestTransport()
			_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
			alertUtil.CreateNewAlertTask(strconv.Itoa(int(alert.Cycle)), alert.Type, alert.Project, constant.Email)
		default:
		}
	}

	return true, nil
}

func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
	if cycle == 1 {
		return float64(usageStat.Used), common.FormatBytes(usageStat.Used)
	}
	return usageStat.UsedPercent, common.FormatPercent(usageStat.UsedPercent)
}

func calculateDaysDifference(expirationTime time.Time) int {
	currentDate := time.Now()
	formattedTime := currentDate.Format(constant.DateTimeLayout)
	parsedTime, _ := time.Parse(constant.DateTimeLayout, formattedTime)
	timeGap := expirationTime.Sub(parsedTime).Milliseconds()
	if timeGap < 0 {
		return -1
	}
	daysDifference := int(math.Floor(float64(timeGap) / (3600 * 1000 * 24)))
	return daysDifference
}

func calculateMinutesDifference(newDate time.Time) int {
	now := time.Now()
	if newDate.After(now) {
		return -1
	}
	minutesDifference := int(now.Sub(newDate).Minutes())
	return minutesDifference
}

func average(arr []float64) float64 {
	total := 0.0
	for _, v := range arr {
		total += v
	}
	return total / float64(len(arr))
}

func createAlertBaseParams(project, cycle string) []dto.Param {
	return []dto.Param{
		{
			Index: "1",
			Key:   "project",
			Value: project,
		},
		{
			Index: "2",
			Key:   "cycle",
			Value: cycle,
		},
	}
}

func createAlertPwdParams(cycle string) []dto.Param {
	return []dto.Param{
		{
			Index: "1",
			Key:   "cycle",
			Value: cycle,
		},
	}
}

func createAlertAvgParams(cycle, module, count string) []dto.Param {
	return []dto.Param{
		{
			Index: "1",
			Key:   "cycle",
			Value: cycle,
		},
		{
			Index: "2",
			Key:   "module",
			Value: module,
		},
		{
			Index: "3",
			Key:   "count",
			Value: count,
		},
	}
}

func createAlertDiskParams(project, count string) []dto.Param {
	return []dto.Param{
		{
			Index: "1",
			Key:   "project",
			Value: project,
		},
		{
			Index: "2",
			Key:   "count",
			Value: count,
		},
	}
}

func serializeAndSortProjects(projectMap map[uint][]time.Time) string {
	keys := make([]int, 0, len(projectMap))
	for k := range projectMap {
		keys = append(keys, int(k))
	}
	sort.Ints(keys)
	projectJSON, err := json.Marshal(projectMap)
	if err != nil {
		global.LOG.Errorf("Failed to serialize projectMap: %v", err)
		return ""
	}

	return string(projectJSON)
}
