package models

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/didi/nightingale/v5/vos"

	"github.com/toolkits/pkg/logger"
	"github.com/toolkits/pkg/str"
	"xorm.io/builder"
)

type AlertEvent struct {
	Id                 int64             `json:"id"`
	RuleId             int64             `json:"rule_id"`
	RuleName           string            `json:"rule_name"`
	RuleNote           string            `json:"rule_note"`
	HashId             string            `json:"hash_id"`                 // 唯一标识
	IsPromePull        int               `json:"is_prome_pull"`           // 代表是否是prometheus pull告警,为1时前端使用 ReadableExpression 拉取最近1小时数据
	LastSend           bool              `json:"last_sent" xorm:"-"`      // true 代表上次发了,false代表还没发:给prometheus做for判断的
	AlertDuration      int64             `xorm:"-" json:"alert_duration"` // 告警统计周期,PULL模型会当做P8S的for时间
	ResClasspaths      string            `json:"res_classpaths"`
	ResIdent           string            `json:"res_ident" xorm:"-"` // res_ident会出现在tags字段,就不用单独写入数据库了,但是各块逻辑中有个单独的res_ident字段更便于处理,所以struct里还留有这个字段;前端不用展示这个字段
	Priority           int               `json:"priority"`
	Status             int               `json:"status"`               // 标识是否 被屏蔽
	IsRecovery         int               `json:"is_recovery" xorm:"-"` // 0: alert, 1: recovery
	HistoryPoints      json.RawMessage   `json:"history_points"`       // HistoryPoints{}
	TriggerTime        int64             `json:"trigger_time"`
	Values             string            `json:"values" xorm:"-"` // e.g. cpu.idle: 23.3; load.1min: 32
	NotifyChannels     string            `json:"notify_channels"`
	NotifyGroups       string            `json:"notify_groups"`
	NotifyUsers        string            `json:"notify_users"`
	RunbookUrl         string            `json:"runbook_url"`
	ReadableExpression string            `json:"readable_expression"` // e.g. mem.bytes.used.percent(all,60s) > 0
	Tags               string            `json:"tags"`                // merge data_tags rule_tags and res_tags
	NotifyGroupObjs    []UserGroup       `json:"notify_group_objs" xorm:"-"`
	NotifyUserObjs     []User            `json:"notify_user_objs" xorm:"-"`
	TagMap             map[string]string `json:"tag_map" xorm:"-"`
}

// IsAlert 语法糖,避免直接拿IsRecovery字段做比对不直观易出错
func (ae *AlertEvent) IsAlert() bool {
	return ae.IsRecovery != 1
}

// IsRecov 语法糖,避免直接拿IsRecovery字段做比对不直观易出错
func (ae *AlertEvent) IsRecov() bool {
	return ae.IsRecovery == 1
}

// MarkAlert 语法糖,标记为告警状态
func (ae *AlertEvent) MarkAlert() {
	ae.IsRecovery = 0
}

// MarkRecov 语法糖,标记为恢复状态
func (ae *AlertEvent) MarkRecov() {
	ae.IsRecovery = 1
}

// MarkMuted 语法糖,标记为屏蔽状态
func (ae *AlertEvent) MarkMuted() {
	ae.Status = 1
}

func (ae *AlertEvent) String() string {
	return fmt.Sprintf("id:%d,rule_id:%d,rule_name:%s,rule_note:%s,hash_id:%s,is_prome_pull:%d,alert_duration:%d,res_classpaths:%s,res_ident:%s,priority:%d,status:%d,is_recovery:%d,history_points:%s,trigger_time:%d,values:%s,notify_channels:%s,runbook_url:%s,readable_expression:%s,tags:%s,notify_group_objs:%+v,notify_user_objs:%+v,tag_map:%v",
		ae.Id,
		ae.RuleId,
		ae.RuleName,
		ae.RuleNote,
		ae.HashId,
		ae.IsPromePull,
		ae.AlertDuration,
		ae.ResClasspaths,
		ae.ResIdent,
		ae.Priority,
		ae.Status,
		ae.IsRecovery,
		string(ae.HistoryPoints),
		ae.TriggerTime,
		ae.Values,
		ae.NotifyChannels,
		ae.RunbookUrl,
		ae.ReadableExpression,
		ae.Tags,
		ae.NotifyGroupObjs,
		ae.NotifyUserObjs,
		ae.TagMap)
}

func (ae *AlertEvent) TableName() string {
	return "alert_event"
}

func (ae *AlertEvent) FillObjs() error {
	userGroupIds := strings.Fields(ae.NotifyGroups)
	if len(userGroupIds) > 0 {
		groups, err := UserGroupGetsByIdsStr(userGroupIds)
		if err != nil {
			return err
		}
		ae.NotifyGroupObjs = groups
	}

	userIds := strings.Fields(ae.NotifyUsers)
	if len(userIds) > 0 {
		users, err := UserGetsByIdsStr(userIds)
		if err != nil {
			return err
		}
		ae.NotifyUserObjs = users
	}

	return nil
}

func (ae *AlertEvent) GetHistoryPoints() ([]vos.HistoryPoints, error) {
	historyPoints := []vos.HistoryPoints{}

	err := json.Unmarshal([]byte(ae.HistoryPoints), &historyPoints)
	return historyPoints, err
}

func (ae *AlertEvent) Add() error {
	return DBInsertOne(ae)
}

func (ar *AlertEvent) DelByHashId() error {
	_, err := DB.Where("hash_id=?", ar.HashId).Delete(new(AlertEvent))
	if err != nil {
		logger.Errorf("mysql.error: delete alert_event fail: %v", err)
		return internalServerError
	}

	return nil
}

func (ar *AlertEvent) HashIdExists() (bool, error) {
	num, err := DB.Where("hash_id=?", ar.HashId).Count(new(AlertEvent))
	return num > 0, err
}

func (ar *AlertEvent) Del() error {
	_, err := DB.Where("id=?", ar.Id).Delete(new(AlertEvent))
	if err != nil {
		logger.Errorf("mysql.error: delete alert_event fail: %v", err)
		return internalServerError
	}

	return nil
}

func AlertEventsDel(ids []int64) error {
	if len(ids) == 0 {
		return fmt.Errorf("param ids is empty")
	}

	_, err := DB.Exec("DELETE FROM alert_event where id in (" + str.IdsString(ids) + ")")
	if err != nil {
		logger.Errorf("mysql.error: delete alert_event(%v) fail: %v", ids, err)
		return internalServerError
	}

	return nil
}

func AlertEventTotal(stime, etime int64, query string, status, priority int) (num int64, err error) {
	cond := builder.NewCond()
	if stime != 0 && etime != 0 {
		cond = cond.And(builder.Between{Col: "trigger_time", LessVal: stime, MoreVal: etime})
	}

	if status != -1 {
		cond = cond.And(builder.Eq{"status": status})
	}

	if priority != -1 {
		cond = cond.And(builder.Eq{"priority": priority})
	}

	if query != "" {
		cond = cond.And(builder.Like{"res_classpaths", "%" + query + "%"})
		cond = cond.And(builder.Like{"rule_name", "%" + query + "%"})
		cond = cond.And(builder.Like{"tags", "%" + query + "%"})
	}

	num, err = DB.Where(cond).Count(new(AlertEvent))
	if err != nil {
		logger.Errorf("mysql.error: count alert_event fail: %v", err)
		return 0, internalServerError
	}

	return num, nil
}

func AlertEventGets(stime, etime int64, query string, status, priority int, limit, offset int) ([]AlertEvent, error) {
	cond := builder.NewCond()
	if stime != 0 && etime != 0 {
		cond = cond.And(builder.Between{Col: "trigger_time", LessVal: stime, MoreVal: etime})
	}

	if status != -1 {
		cond = cond.And(builder.Eq{"status": status})
	}

	if priority != -1 {
		cond = cond.And(builder.Eq{"priority": priority})
	}

	if query != "" {
		cond = cond.And(builder.Like{"res_classpaths", "%" + query + "%"})
		cond = cond.And(builder.Like{"rule_name", "%" + query + "%"})
		cond = cond.And(builder.Like{"tags", "%" + query + "%"})
	}

	var objs []AlertEvent
	err := DB.Where(cond).Desc("trigger_time").Limit(limit, offset).Find(&objs)
	if err != nil {
		logger.Errorf("mysql.error: query alert_event fail: %v", err)
		return objs, internalServerError
	}

	if len(objs) == 0 {
		return []AlertEvent{}, nil
	}

	return objs, nil
}

func AlertEventGet(where string, args ...interface{}) (*AlertEvent, error) {
	var obj AlertEvent
	has, err := DB.Where(where, args...).Get(&obj)
	if err != nil {
		logger.Errorf("mysql.error: query alert_event(%s)%+v fail: %s", where, args, err)
		return nil, internalServerError
	}

	if !has {
		return nil, nil
	}

	return &obj, nil
}