package model

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/goccy/go-json"
	"github.com/nezhahq/nezha/pkg/utils"
)

const (
	_ = iota
	NotificationRequestTypeJSON
	NotificationRequestTypeForm
)

const (
	_ = iota
	NotificationRequestMethodGET
	NotificationRequestMethodPOST
)

type NotificationServerBundle struct {
	Notification *Notification
	Server       *Server
	Loc          *time.Location
}

const (
	_ = iota
	NotificationTypeWebhook
	NotificationTypeEmail
	NotificationTypeTelegram
)

var (
	SendGlobalTelegramFunc func(message string) error
	SendGlobalEmailFunc    func(message string) error
)

type Notification struct {
	Common
	Name              string `json:"name"`
	Type              uint8  `json:"type"` // 1: Webhook, 2: SMTP, 3: Telegram
	URL               string `json:"url"`  // SMTP: host:port, Webhook: url, Telegram: bot_token
	RequestMethod     uint8  `json:"request_method"`
	RequestType       uint8  `json:"request_type"`
	RequestHeader     string `json:"request_header" gorm:"type:longtext"` // Webhook: header
	RequestBody       string `json:"request_body" gorm:"type:longtext"`   // Webhook: body
	VerifyTLS         *bool  `json:"verify_tls,omitempty"`
	FormatMetricUnits *bool  `json:"format_metric_units,omitempty"`
}

func (ns *NotificationServerBundle) reqURL(message string) string {
	n := ns.Notification
	return ns.replaceParamsInString(n.URL, message, func(msg string) string {
		return url.QueryEscape(msg)
	})
}

func (n *Notification) reqMethod() (string, error) {
	switch n.RequestMethod {
	case NotificationRequestMethodPOST:
		return http.MethodPost, nil
	case NotificationRequestMethodGET:
		return http.MethodGet, nil
	}
	return "", errors.New("不支持的请求方式")
}

func (ns *NotificationServerBundle) reqBody(message string) (string, error) {
	n := ns.Notification
	if n.RequestMethod == NotificationRequestMethodGET || message == "" {
		return "", nil
	}
	switch n.RequestType {
	case NotificationRequestTypeJSON:
		return ns.replaceParamsInString(n.RequestBody, message, func(msg string) string {
			msgBytes, _ := json.Marshal(msg)
			return string(msgBytes)[1 : len(msgBytes)-1]
		}), nil
	case NotificationRequestTypeForm:
		data, err := utils.GjsonIter(n.RequestBody)
		if err != nil {
			return "", err
		}
		params := url.Values{}
		for k, v := range data {
			params.Add(k, ns.replaceParamsInString(v, message, nil))
		}
		return params.Encode(), nil
	}
	return "", errors.New("不支持的请求类型")
}

func (n *Notification) setContentType(req *http.Request) {
	if n.RequestMethod == NotificationRequestMethodGET {
		return
	}
	if n.RequestType == NotificationRequestTypeForm {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	} else {
		req.Header.Set("Content-Type", "application/json")
	}
}

func (n *Notification) setRequestHeader(req *http.Request) error {
	if n.RequestHeader == "" {
		return nil
	}
	m, err := utils.GjsonIter(n.RequestHeader)
	if err != nil {
		return err
	}
	for k, v := range m {
		req.Header.Set(k, v)
	}
	return nil
}

func (ns *NotificationServerBundle) Send(message string) error {
	n := ns.Notification

	if n.Type == NotificationTypeEmail || n.Type == NotificationTypeTelegram {
		template := n.RequestBody
		if template == "" {
			template = message
		}
		content := ns.replaceParamsInString(template, message, nil)

		if n.Type == NotificationTypeEmail && SendGlobalEmailFunc != nil {
			return SendGlobalEmailFunc(content)
		}
		if n.Type == NotificationTypeTelegram && SendGlobalTelegramFunc != nil {
			return SendGlobalTelegramFunc(content)
		}
		return nil
	}

	var client *http.Client
	if n.VerifyTLS != nil && *n.VerifyTLS {
		client = utils.HttpClient
	} else {
		client = utils.HttpClientSkipTlsVerify
	}

	reqBody, err := ns.reqBody(message)
	if err != nil {
		return err
	}

	reqMethod, err := n.reqMethod()
	if err != nil {
		return err
	}

	req, err := http.NewRequest(reqMethod, ns.reqURL(message), strings.NewReader(reqBody))
	if err != nil {
		return err
	}

	n.setContentType(req)

	if err := n.setRequestHeader(req); err != nil {
		return err
	}

	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("%d@%s %s", resp.StatusCode, resp.Status, string(body))
	} else {
		_, _ = io.Copy(io.Discard, resp.Body)
	}

	return nil
}





// replaceParamInString 替换字符串中的占位符
func (ns *NotificationServerBundle) replaceParamsInString(str string, message string, mod func(string) string) string {
	if mod == nil {
		mod = func(s string) string { return s }
	}

	replacements := []string{
		"#NEZHA#", mod(message),
		"#DATETIME#", mod(time.Now().In(ns.Loc).String()),
	}

	if ns.Server != nil {
		var noteData struct {
			BillingDataMod struct {
				EndDate string `json:"endDate"`
				Amount  string `json:"amount"`
				Cycle   string `json:"cycle"`
			} `json:"billingDataMod"`
		}
		expiresStr := ""
		amountStr := ""
		cycleStr := ""

		if ns.Server.Note != "" && json.Unmarshal([]byte(ns.Server.Note), &noteData) == nil && noteData.BillingDataMod.EndDate != "" {
			expiresStr = noteData.BillingDataMod.EndDate
			amountStr = noteData.BillingDataMod.Amount
			cycleStr = noteData.BillingDataMod.Cycle
		} else if ns.Server.PublicNote != "" && json.Unmarshal([]byte(ns.Server.PublicNote), &noteData) == nil && noteData.BillingDataMod.EndDate != "" {
			expiresStr = noteData.BillingDataMod.EndDate
			amountStr = noteData.BillingDataMod.Amount
			cycleStr = noteData.BillingDataMod.Cycle
		}

		replacements = append(replacements,
			"#SERVER.NAME#", mod(ns.Server.Name),
			"#SERVER.ID#", mod(fmt.Sprintf("%d", ns.Server.ID)),
			"#SERVER.NOTE#", mod(ns.Server.Note),
			"#SERVER.PUBLIC_NOTE#", mod(ns.Server.PublicNote),
			"#SERVER.EXPIRE_DATE#", mod(expiresStr),
			"#SERVER.BILLING_AMOUNT#", mod(amountStr),
			"#SERVER.BILLING_CYCLE#", mod(cycleStr),
		)

		if ns.Server.State != nil && ns.Server.Host != nil {
			replacements = append(replacements,
				// Converted metrics
				"#SERVER.CPU#", mod(ns.formatUsage(false, ns.Server.State.CPU)),
				"#SERVER.MEM#", mod(ns.formatUsage(true, float64(ns.Server.State.MemUsed)/float64(ns.Server.Host.MemTotal))),
				"#SERVER.SWAP#", mod(ns.formatUsage(true, float64(ns.Server.State.SwapUsed)/float64(ns.Server.Host.SwapTotal))),
				"#SERVER.DISK#", mod(ns.formatUsage(true, float64(ns.Server.State.DiskUsed)/float64(ns.Server.Host.DiskTotal))),
				"#SERVER.SPEEDIN#", mod(fmt.Sprintf("%s/s", ns.formatSize(ns.Server.State.NetInSpeed))),
				"#SERVER.SPEEDOUT#", mod(fmt.Sprintf("%s/s", ns.formatSize(ns.Server.State.NetOutSpeed))),
				"#SERVER.TRANSFERIN#", mod(ns.formatSize(ns.Server.State.NetInTransfer)),
				"#SERVER.TRANSFEROUT#", mod(ns.formatSize(ns.Server.State.NetOutTransfer)),

				// Raw metrics
				"#SERVER.CPUUSED#", mod(fmt.Sprintf("%f", ns.Server.State.CPU)),
				"#SERVER.MEMUSED#", mod(fmt.Sprintf("%d", ns.Server.State.MemUsed)),
				"#SERVER.SWAPUSED#", mod(fmt.Sprintf("%d", ns.Server.State.SwapUsed)),
				"#SERVER.DISKUSED#", mod(fmt.Sprintf("%d", ns.Server.State.DiskUsed)),
				"#SERVER.NETINSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetInSpeed)),
				"#SERVER.NETOUTSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutSpeed)),
				"#SERVER.TRANSFERINRAW#", mod(fmt.Sprintf("%d", ns.Server.State.NetInTransfer)),
				"#SERVER.TRANSFEROUTRAW#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutTransfer)),
				"#SERVER.UPTIME#", mod(fmt.Sprintf("%d", ns.Server.State.Uptime)),
				"#SERVER.MEMTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.MemTotal)),
				"#SERVER.SWAPTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.SwapTotal)),
				"#SERVER.DISKTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.DiskTotal)),
				"#SERVER.LOAD1#", mod(fmt.Sprintf("%f", ns.Server.State.Load1)),
				"#SERVER.LOAD5#", mod(fmt.Sprintf("%f", ns.Server.State.Load5)),
				"#SERVER.LOAD15#", mod(fmt.Sprintf("%f", ns.Server.State.Load15)),
				"#SERVER.TCPCONNCOUNT#", mod(fmt.Sprintf("%d", ns.Server.State.TcpConnCount)),
				"#SERVER.UDPCONNCOUNT#", mod(fmt.Sprintf("%d", ns.Server.State.UdpConnCount)),
			)
		} else {
			replacements = append(replacements,
				"#SERVER.CPU#", mod("N/A"),
				"#SERVER.MEM#", mod("N/A"),
				"#SERVER.SWAP#", mod("N/A"),
				"#SERVER.DISK#", mod("N/A"),
				"#SERVER.SPEEDIN#", mod("N/A"),
				"#SERVER.SPEEDOUT#", mod("N/A"),
				"#SERVER.TRANSFERIN#", mod("N/A"),
				"#SERVER.TRANSFEROUT#", mod("N/A"),
				"#SERVER.CPUUSED#", mod("0"),
				"#SERVER.MEMUSED#", mod("0"),
				"#SERVER.SWAPUSED#", mod("0"),
				"#SERVER.DISKUSED#", mod("0"),
				"#SERVER.NETINSPEED#", mod("0"),
				"#SERVER.NETOUTSPEED#", mod("0"),
				"#SERVER.TRANSFERINRAW#", mod("0"),
				"#SERVER.TRANSFEROUTRAW#", mod("0"),
				"#SERVER.UPTIME#", mod("0"),
				"#SERVER.MEMTOTAL#", mod("0"),
				"#SERVER.SWAPTOTAL#", mod("0"),
				"#SERVER.DISKTOTAL#", mod("0"),
				"#SERVER.LOAD1#", mod("0"),
				"#SERVER.LOAD5#", mod("0"),
				"#SERVER.LOAD15#", mod("0"),
				"#SERVER.TCPCONNCOUNT#", mod("0"),
				"#SERVER.UDPCONNCOUNT#", mod("0"),
			)
		}

		var ipv4, ipv6, validIP string
		if ns.Server.GeoIP != nil {
			ip := ns.Server.GeoIP.IP
			if ip.IPv4Addr != "" && ip.IPv6Addr != "" {
				ipv4 = ip.IPv4Addr
				ipv6 = ip.IPv6Addr
				validIP = ipv4
			} else if ip.IPv4Addr != "" {
				ipv4 = ip.IPv4Addr
				validIP = ipv4
			} else {
				ipv6 = ip.IPv6Addr
				validIP = ipv6
			}
		}

		replacements = append(replacements,
			"#SERVER.IP#", mod(validIP),
			"#SERVER.IPV4#", mod(ipv4),
			"#SERVER.IPV6#", mod(ipv6),
		)
	}

	replacer := strings.NewReplacer(replacements...)
	return replacer.Replace(str)
}

func (ns *NotificationServerBundle) formatUsage(toPercentage bool, usage float64) string {
	if ns.Notification.FormatMetricUnits != nil && *ns.Notification.FormatMetricUnits {
		if toPercentage {
			usage = usage * 100
		}

		return fmt.Sprintf("%.2f %%", usage)
	}
	return fmt.Sprintf("%f", usage)
}

func (ns *NotificationServerBundle) formatSize(size uint64) string {
	if ns.Notification.FormatMetricUnits != nil && *ns.Notification.FormatMetricUnits {
		return utils.Bytes(size)
	}
	return fmt.Sprintf("%d", size)
}
