package email

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"kra/internal/biz/system"
	"mime"
	"net"
	"net/smtp"
	"strings"
	"time"

	"kra/internal/conf"
)

type emailRepo struct {
	runtime *conf.Runtime
}

func NewEmailRepo(runtime *conf.Runtime) system.EmailRepo {
	return &emailRepo{runtime: runtime}
}

func (r *emailRepo) email() *conf.AdminBackend_Email {
	if r.runtime == nil {
		return nil
	}
	config := r.runtime.Admin()
	if config == nil {
		return nil
	}
	return config.Email
}

func (r *emailRepo) Enabled() bool {
	return emailEnabled(r.email())
}

func (r *emailRepo) DefaultRecipients() []string {
	config := r.email()
	if config == nil || strings.TrimSpace(config.To) == "" {
		return nil
	}
	return []string{strings.TrimSpace(config.To)}
}

func emailEnabled(config *conf.AdminBackend_Email) bool {
	return config != nil && strings.TrimSpace(config.Host) != "" &&
		strings.TrimSpace(config.From) != "" && strings.TrimSpace(config.Secret) != "" && config.Port > 0
}

func normalizeRecipients(values []string) []string {
	result := make([]string, 0, len(values))
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			result = append(result, value)
		}
	}
	return result
}

func cleanHeader(value string) string {
	return strings.NewReplacer("\r", "", "\n", "").Replace(value)
}

func (r *emailRepo) Send(ctx context.Context, to []string, subject, body string) error {
	config := r.email()
	if !emailEnabled(config) {
		return errors.New("邮件服务未配置")
	}
	to = normalizeRecipients(to)
	if len(to) == 0 {
		return errors.New("收件人不能为空")
	}
	if ctx == nil {
		ctx = context.Background()
	}
	address := net.JoinHostPort(config.Host, fmt.Sprint(config.Port))
	dialer := &net.Dialer{Timeout: 10 * time.Second}
	var conn net.Conn
	var err error
	if config.IsSsl {
		conn, err = (&tls.Dialer{NetDialer: dialer, Config: &tls.Config{ServerName: config.Host, MinVersion: tls.VersionTLS12}}).DialContext(ctx, "tcp", address)
	} else {
		conn, err = dialer.DialContext(ctx, "tcp", address)
	}
	if err != nil {
		return err
	}
	defer conn.Close()
	_ = conn.SetDeadline(time.Now().Add(15 * time.Second))
	client, err := smtp.NewClient(conn, config.Host)
	if err != nil {
		return err
	}
	defer client.Close()
	var auth smtp.Auth
	if config.IsLoginAuth {
		auth = &loginAuth{username: config.From, password: config.Secret}
	} else {
		auth = smtp.PlainAuth("", config.From, config.Secret, config.Host)
	}
	if ok, _ := client.Extension("AUTH"); ok {
		if err = client.Auth(auth); err != nil {
			return err
		}
	}
	if err = client.Mail(config.From); err != nil {
		return err
	}
	for _, recipient := range to {
		if err = client.Rcpt(recipient); err != nil {
			return err
		}
	}
	writer, err := client.Data()
	if err != nil {
		return err
	}
	fromHeader := cleanHeader(config.From)
	if config.Nickname != "" {
		fromHeader = fmt.Sprintf("%s <%s>", mime.QEncoding.Encode("UTF-8", cleanHeader(config.Nickname)), fromHeader)
	}
	toHeader := make([]string, 0, len(to))
	for _, recipient := range to {
		toHeader = append(toHeader, cleanHeader(recipient))
	}
	message := "From: " + fromHeader + "\r\n" +
		"To: " + strings.Join(toHeader, ",") + "\r\n" +
		"Subject: " + mime.QEncoding.Encode("UTF-8", cleanHeader(subject)) + "\r\n" +
		"MIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n" + body
	if _, err = io.WriteString(writer, message); err != nil {
		_ = writer.Close()
		return err
	}
	if err = writer.Close(); err != nil {
		return err
	}
	return client.Quit()
}

type loginAuth struct{ username, password string }

func (a *loginAuth) Start(*smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", nil, nil
}

func (a *loginAuth) Next(challenge []byte, more bool) ([]byte, error) {
	if !more {
		return nil, nil
	}
	prompt := strings.ToLower(strings.TrimSpace(string(challenge)))
	if strings.Contains(prompt, "username") || strings.Contains(prompt, "user") {
		return []byte(a.username), nil
	}
	if strings.Contains(prompt, "password") || strings.Contains(prompt, "pass") {
		return []byte(a.password), nil
	}
	return nil, fmt.Errorf("unsupported SMTP LOGIN challenge %q", prompt)
}
