package service

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"mime"
	"net"
	"net/http"
	"net/smtp"
	"net/url"
	"strings"
)

// 通知渠道类型;telegram 走既有独立路径,不纳入此枚举。
const (
	ChannelWebhook = "webhook"
	ChannelNtfy    = "ntfy"
	ChannelBark    = "bark"
	ChannelSMTP    = "smtp"
)

// NotifyChannelTypes 是新增渠道的稳定顺序(视图输出与遍历发送共用)。
var NotifyChannelTypes = []string{ChannelWebhook, ChannelNtfy, ChannelBark, ChannelSMTP}

// notifyMsg 是渲染完成的渠道无关载荷:Title 取通知类型显示名,Text 为模板渲染文本。
type notifyMsg struct {
	Title string
	Text  string
}

// defaultWebhookBody 是通用 Webhook 的缺省 body 模板(兼容 Slack incoming webhook)。
const defaultWebhookBody = `{"text": "{{title}}\n{{text}}"}`

// 渠道服务端缺省地址。
const (
	defaultNtfyServer = "https://ntfy.sh"
	defaultBarkServer = "https://api.day.app"
)

// WebhookChannel 是通用 Webhook 渠道配置;BodyTemplate 空串按缺省模板发送。
type WebhookChannel struct {
	Enabled      bool
	URL          string
	BodyTemplate string
}

// NtfyChannel 是 ntfy 渠道配置;Server 空串用官方 ntfy.sh,Token 可选。
type NtfyChannel struct {
	Enabled bool
	Server  string
	Topic   string
	Token   string
}

// BarkChannel 是 Bark(iOS)渠道配置;Server 空串用官方 api.day.app。
type BarkChannel struct {
	Enabled   bool
	Server    string
	DeviceKey string
}

// SMTPChannel 是邮件渠道配置;465 端口走隐式 TLS,其余端口 STARTTLS,Username 空则免认证。
type SMTPChannel struct {
	Enabled  bool
	Host     string
	Port     int
	Username string
	Password string
	From     string
	To       string
}

// validNotifyURL 校验渠道出站地址:仅接受 http/https 且 host 非空。
func validNotifyURL(raw string) error {
	u, err := url.Parse(raw)
	if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
		return fmt.Errorf("地址须为 http(s):// 开头的合法 URL")
	}
	return nil
}

// renderWebhookBody 渲染 body 模板:占位值先做 JSON 字符串转义(去掉包裹引号)再替换,
// 保证换行/引号嵌入 JSON 字符串后仍合法。
func renderWebhookBody(tpl string, msg notifyMsg) string {
	if strings.TrimSpace(tpl) == "" {
		tpl = defaultWebhookBody
	}
	r := strings.NewReplacer("{{title}}", jsonEscape(msg.Title), "{{text}}", jsonEscape(msg.Text))
	return r.Replace(tpl)
}

// jsonEscape 返回 s 作为 JSON 字符串字面量的内容(不含首尾引号)。
func jsonEscape(s string) string {
	b, _ := json.Marshal(s)
	return string(b[1 : len(b)-1])
}

// sendWebhook 向自定义 endpoint POST 渲染后的 JSON body。
func sendWebhook(ctx context.Context, client *http.Client, cfg WebhookChannel, msg notifyMsg) error {
	body := renderWebhookBody(cfg.BodyTemplate, msg)
	return postJSON(ctx, client, cfg.URL, "", []byte(body))
}

// sendNtfy 以 JSON publish 模式向 ntfy 服务端根路径发布(UTF-8 标题无兼容问题)。
func sendNtfy(ctx context.Context, client *http.Client, cfg NtfyChannel, msg notifyMsg) error {
	server := cfg.Server
	if server == "" {
		server = defaultNtfyServer
	}
	body, err := json.Marshal(map[string]string{"topic": cfg.Topic, "title": msg.Title, "message": msg.Text})
	if err != nil {
		return fmt.Errorf("ntfy send: %w", err)
	}
	return postJSON(ctx, client, strings.TrimRight(server, "/"), cfg.Token, body)
}

// sendBark 调用 Bark v2 push 接口。
func sendBark(ctx context.Context, client *http.Client, cfg BarkChannel, msg notifyMsg) error {
	server := cfg.Server
	if server == "" {
		server = defaultBarkServer
	}
	body, err := json.Marshal(map[string]string{"title": msg.Title, "body": msg.Text, "device_key": cfg.DeviceKey})
	if err != nil {
		return fmt.Errorf("bark send: %w", err)
	}
	return postJSON(ctx, client, strings.TrimRight(server, "/")+"/push", "", body)
}

// postJSON 发送 JSON POST;token 非空时附 Bearer 认证,非 2xx 一律视为失败。
func postJSON(ctx context.Context, client *http.Client, u, token string, body []byte) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(body))
	if err != nil {
		return sanitizeURLError(err)
	}
	req.Header.Set("Content-Type", "application/json")
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	resp, err := client.Do(req)
	if err != nil {
		return sanitizeURLError(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("http %d", resp.StatusCode)
	}
	return nil
}

// sendSMTP 组装邮件报文并投递;主题按 RFC2047 编码,正文 UTF-8 纯文本。
func sendSMTP(ctx context.Context, cfg SMTPChannel, msg notifyMsg) error {
	var b strings.Builder
	fmt.Fprintf(&b, "From: %s\r\n", cfg.From)
	fmt.Fprintf(&b, "To: %s\r\n", cfg.To)
	fmt.Fprintf(&b, "Subject: %s\r\n", mime.QEncoding.Encode("utf-8", msg.Title))
	b.WriteString("MIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n")
	b.WriteString(strings.ReplaceAll(msg.Text, "\n", "\r\n"))
	return smtpDeliver(ctx, cfg, []byte(b.String()))
}

// smtpDeliver 建立 SMTP 会话:465 端口隐式 TLS,其余端口连通后尝试 STARTTLS。
func smtpDeliver(ctx context.Context, cfg SMTPChannel, mail []byte) error {
	addr := net.JoinHostPort(cfg.Host, fmt.Sprint(cfg.Port))
	d := &net.Dialer{}
	conn, err := d.DialContext(ctx, "tcp", addr)
	if err != nil {
		return fmt.Errorf("smtp dial: %w", err)
	}
	if cfg.Port == 465 {
		conn = tls.Client(conn, &tls.Config{ServerName: cfg.Host})
	}
	c, err := smtp.NewClient(conn, cfg.Host)
	if err != nil {
		conn.Close()
		return fmt.Errorf("smtp handshake: %w", err)
	}
	defer c.Close()
	return smtpWrite(c, cfg, mail)
}

// smtpWrite 在既有会话上完成 STARTTLS/认证/收发件与报文写入。
func smtpWrite(c *smtp.Client, cfg SMTPChannel, mail []byte) error {
	if cfg.Port != 465 {
		if ok, _ := c.Extension("STARTTLS"); ok {
			if err := c.StartTLS(&tls.Config{ServerName: cfg.Host}); err != nil {
				return fmt.Errorf("smtp starttls: %w", err)
			}
		}
	}
	if cfg.Username != "" {
		auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
		if err := c.Auth(auth); err != nil {
			return fmt.Errorf("smtp auth: %w", err)
		}
	}
	if err := c.Mail(cfg.From); err != nil {
		return fmt.Errorf("smtp from: %w", err)
	}
	if err := c.Rcpt(cfg.To); err != nil {
		return fmt.Errorf("smtp rcpt: %w", err)
	}
	w, err := c.Data()
	if err != nil {
		return fmt.Errorf("smtp data: %w", err)
	}
	if _, err := w.Write(mail); err != nil {
		w.Close()
		return fmt.Errorf("smtp write: %w", err)
	}
	if err := w.Close(); err != nil {
		return fmt.Errorf("smtp close: %w", err)
	}
	return c.Quit()
}
