package service

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"mime"
	"mime/multipart"
	"mime/quotedprintable"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"strings"
)

type EmailConfig struct {
	Host         string
	Port         int
	Username     string
	Password     string
	Encryption   string
	FromAddress  string
	FromName     string
	TemplateName string
}

type EmailMessage struct {
	To       []string
	Subject  string
	TextBody string
	HTMLBody string
}

func LoadEmailConfig() EmailConfig {
	fromAddress := strings.TrimSpace(MustGetString("email_from_address", ""))
	if fromAddress == "" {
		fromAddress = strings.TrimSpace(MustGetString("email_from", ""))
	}

	fromName := strings.TrimSpace(MustGetString("email_from_name", ""))
	if fromName == "" {
		fromName = strings.TrimSpace(MustGetString("app_name", "XBoard"))
	}

	return EmailConfig{
		Host:         strings.TrimSpace(MustGetString("email_host", "")),
		Port:         MustGetInt("email_port", 465),
		Username:     strings.TrimSpace(MustGetString("email_username", "")),
		Password:     MustGetString("email_password", ""),
		Encryption:   normalizeEmailEncryption(MustGetString("email_encryption", "")),
		FromAddress:  fromAddress,
		FromName:     fromName,
		TemplateName: firstNonEmpty(strings.TrimSpace(MustGetString("email_template", "")), "classic"),
	}
}

func (cfg EmailConfig) DebugConfig() map[string]any {
	return map[string]any{
		"driver":     "smtp",
		"host":       cfg.Host,
		"port":       cfg.Port,
		"encryption": cfg.Encryption,
		"username":   cfg.Username,
		"from": map[string]any{
			"address": cfg.SenderAddress(),
			"name":    cfg.FromName,
		},
		"from_address": cfg.SenderAddress(),
		"from_name":    cfg.FromName,
	}
}

func (cfg EmailConfig) SenderAddress() string {
	return firstNonEmpty(strings.TrimSpace(cfg.FromAddress), strings.TrimSpace(cfg.Username))
}

func (cfg EmailConfig) Validate() error {
	switch {
	case strings.TrimSpace(cfg.Host) == "":
		return errors.New("email_host is required")
	case cfg.Port <= 0:
		return errors.New("email_port is required")
	case cfg.SenderAddress() == "":
		return errors.New("email_from_address or email_username is required")
	}
	return nil
}

func SendMailWithCurrentSettings(message EmailMessage) error {
	return SendMail(LoadEmailConfig(), message)
}

func SendMail(cfg EmailConfig, message EmailMessage) error {
	if err := cfg.Validate(); err != nil {
		return err
	}
	if len(message.To) == 0 {
		return errors.New("at least one recipient is required")
	}
	if strings.TrimSpace(message.Subject) == "" {
		return errors.New("subject is required")
	}
	if strings.TrimSpace(message.TextBody) == "" && strings.TrimSpace(message.HTMLBody) == "" {
		return errors.New("message body is required")
	}

	payload, err := buildEmailMessage(cfg, message)
	if err != nil {
		return err
	}

	client, err := dialSMTP(cfg)
	if err != nil {
		return err
	}
	defer client.Close()

	if cfg.Username != "" && cfg.Password != "" {
		if ok, _ := client.Extension("AUTH"); !ok {
			return errors.New("smtp server does not support authentication")
		}
		if err := client.Auth(smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)); err != nil {
			return err
		}
	}

	if err := client.Mail(cfg.SenderAddress()); err != nil {
		return err
	}
	for _, recipient := range message.To {
		recipient = strings.TrimSpace(recipient)
		if recipient == "" {
			continue
		}
		if err := client.Rcpt(recipient); err != nil {
			return err
		}
	}

	writer, err := client.Data()
	if err != nil {
		return err
	}
	if _, err := writer.Write(payload); err != nil {
		_ = writer.Close()
		return err
	}
	if err := writer.Close(); err != nil {
		return err
	}

	return client.Quit()
}

func dialSMTP(cfg EmailConfig) (*smtp.Client, error) {
	addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)

	switch cfg.Encryption {
	case "ssl":
		conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: cfg.Host})
		if err != nil {
			return nil, err
		}
		return smtp.NewClient(conn, cfg.Host)
	case "tls":
		client, err := smtp.Dial(addr)
		if err != nil {
			return nil, err
		}
		if ok, _ := client.Extension("STARTTLS"); !ok {
			_ = client.Close()
			return nil, errors.New("smtp server does not support STARTTLS")
		}
		if err := client.StartTLS(&tls.Config{ServerName: cfg.Host}); err != nil {
			_ = client.Close()
			return nil, err
		}
		return client, nil
	default:
		return smtp.Dial(addr)
	}
}

func buildEmailMessage(cfg EmailConfig, message EmailMessage) ([]byte, error) {
	fromAddress := cfg.SenderAddress()
	if _, err := mail.ParseAddress(fromAddress); err != nil {
		return nil, fmt.Errorf("invalid sender address: %w", err)
	}

	toAddresses := make([]string, 0, len(message.To))
	for _, recipient := range message.To {
		recipient = strings.TrimSpace(recipient)
		if recipient == "" {
			continue
		}
		if _, err := mail.ParseAddress(recipient); err != nil {
			return nil, fmt.Errorf("invalid recipient address: %w", err)
		}
		toAddresses = append(toAddresses, recipient)
	}
	if len(toAddresses) == 0 {
		return nil, errors.New("no valid recipients")
	}

	fromHeader := (&mail.Address{Name: cfg.FromName, Address: fromAddress}).String()
	subjectHeader := mime.QEncoding.Encode("UTF-8", strings.TrimSpace(message.Subject))

	var buf bytes.Buffer
	writeHeader := func(key, value string) {
		buf.WriteString(key)
		buf.WriteString(": ")
		buf.WriteString(value)
		buf.WriteString("\r\n")
	}

	writeHeader("From", fromHeader)
	writeHeader("To", strings.Join(toAddresses, ", "))
	writeHeader("Subject", subjectHeader)
	writeHeader("MIME-Version", "1.0")

	textBody := message.TextBody
	htmlBody := message.HTMLBody

	if strings.TrimSpace(htmlBody) != "" {
		mw := multipart.NewWriter(&buf)
		writeHeader("Content-Type", fmt.Sprintf(`multipart/alternative; boundary="%s"`, mw.Boundary()))
		buf.WriteString("\r\n")

		if strings.TrimSpace(textBody) == "" {
			textBody = htmlBody
		}

		if err := writeMIMEPart(mw, "text/plain", textBody); err != nil {
			return nil, err
		}
		if err := writeMIMEPart(mw, "text/html", htmlBody); err != nil {
			return nil, err
		}
		if err := mw.Close(); err != nil {
			return nil, err
		}
		return buf.Bytes(), nil
	}

	writeHeader("Content-Type", `text/plain; charset="UTF-8"`)
	writeHeader("Content-Transfer-Encoding", "quoted-printable")
	buf.WriteString("\r\n")

	qp := quotedprintable.NewWriter(&buf)
	if _, err := qp.Write([]byte(textBody)); err != nil {
		return nil, err
	}
	if err := qp.Close(); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

func writeMIMEPart(mw *multipart.Writer, contentType string, body string) error {
	header := textproto.MIMEHeader{}
	header.Set("Content-Type", fmt.Sprintf(`%s; charset="UTF-8"`, contentType))
	header.Set("Content-Transfer-Encoding", "quoted-printable")

	part, err := mw.CreatePart(header)
	if err != nil {
		return err
	}

	qp := quotedprintable.NewWriter(part)
	if _, err := qp.Write([]byte(body)); err != nil {
		return err
	}
	return qp.Close()
}

func normalizeEmailEncryption(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "ssl":
		return "ssl"
	case "tls", "starttls":
		return "tls"
	default:
		return ""
	}
}
