package service

import (
	"strconv"
	"strings"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/app/model"
	"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/xpack"
)

type AlertSender struct {
	alert     dto.AlertDTO
	quotaType string
}

func NewAlertSender(alert dto.AlertDTO, quotaType string) *AlertSender {
	return &AlertSender{
		alert:     alert,
		quotaType: quotaType,
	}
}

func (s *AlertSender) Send(quota string, params []dto.Param) {
	s.sendByConfigIds(s.alert.Method, quota, params, false)
}

func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
	s.sendByConfigIds(s.alert.Method, quota, params, true)
}

func (s *AlertSender) sendByConfigIds(methodStr string, quota string, params []dto.Param, isResource bool) {
	alertRepo := repo.NewIAlertRepo()
	configIds := strings.Split(methodStr, ",")
	for _, idStr := range configIds {
		idStr = strings.TrimSpace(idStr)
		configId, err := strconv.ParseUint(idStr, 10, 64)
		if err != nil {
			s.sendByLegacyMethod(idStr, quota, params, isResource)
			continue
		}
		config, err := alertRepo.GetConfigById(uint(configId))
		if err != nil {
			global.LOG.Errorf("alert config not found for id %d: %v", configId, err)
			continue
		}
		s.sendByConfig(config, quota, params, isResource)
	}
}

func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, params []dto.Param, isResource bool) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	switch config.Type {
	case constant.SMS:
		if isResource {
			s.sendResourceSMSWithConfig(config, quota, params)
		} else {
			s.sendSMSWithConfig(config, quota, params)
		}
	case constant.Email:
		if isResource {
			s.sendResourceEmailWithConfig(config, quota, params)
		} else {
			s.sendEmailWithConfig(config, quota, params)
		}
	case constant.Bark:
		if isResource {
			s.sendResourceBarkWithConfig(config, quota, params)
		} else {
			s.sendBarkWithConfig(config, quota, params)
		}
	case constant.WeCom, constant.DingTalk, constant.FeiShu:
		if isResource {
			s.sendResourceWebhookWithConfig(config, quota, params)
		} else {
			s.sendWebhookWithConfig(config, quota, params)
		}
	}
}

func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
	alertRepo := repo.NewIAlertRepo()
	typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS}
	configType := method
	if mapped, ok := typeMap[method]; ok {
		configType = mapped
	}
	config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
	if err != nil {
		return
	}
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	s.sendByConfig(config, quota, params, isResource)
}

func (s *AlertSender) sendSMSWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	method := strconv.Itoa(int(config.ID))
	if !alertUtil.CheckSMSSendLimit(config, method) {
		return
	}

	totalCount, isValid := s.canSendAlert(method)
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:  constant.AlertSuccess,
		Count:   totalCount + 1,
		AlertId: s.alert.ID,
		Type:    s.alert.Type,
		Method:  method,
	}

	err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, config, method)
	if err != nil {
		global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
}

func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMS))
	if err != nil {
		return
	}
	s.sendSMSWithConfig(config, quota, params)
}

func (s *AlertSender) sendEmailWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:      constant.AlertSuccess,
		Count:       totalCount + 1,
		AlertId:     s.alert.ID,
		Type:        s.alert.Type,
		AlertRule:   alertUtil.ProcessAlertRule(s.alert),
		AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
		Method:      strconv.Itoa(int(config.ID)),
	}

	transport := xpack.MultiNodeProvider.LoadRequestTransport()
	agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
	err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo, config)
	if err != nil {
		global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
}

func (s *AlertSender) sendResourceEmailWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:      constant.AlertSuccess,
		Count:       todayCount + 1,
		AlertId:     s.alert.ID,
		Type:        s.alert.Type,
		AlertRule:   alertUtil.ProcessAlertRule(s.alert),
		AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
		Method:      strconv.Itoa(int(config.ID)),
	}

	transport := xpack.MultiNodeProvider.LoadRequestTransport()
	agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
	if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo, config); err != nil {
		global.LOG.Errorf("failed to send Email alert: %v", err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
}

func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
	if err != nil {
		return
	}
	s.sendEmailWithConfig(config, quota, params)
}

func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
	if err != nil {
		return
	}
	s.sendResourceEmailWithConfig(config, quota, params)
}

func (s *AlertSender) sendBarkWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:      constant.AlertSuccess,
		Count:       totalCount + 1,
		AlertId:     s.alert.ID,
		Type:        s.alert.Type,
		AlertRule:   alertUtil.ProcessAlertRule(s.alert),
		AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
		Method:      strconv.Itoa(int(config.ID)),
	}

	transport := xpack.MultiNodeProvider.LoadRequestTransport()
	agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
	err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo, config)
	if err != nil {
		global.LOG.Errorf("%s alert bark push failed: %v", s.alert.Type, err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
}

func (s *AlertSender) sendResourceBarkWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:      constant.AlertSuccess,
		Count:       todayCount + 1,
		AlertId:     s.alert.ID,
		Type:        s.alert.Type,
		AlertRule:   alertUtil.ProcessAlertRule(s.alert),
		AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
		Method:      strconv.Itoa(int(config.ID)),
	}

	transport := xpack.MultiNodeProvider.LoadRequestTransport()
	agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
	if err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo, config); err != nil {
		global.LOG.Errorf("failed to send Bark alert: %v", err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
}

func (s *AlertSender) sendBark(quota string, params []dto.Param) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
	if err != nil {
		return
	}
	s.sendBarkWithConfig(config, quota, params)
}

func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
	if err != nil {
		return
	}
	s.sendResourceBarkWithConfig(config, quota, params)
}

func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:  constant.AlertSuccess,
		Count:   totalCount + 1,
		AlertId: s.alert.ID,
		Type:    s.alert.Type,
		Method:  strconv.Itoa(int(config.ID)),
	}
	transport := xpack.MultiNodeProvider.LoadRequestTransport()
	agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
	err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
	if err != nil {
		global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
}

func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:  constant.AlertSuccess,
		Count:   todayCount + 1,
		AlertId: s.alert.ID,
		Type:    s.alert.Type,
		Method:  strconv.Itoa(int(config.ID)),
	}
	transport := xpack.MultiNodeProvider.LoadRequestTransport()
	agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
	if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo); err != nil {
		global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
}

func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(method))
	if err != nil {
		return
	}
	s.sendWebhookWithConfig(config, quota, params)
}

func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, method string) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(method))
	if err != nil {
		return
	}
	s.sendResourceWebhookWithConfig(config, quota, params)
}

func (s *AlertSender) sendResourceSMSWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
	if !alertUtil.IsAlertConfigEnabled(config) {
		return
	}
	method := strconv.Itoa(int(config.ID))
	if !alertUtil.CheckSMSSendLimit(config, method) {
		return
	}

	todayCount, isValid := s.canResourceSendAlert(method)
	if !isValid {
		return
	}

	create := dto.AlertLogCreate{
		Status:  constant.AlertSuccess,
		Count:   todayCount + 1,
		AlertId: s.alert.ID,
		Type:    s.alert.Type,
		Method:  method,
	}

	if err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, config, method); err != nil {
		global.LOG.Errorf("failed to send SMS alert: %v", err)
		return
	}
	alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
}

func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
	alertRepo := repo.NewIAlertRepo()
	config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
	if err != nil {
		return
	}
	s.sendResourceSMSWithConfig(config, quota, params)
}

func (s *AlertSender) canSendAlert(method string) (uint, bool) {
	todayCount, totalCount, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
	if err != nil {
		global.LOG.Errorf("error getting task count: %v", err)
		return totalCount, false
	}

	if todayCount >= 1 || s.alert.SendCount <= totalCount {
		return totalCount, false
	}
	return totalCount, true
}

func (s *AlertSender) canResourceSendAlert(method string) (uint, bool) {
	todayCount, _, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
	if err != nil {
		global.LOG.Errorf("error getting task count: %v", err)
		return todayCount, false
	}
	if s.alert.SendCount <= todayCount {
		return todayCount, false
	}
	return todayCount, true
}
