package mail

import (
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"mime"
	"net"
	"net/smtp"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"ymhut-box/server/unified-management/internal/config"
	"ymhut-box/server/unified-management/internal/db"
)

type Message struct {
	From           string
	FromName       string
	To             string
	Subject        string
	PlainBody      string
	HTMLBody       string
	AttachmentPath string
	AttachmentName string
}

func SafeConfig(cfg config.MailConfig) map[string]any {
	return map[string]any{
		"host":             cfg.Host,
		"port":             cfg.Port,
		"secure":           cfg.Secure,
		"username":         cfg.Username,
		"fromAddress":      cfg.FromAddress,
		"fromName":         cfg.FromName,
		"developerAddress": cfg.DeveloperAddress,
		"timeoutSeconds":   cfg.TimeoutSeconds,
		"hasPassword":      strings.TrimSpace(cfg.Password) != "",
		"configured":       IsConfigured(cfg),
	}
}

func IsConfigured(cfg config.MailConfig) bool {
	channel := normalize(cfg)
	return channel.Host != "" && channel.FromAddress != "" && channel.DeveloperAddress != ""
}

func BuildFeedbackMessage(cfg *config.Config, record db.Feedback) (Message, error) {
	channel, err := channel(cfg.Mail)
	if err != nil {
		return Message{}, err
	}
	attachment := record.PackagePath
	name := ""
	if attachment != "" {
		name = record.Code + ".zip"
	}
	subject := "[" + record.Code + "] YMhut Box 反馈：" + truncate(record.Title, 80)
	return Message{
		From:           channel.FromAddress,
		FromName:       channel.FromName,
		To:             channel.DeveloperAddress,
		Subject:        subject,
		PlainBody:      feedbackPlain(record),
		HTMLBody:       feedbackHTML(record),
		AttachmentPath: attachment,
		AttachmentName: name,
	}, nil
}

func BuildTestMessage(cfg *config.Config) (Message, error) {
	channel, err := channel(cfg.Mail)
	if err != nil {
		return Message{}, err
	}
	now := time.Now().UTC().Format(time.RFC3339)
	return Message{
		From:      channel.FromAddress,
		FromName:  channel.FromName,
		To:        channel.DeveloperAddress,
		Subject:   "YMhut Box 反馈通知测试",
		PlainBody: "这是一封来自 unified-management 的测试通知。\n时间：" + now,
		HTMLBody:  "<p>这是一封来自 unified-management 的测试通知。</p><p>时间：" + htmlEscape(now) + "</p>",
	}, nil
}

func Send(cfg *config.Config, message Message) error {
	channel, err := channel(cfg.Mail)
	if err != nil {
		return err
	}
	raw, err := BuildMIME(message)
	if err != nil {
		return err
	}
	return smtpSend(channel, message.From, message.To, raw)
}

func BuildMIME(message Message) (string, error) {
	boundary := "ymhut_" + randomish()
	altBoundary := "ymhut_alt_" + randomish()
	headers := []string{
		"Date: " + time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05") + " +0000",
		"From: " + mimeAddress(message.From, message.FromName),
		"To: " + message.To,
		"Subject: " + mime.BEncoding.Encode("UTF-8", message.Subject),
		"MIME-Version: 1.0",
		`Content-Type: multipart/mixed; boundary="` + boundary + `"`,
	}
	body := []string{
		"--" + boundary,
		`Content-Type: multipart/alternative; boundary="` + altBoundary + `"`,
		"",
		"--" + altBoundary,
		"Content-Type: text/plain; charset=UTF-8",
		"Content-Transfer-Encoding: base64",
		"",
		wrapBase64([]byte(message.PlainBody)),
		"--" + altBoundary,
		"Content-Type: text/html; charset=UTF-8",
		"Content-Transfer-Encoding: base64",
		"",
		wrapBase64([]byte(message.HTMLBody)),
		"--" + altBoundary + "--",
	}
	if message.AttachmentPath != "" {
		data, err := os.ReadFile(message.AttachmentPath)
		if err == nil {
			name := firstNonEmpty(message.AttachmentName, filepath.Base(message.AttachmentPath))
			escaped := strings.ReplaceAll(strings.ReplaceAll(name, `\`, `\\`), `"`, `\"`)
			body = append(body,
				"--"+boundary,
				`Content-Type: application/zip; name="`+escaped+`"`,
				"Content-Transfer-Encoding: base64",
				`Content-Disposition: attachment; filename="`+escaped+`"`,
				"",
				wrapBase64(data),
			)
		}
	}
	body = append(body, "--"+boundary+"--")
	return strings.Join(headers, "\r\n") + "\r\n\r\n" + strings.Join(body, "\r\n"), nil
}

func channel(cfg config.MailConfig) (config.MailConfig, error) {
	cfg = normalize(cfg)
	if cfg.Host == "" || cfg.FromAddress == "" || cfg.DeveloperAddress == "" {
		return cfg, errors.New("mail is not configured")
	}
	return cfg, nil
}

func normalize(cfg config.MailConfig) config.MailConfig {
	cfg.Secure = strings.ToLower(strings.TrimSpace(cfg.Secure))
	if cfg.Secure == "" {
		cfg.Secure = "ssl"
	}
	if cfg.Port <= 0 {
		cfg.Port = 465
	}
	if cfg.FromAddress == "" {
		cfg.FromAddress = cfg.Username
	}
	if cfg.FromName == "" {
		cfg.FromName = "YMhut Box Feedback"
	}
	if cfg.TimeoutSeconds <= 0 {
		cfg.TimeoutSeconds = 20
	}
	return cfg
}

func smtpSend(channel config.MailConfig, from, to, rawMessage string) error {
	address := net.JoinHostPort(channel.Host, fmt.Sprintf("%d", channel.Port))
	timeout := time.Duration(channel.TimeoutSeconds) * time.Second
	var client *smtp.Client
	if channel.Secure == "ssl" || channel.Secure == "tls" {
		conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", address, &tls.Config{ServerName: channel.Host})
		if err != nil {
			return fmt.Errorf("邮件服务器连接失败：%w", err)
		}
		var clientErr error
		client, clientErr = smtp.NewClient(conn, channel.Host)
		if clientErr != nil {
			_ = conn.Close()
			return clientErr
		}
	} else {
		conn, err := net.DialTimeout("tcp", address, timeout)
		if err != nil {
			return fmt.Errorf("邮件服务器连接失败：%w", err)
		}
		var clientErr error
		client, clientErr = smtp.NewClient(conn, channel.Host)
		if clientErr != nil {
			_ = conn.Close()
			return clientErr
		}
	}
	defer client.Close()
	if channel.Secure == "starttls" {
		if err := client.StartTLS(&tls.Config{ServerName: channel.Host}); err != nil {
			return fmt.Errorf("邮件加密握手失败：%w", err)
		}
	}
	if channel.Username != "" || channel.Password != "" {
		if err := client.Auth(smtp.PlainAuth("", channel.Username, channel.Password, channel.Host)); err != nil {
			return fmt.Errorf("邮件认证失败：%w", err)
		}
	}
	if err := client.Mail(extractEmail(from)); err != nil {
		return err
	}
	if err := client.Rcpt(extractEmail(to)); err != nil {
		return err
	}
	writer, err := client.Data()
	if err != nil {
		return err
	}
	if _, err := writer.Write([]byte(rawMessage)); err != nil {
		_ = writer.Close()
		return err
	}
	if err := writer.Close(); err != nil {
		return err
	}
	return client.Quit()
}

func feedbackPlain(record db.Feedback) string {
	return strings.Join([]string{
		"YMhut Box 反馈工单",
		"反馈编号：" + record.Code,
		"标题：" + record.Title,
		"类型：" + typeLabel(record.Type),
		"优先级：" + priorityLabel(record.Priority, record.Severity),
		"联系方式：" + record.Contact,
		"接收时间：" + record.CreatedAt,
		"包含文件：" + record.IncludedFiles,
		"反馈包 SHA256：" + record.PlainPackageSha256,
		"",
		"正文：",
		record.Body,
		"",
		"反馈包摘要：",
		record.SummaryText,
	}, "\n")
}

func feedbackHTML(record db.Feedback) string {
	rows := [][2]string{
		{"反馈编号", record.Code},
		{"标题", record.Title},
		{"类型", typeLabel(record.Type)},
		{"优先级", priorityLabel(record.Priority, record.Severity)},
		{"联系方式", record.Contact},
		{"接收时间", record.CreatedAt},
		{"包含文件", record.IncludedFiles},
		{"反馈包 SHA256", record.PlainPackageSha256},
	}
	html := `<h2>YMhut Box 反馈工单</h2><table cellpadding="8" cellspacing="0" border="1" style="border-collapse:collapse">`
	for _, row := range rows {
		html += `<tr><th align="left">` + htmlEscape(row[0]) + "</th><td>" + strings.ReplaceAll(htmlEscape(row[1]), "\n", "<br>") + "</td></tr>"
	}
	html += "</table>"
	html += `<h3>正文</h3><p style="white-space:pre-wrap">` + htmlEscape(record.Body) + "</p>"
	html += `<h3>反馈包摘要</h3><pre style="white-space:pre-wrap">` + htmlEscape(record.SummaryText) + "</pre>"
	return html
}

func typeLabel(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "suggestion":
		return "建议"
	case "ui":
		return "界面反馈"
	case "other":
		return "其他"
	default:
		return "问题"
	}
}

func priorityLabel(priority, severity string) string {
	value := firstNonEmpty(priority, severity)
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "urgent", "blocking":
		return "紧急"
	case "high", "major":
		return "高"
	case "low", "minor":
		return "低"
	default:
		return "普通"
	}
}

func htmlEscape(value string) string {
	value = strings.ReplaceAll(value, "&", "&amp;")
	value = strings.ReplaceAll(value, "<", "&lt;")
	value = strings.ReplaceAll(value, ">", "&gt;")
	value = strings.ReplaceAll(value, `"`, "&quot;")
	return strings.ReplaceAll(value, "'", "&#39;")
}

func mimeAddress(address, name string) string {
	if name == "" {
		return address
	}
	return mime.BEncoding.Encode("UTF-8", name) + " <" + extractEmail(address) + ">"
}

func extractEmail(value string) string {
	re := regexp.MustCompile(`<([^>]+)>`)
	if match := re.FindStringSubmatch(value); len(match) == 2 {
		return strings.TrimSpace(match[1])
	}
	return strings.TrimSpace(value)
}

func wrapBase64(data []byte) string {
	encoded := base64.StdEncoding.EncodeToString(data)
	var builder strings.Builder
	for len(encoded) > 76 {
		builder.WriteString(encoded[:76])
		builder.WriteString("\r\n")
		encoded = encoded[76:]
	}
	builder.WriteString(encoded)
	return builder.String()
}

func randomish() string {
	return strings.ReplaceAll(fmt.Sprintf("%d", time.Now().UnixNano()), "-", "")
}

func truncate(value string, max int) string {
	runes := []rune(strings.TrimSpace(value))
	if len(runes) <= max {
		return string(runes)
	}
	return string(runes[:max])
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}
