package singleton

import (
	"cmp"
	"crypto/tls"
	"fmt"
	"html"
	"log"
	"net"
	"net/smtp"
	"slices"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/nezhahq/nezha/model"
	"github.com/nezhahq/nezha/pkg/utils"
)

const (
	firstNotificationDelay = time.Minute * 15
)

func init() {
	model.SendGlobalTelegramFunc = func(message string) error {
		if Conf.TelegramBotToken != "" && Conf.TelegramAdminChatID != "" {
			adminChatID, err := strconv.ParseInt(Conf.TelegramAdminChatID, 10, 64)
			if err == nil && adminChatID != 0 {
				safeDesc := html.EscapeString(message)
				msg := fmt.Sprintf("⚠️ <b>Nezha 报警通知</b>\n\n%s", safeDesc)
				sendTGMessage(adminChatID, msg)
				log.Printf("NEZHA>> Sent notification to Telegram Admin Bot")
				return nil
			}
		}
		return nil
	}

	model.SendGlobalEmailFunc = func(message string) error {
		if Conf.SMTPServer != "" && Conf.SMTPUser != "" && Conf.SMTPPassword != "" && Conf.AdminEmail != "" {
			if err := sendSMTPAdminNotification(message); err != nil {
				log.Printf("NEZHA>> Sending admin email notification failed: %v", err)
				return err
			} else {
				log.Printf("NEZHA>> Sent notification to Admin Email")
				return nil
			}
		}
		return nil
	}
}

type NotificationClass struct {
	class[uint64, *model.Notification]

	groupToIDList map[uint64]map[uint64]*model.Notification
	idToGroupList map[uint64]map[uint64]struct{}

	groupList map[uint64]string
	groupMu   sync.RWMutex
}

func NewNotificationClass() *NotificationClass {
	var sortedList []*model.Notification

	groupToIDList := make(map[uint64]map[uint64]*model.Notification)
	idToGroupList := make(map[uint64]map[uint64]struct{})

	groupNotifications := make(map[uint64][]uint64)
	var ngn []model.NotificationGroupNotification
	DB.Find(&ngn)

	for _, n := range ngn {
		groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID)
	}

	DB.Find(&sortedList)
	list := make(map[uint64]*model.Notification, len(sortedList))
	for _, n := range sortedList {
		list[n.ID] = n
	}

	var groups []model.NotificationGroup
	DB.Find(&groups)
	groupList := make(map[uint64]string)
	for _, grp := range groups {
		groupList[grp.ID] = grp.Name
	}

	for gid, nids := range groupNotifications {
		groupToIDList[gid] = make(map[uint64]*model.Notification)
		for _, nid := range nids {
			if n, ok := list[nid]; ok {
				groupToIDList[gid][n.ID] = n

				if idToGroupList[n.ID] == nil {
					idToGroupList[n.ID] = make(map[uint64]struct{})
				}

				idToGroupList[n.ID][gid] = struct{}{}
			}
		}
	}

	nc := &NotificationClass{
		class: class[uint64, *model.Notification]{
			list:       list,
			sortedList: sortedList,
		},
		groupToIDList: groupToIDList,
		idToGroupList: idToGroupList,
		groupList:     groupList,
	}
	return nc
}

func (c *NotificationClass) Update(n *model.Notification) {
	c.listMu.Lock()

	_, ok := c.list[n.ID]
	c.list[n.ID] = n

	if ok {
		if gids, ok := c.idToGroupList[n.ID]; ok {
			for gid := range gids {
				c.groupToIDList[gid][n.ID] = n
			}
		}
	}

	c.listMu.Unlock()
	c.sortList()
}

func (c *NotificationClass) UpdateGroup(ng *model.NotificationGroup, ngn []uint64) {
	c.groupMu.Lock()
	defer c.groupMu.Unlock()

	_, ok := c.groupList[ng.ID]
	c.groupList[ng.ID] = ng.Name

	c.listMu.Lock()
	defer c.listMu.Unlock()
	if !ok {
		c.groupToIDList[ng.ID] = make(map[uint64]*model.Notification, len(ngn))
		for _, n := range ngn {
			if c.idToGroupList[n] == nil {
				c.idToGroupList[n] = make(map[uint64]struct{})
			}
			c.idToGroupList[n][ng.ID] = struct{}{}
			c.groupToIDList[ng.ID][n] = c.list[n]
		}
	} else {
		oldList := make(map[uint64]struct{})
		for nid := range c.groupToIDList[ng.ID] {
			oldList[nid] = struct{}{}
		}

		c.groupToIDList[ng.ID] = make(map[uint64]*model.Notification)
		for _, nid := range ngn {
			c.groupToIDList[ng.ID][nid] = c.list[nid]
			if c.idToGroupList[nid] == nil {
				c.idToGroupList[nid] = make(map[uint64]struct{})
			}
			c.idToGroupList[nid][ng.ID] = struct{}{}
		}

		for oldID := range oldList {
			if _, ok := c.groupToIDList[ng.ID][oldID]; !ok {
				delete(c.groupToIDList[oldID], ng.ID)
				if len(c.idToGroupList[oldID]) == 0 {
					delete(c.idToGroupList, oldID)
				}
			}
		}
	}
}

func (c *NotificationClass) Delete(idList []uint64) {
	c.listMu.Lock()

	for _, id := range idList {
		delete(c.list, id)
		// 如果绑定了通知组才删除
		if gids, ok := c.idToGroupList[id]; ok {
			for gid := range gids {
				delete(c.groupToIDList[gid], id)
				delete(c.idToGroupList, id)
			}
		}
	}

	c.listMu.Unlock()
	c.sortList()
}

func (c *NotificationClass) DeleteGroup(gids []uint64) {
	c.listMu.Lock()
	defer c.listMu.Unlock()
	c.groupMu.Lock()
	defer c.groupMu.Unlock()

	for _, gid := range gids {
		delete(c.groupList, gid)
		delete(c.groupToIDList, gid)
	}
}

func (c *NotificationClass) GetGroupName(gid uint64) string {
	c.groupMu.RLock()
	defer c.groupMu.RUnlock()

	return c.groupList[gid]
}

func (c *NotificationClass) sortList() {
	c.listMu.RLock()
	defer c.listMu.RUnlock()

	sortedList := utils.MapValuesToSlice(c.list)
	slices.SortFunc(sortedList, func(a, b *model.Notification) int {
		return cmp.Compare(a.ID, b.ID)
	})

	c.sortedListMu.Lock()
	defer c.sortedListMu.Unlock()
	c.sortedList = sortedList
}

func (c *NotificationClass) UnMuteNotification(notificationGroupID uint64, muteLabel string) {
	fullMuteLabel := NotificationMuteLabel.AppendNotificationGroupName(muteLabel, c.GetGroupName(notificationGroupID))
	Cache.Delete(fullMuteLabel)
}

// SendNotification 向指定的通知方式组的所有通知方式发送通知
func (c *NotificationClass) SendNotification(notificationGroupID uint64, desc string, muteLabel string, ext ...*model.Server) {
	if muteLabel != "" {
		// 将通知方式组名称加入静音标志
		muteLabel := NotificationMuteLabel.AppendNotificationGroupName(muteLabel, c.GetGroupName(notificationGroupID))
		// 通知防骚扰策略
		var flag bool
		if cacheN, has := Cache.Get(muteLabel); has {
			nHistory := cacheN.(NotificationHistory)
			// 每次提醒都增加一倍等待时间，最后每天最多提醒一次
			if time.Now().After(nHistory.Until) {
				flag = true
				nHistory.Duration *= 2
				if nHistory.Duration > time.Hour*24 {
					nHistory.Duration = time.Hour * 24
				}
				nHistory.Until = time.Now().Add(nHistory.Duration)
				// 缓存有效期加 10 分钟
				Cache.Set(muteLabel, nHistory, nHistory.Duration+time.Minute*10)
			}
		} else {
			// 新提醒直接通知
			flag = true
			Cache.Set(muteLabel, NotificationHistory{
				Duration: firstNotificationDelay,
				Until:    time.Now().Add(firstNotificationDelay),
			}, firstNotificationDelay+time.Minute*10)
		}

		if !flag {
			if Conf.Debug {
				log.Println("NEZHA>> Muted repeated notification", desc, muteLabel)
			}
			return
		}
	}
	// 向该通知方式组的所有通知方式发出通知
	c.listMu.RLock()
	defer c.listMu.RUnlock()
	for _, n := range c.groupToIDList[notificationGroupID] {
		log.Printf("NEZHA>> Try to notify %s", n.Name)
	}
	for _, n := range c.groupToIDList[notificationGroupID] {
		ns := model.NotificationServerBundle{
			Notification: n,
			Server:       nil,
			Loc:          Loc,
		}
		if len(ext) > 0 {
			ns.Server = ext[0]
		}
		if err := ns.Send(desc); err != nil {
			log.Printf("NEZHA>> Sending notification to %s failed: %v", n.Name, err)
		} else {
			log.Printf("NEZHA>> Sending notification to %s succeeded", n.Name)
		}
	}
}

func sendSMTPAdminNotification(message string) error {
	host, port, err := net.SplitHostPort(Conf.SMTPServer)
	if err != nil {
		return fmt.Errorf("SMTP服务器地址格式错误 (host:port)")
	}

	fromEmail := Conf.SMTPUser
	if !strings.Contains(Conf.SMTPUser, "@") {
		fromEmail = fmt.Sprintf("nezha@%s", host)
	}

	header := make(map[string]string)
	header["From"] = fmt.Sprintf("Nezha Monitoring <%s>", fromEmail)
	header["To"] = Conf.AdminEmail
	header["Subject"] = "Nezha Monitoring Alert"
	header["Date"] = time.Now().Format(time.RFC1123Z)
	header["Content-Type"] = "text/plain; charset=UTF-8"

	var msg strings.Builder
	for k, v := range header {
		msg.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
	}
	msg.WriteString("\r\n")
	msg.WriteString(message)

	tlsConfig := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         host,
	}

	auth := smtp.PlainAuth("", Conf.SMTPUser, Conf.SMTPPassword, host)

	if port == "465" {
		conn, err := tls.Dial("tcp", Conf.SMTPServer, tlsConfig)
		if err != nil {
			return fmt.Errorf("SMTP SSL Dial error: %w", err)
		}
		defer conn.Close()

		client, err := smtp.NewClient(conn, host)
		if err != nil {
			return fmt.Errorf("SMTP NewClient error: %w", err)
		}
		defer client.Quit()

		if err = client.Auth(auth); err != nil {
			return fmt.Errorf("SMTP Auth error: %w", err)
		}
		if err = client.Mail(fromEmail); err != nil {
			return fmt.Errorf("SMTP Mail error: %w", err)
		}
		if err = client.Rcpt(Conf.AdminEmail); err != nil {
			return fmt.Errorf("SMTP Rcpt error: %w", err)
		}
		w, err := client.Data()
		if err != nil {
			return fmt.Errorf("SMTP Data error: %w", err)
		}
		if _, err = w.Write([]byte(msg.String())); err != nil {
			return fmt.Errorf("SMTP Write error: %w", err)
		}
		if err = w.Close(); err != nil {
			return fmt.Errorf("SMTP Close error: %w", err)
		}
		return nil
	}

	conn, err := net.Dial("tcp", Conf.SMTPServer)
	if err != nil {
		return fmt.Errorf("SMTP Dial error: %w", err)
	}
	defer conn.Close()

	client, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("SMTP NewClient error: %w", err)
	}
	defer client.Quit()

	if ok, _ := client.Extension("STARTTLS"); ok {
		if err = client.StartTLS(tlsConfig); err != nil {
			return fmt.Errorf("SMTP StartTLS error: %w", err)
		}
	}

	if err = client.Auth(auth); err != nil {
		return fmt.Errorf("SMTP Auth error: %w", err)
	}
	if err = client.Mail(fromEmail); err != nil {
		return fmt.Errorf("SMTP Mail error: %w", err)
	}
	if err = client.Rcpt(Conf.AdminEmail); err != nil {
		return fmt.Errorf("SMTP Rcpt error: %w", err)
	}
	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("SMTP Data error: %w", err)
	}
	if _, err = w.Write([]byte(msg.String())); err != nil {
		return fmt.Errorf("SMTP Write error: %w", err)
	}
	if err = w.Close(); err != nil {
		return fmt.Errorf("SMTP Close error: %w", err)
	}
	return nil
}

type _NotificationMuteLabel struct{}

var NotificationMuteLabel _NotificationMuteLabel

func (_NotificationMuteLabel) IPChanged(serverId uint64) string {
	return fmt.Sprintf("bf::ic-%d", serverId)
}

func (_NotificationMuteLabel) ServerIncident(alertId uint64, serverId uint64) string {
	return fmt.Sprintf("bf::sei-%d-%d", alertId, serverId)
}

func (_NotificationMuteLabel) ServerIncidentResolved(alertId uint64, serverId uint64) string {
	return fmt.Sprintf("bf::seir-%d-%d", alertId, serverId)
}

func (_NotificationMuteLabel) AppendNotificationGroupName(label string, notificationGroupName string) string {
	return fmt.Sprintf("%s:%s", label, notificationGroupName)
}

func (_NotificationMuteLabel) ServiceLatencyMin(serviceId uint64) string {
	return fmt.Sprintf("bf::sln-%d", serviceId)
}

func (_NotificationMuteLabel) ServiceLatencyMax(serviceId uint64) string {
	return fmt.Sprintf("bf::slm-%d", serviceId)
}

func (_NotificationMuteLabel) ServiceStateChanged(serviceId uint64) string {
	return fmt.Sprintf("bf::ssc-%d", serviceId)
}

func (_NotificationMuteLabel) ServiceTLS(serviceId uint64, extraInfo string) string {
	return fmt.Sprintf("bf::stls-%d-%s", serviceId, extraInfo)
}
