package alert

import (
	"encoding/json"
	"fmt"
	"github.com/1Panel-dev/1Panel/agent/app/model"
	"github.com/1Panel-dev/1Panel/agent/app/repo"
	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/i18n"
	"github.com/1Panel-dev/1Panel/agent/utils/email"
	"github.com/jinzhu/copier"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/global"
)

var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"}

func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport) error {
	params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
	alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
	alertRule := ProcessAlertRule(alert)
	create.AlertRule = alertRule
	create.AlertDetail = alertDetail
	return CreateEmailAlertLog(create, alert, params, transport)
}

func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport) error {
	var alertLog model.AlertLog
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
	if err != nil {
		return err
	}
	var cfg dto.AlertCommonConfig
	err = json.Unmarshal([]byte(config.Config), &cfg)
	if err != nil {
		return err
	}
	create.Method = constant.Email
	// 获取远端推送信息
	if !global.IsMaster && cfg.IsOffline == constant.StatusEnable {
		create.Status = constant.AlertPushing
		return SaveAlertLog(create, &alertLog)
	} else {
		emailConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
		if err != nil {
			return err
		}
		var emailInfo dto.AlertEmailConfig
		err = json.Unmarshal([]byte(emailConfig.Config), &emailInfo)
		if err != nil {
			return err
		}
		smtpConfig := email.SMTPConfig{
			Host:       emailInfo.Host,
			Port:       emailInfo.Port,
			Username:   emailInfo.Sender,
			Password:   emailInfo.Password,
			From:       fmt.Sprintf("%s <%s>", emailInfo.DisplayName, emailInfo.Sender),
			Encryption: emailInfo.Encryption,
			Recipient:  emailInfo.Recipient,
		}
		content := alert.Title
		if GetEmailContent(alert.Type, params) != "" {
			content = GetEmailContent(alert.Type, params)
		}
		msg := email.EmailMessage{
			Subject: i18n.GetMsgByKey("PanelAlertTitle"),
			Body:    content,
			IsHTML:  true,
		}

		if err = email.SendMail(smtpConfig, msg, transport); err != nil {
			create.Message = err.Error()
			create.Status = constant.AlertError
			return SaveAlertLog(create, &alertLog)
		}
		create.Status = constant.AlertSuccess
		return SaveAlertLog(create, &alertLog)
	}
}

func SaveAlertLog(create dto.AlertLogCreate, alertLog *model.AlertLog) error {
	alertRepo := repo.NewIAlertRepo()
	if err := copier.Copy(&alertLog, &create); err != nil {
		return buserr.WithErr("ErrStructTransform", err)
	}

	if err := alertRepo.CreateLog(alertLog); err != nil {
		global.LOG.Errorf("Error creating alert logs, err: %v", err)
		return err
	}

	return nil
}

func CreateNewAlertTask(quota, alertType, quotaType, method string) {
	alertRepo := repo.NewIAlertRepo()
	taskBase := model.AlertTask{
		Type:      alertType,
		Quota:     quota,
		QuotaType: quotaType,
		Method:    method,
	}
	err := alertRepo.CreateAlertTask(&taskBase)
	if err != nil {
		global.LOG.Errorf("error creating alert tasks, err: %v", err)
	}
}

func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param, method string) string {
	alertDetail := dto.AlertDetail{
		Type:    GetCronJobType(alert.Type),
		SubType: alert.Type,
		Title:   alert.Title,
		Method:  method,
		Project: project,
		Params:  params,
	}
	marshal, err := json.Marshal(alertDetail)
	if err != nil {
		global.LOG.Errorf("error processing alert detail, err: %v", err)
		return ""
	}
	return string(marshal)
}

func ProcessAlertRule(alert dto.AlertDTO) string {
	marshal, err := json.Marshal(alert)
	if err != nil {
		global.LOG.Errorf("error processing alert rule, err: %v", err)
		return ""
	}
	return string(marshal)
}

func GetCronJobType(alertType string) string {
	for _, at := range cronJobAlertTypes {
		if at == alertType {
			return "cronJob"
		}
	}
	return alertType
}

func GetCronJobTypeName(cronJobType string) string {
	module := cronJobType
	switch cronJobType {
	case "shell":
		module = "Shell 脚本"
	case "app":
		module = "备份应用"
	case "website":
		module = "备份网站"
	case "database":
		module = "备份数据库"
	case "log":
		module = "备份日志"
	case "directory":
		module = "备份目录"
	case "curl":
		module = "访问 URL"
	case "cutWebsiteLog":
		module = "切割网站日志"
	case "clean":
		module = "缓存清理"
	case "snapshot":
		module = "系统快照"
	case "ntp":
		module = "同步服务器时间"
	default:
	}
	return module
}

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

var checkTaskMutex sync.Mutex

func CheckTaskFrequency(method string) bool {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
	if err != nil {
		return false
	}
	var cfg dto.AlertSmsConfig
	err = json.Unmarshal([]byte(config.Config), &cfg)
	if err != nil {
		return false
	}
	limitCount := cfg.AlertDailyNum
	checkTaskMutex.Lock()
	todayCount, err := alertRepo.GetLicensePushCount(method)
	defer checkTaskMutex.Unlock()
	if err != nil {
		global.LOG.Errorf("error getting license push count info, err: %v", err)
		return false
	}
	if todayCount >= limitCount {
		return false
	}

	return true
}

type Settings struct {
	NoticeAlert   Category `json:"noticeAlert"`
	ResourceAlert Category `json:"resourceAlert"`
}

type Category struct {
	SendTimeRange string   `json:"sendTimeRange"`
	Type          []string `json:"type"`
}

// CheckSendTimeRange 是否在时间范围内
func CheckSendTimeRange(alertType string) bool {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
	if err != nil {
		return false
	}
	var cfg dto.AlertCommonConfig
	err = json.Unmarshal([]byte(config.Config), &cfg)
	if err != nil {
		return false
	}

	var timeRange string
	if contains(cfg.AlertSendTimeRange.NoticeAlert.Type, alertType) {
		timeRange = cfg.AlertSendTimeRange.NoticeAlert.SendTimeRange
	} else if contains(cfg.AlertSendTimeRange.ResourceAlert.Type, alertType) {
		timeRange = cfg.AlertSendTimeRange.ResourceAlert.SendTimeRange
	} else {
		global.LOG.Warnf("Alert type not found in sendTimeRange: %s", alertType)
		return false
	}

	if !isWithinTimeRange(timeRange) {
		return false
	}
	return true
}

func contains(arr []string, target string) bool {
	for _, item := range arr {
		if item == target {
			return true
		}
	}
	return false
}

func isWithinTimeRange(savedTimeString string) bool {
	now := time.Now()
	timeParts := strings.Split(savedTimeString, " - ")
	if len(timeParts) != 2 {
		global.LOG.Info("Time range string format error, should be: 'HH:MM:SS - HH:MM:SS'")
		return false
	}
	startTime, err1 := time.Parse("15:04:05", strings.TrimSpace(timeParts[0]))
	endTime, err2 := time.Parse("15:04:05", strings.TrimSpace(timeParts[1]))
	if err1 != nil || err2 != nil {
		global.LOG.Infof("Invalid time format in range: %s, errors: %v, %v", savedTimeString, err1, err2)
		return false
	}

	skipTime := time.Date(now.Year(), now.Month(), now.Day(), startTime.Hour(), startTime.Minute(), startTime.Second(), 0, now.Location())
	endSkipTime := time.Date(now.Year(), now.Month(), now.Day(), endTime.Hour(), endTime.Minute(), endTime.Second(), 0, now.Location())

	if endSkipTime.Before(skipTime) {
		return now.After(skipTime) || now.Before(endSkipTime)
	}
	return now.After(skipTime) && now.Before(endSkipTime)
}

func GetEmailContent(alertType string, params []dto.Param) string {
	switch GetCronJobType(alertType) {
	case "ssl":
		return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
	case "siteEndTime":
		return i18n.GetMsgWithMap("WebSiteAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
	case "panelPwdEndTime":
		return i18n.GetMsgWithMap("PanelPwdExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
	case "licenseTime":
		return i18n.GetMsgWithMap("LicenseExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
	case "panelUpdate":
		return i18n.GetMsgByKey("PanelVersionAlert")
	case "cpu":
		return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
	case "memory":
		return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
	case "load":
		return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
	case "disk":
		return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2")})
	case "cronJob":
		return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1")})
	case "clams":
		return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
	default:
		return ""
	}
}

func getValueByIndex(params []dto.Param, index string) string {
	for _, p := range params {
		if p.Index == index {
			return p.Value
		}
	}
	return ""
}
