// Package mailer sends transactional emails via an outbound SMTP relay.
// It uses only the Go standard library (net/smtp + crypto/tls) to avoid
// adding external dependencies.
package mailer

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/smtp"
	"strings"
	"sync"
	"time"

	"archivmail/config"
)

// Mailer sends transactional emails via the configured SMTP-Out relay.
type Mailer struct {
	mu  sync.RWMutex
	cfg config.SMTPOutConfig
}

// New creates a Mailer from the smtp_out config section.
func New(cfg config.SMTPOutConfig) *Mailer {
	return &Mailer{cfg: cfg}
}

// Reload replaces the runtime configuration without restarting the process.
func (m *Mailer) Reload(cfg config.SMTPOutConfig) {
	m.mu.Lock()
	m.cfg = cfg
	m.mu.Unlock()
}

// IsConfigured returns true when the smtp_out config is usable (host + from set).
func (m *Mailer) IsConfigured() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.cfg.Host != "" && m.cfg.From != ""
}

// Send sends an HTML + plaintext email to a single recipient.
func (m *Mailer) Send(to, subject, htmlBody, textBody string) error {
	m.mu.RLock()
	cfg := m.cfg
	m.mu.RUnlock()

	if cfg.Host == "" || cfg.From == "" {
		return fmt.Errorf("mailer: smtp_out not configured")
	}

	addr := fmt.Sprintf("%s:%d", cfg.Host, port(cfg.Port))

	msg := buildMIME(cfg.From, to, subject, htmlBody, textBody)

	var auth smtp.Auth
	if cfg.User != "" {
		auth = smtp.PlainAuth("", cfg.User, cfg.Password, cfg.Host)
	}

	if cfg.TLS {
		return sendTLS(addr, cfg.Host, auth, cfg.From, to, msg)
	}
	return sendSTARTTLS(addr, auth, cfg.From, to, msg)
}

func port(p int) int {
	if p == 0 {
		return 587
	}
	return p
}

// sendTLS connects directly via TLS (port 465).
func sendTLS(addr, host string, auth smtp.Auth, from, to string, msg []byte) error {
	tlsCfg := &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12}
	conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 10 * time.Second}, "tcp", addr, tlsCfg)
	if err != nil {
		return fmt.Errorf("mailer: tls dial: %w", err)
	}
	defer conn.Close()

	c, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("mailer: smtp client: %w", err)
	}
	defer c.Close()

	if auth != nil {
		if err := c.Auth(auth); err != nil {
			return fmt.Errorf("mailer: auth: %w", err)
		}
	}
	return send(c, from, to, msg)
}

// sendSTARTTLS connects plain and upgrades via STARTTLS (port 587).
func sendSTARTTLS(addr string, auth smtp.Auth, from, to string, msg []byte) error {
	c, err := smtp.Dial(addr)
	if err != nil {
		return fmt.Errorf("mailer: dial: %w", err)
	}
	defer c.Close()

	host, _, _ := net.SplitHostPort(addr)
	if ok, _ := c.Extension("STARTTLS"); ok {
		tlsCfg := &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12}
		if err := c.StartTLS(tlsCfg); err != nil {
			return fmt.Errorf("mailer: starttls: %w", err)
		}
	}
	if auth != nil {
		if err := c.Auth(auth); err != nil {
			return fmt.Errorf("mailer: auth: %w", err)
		}
	}
	return send(c, from, to, msg)
}

func send(c *smtp.Client, from, to string, msg []byte) error {
	if err := c.Mail(from); err != nil {
		return fmt.Errorf("mailer: MAIL FROM: %w", err)
	}
	if err := c.Rcpt(to); err != nil {
		return fmt.Errorf("mailer: RCPT TO: %w", err)
	}
	wc, err := c.Data()
	if err != nil {
		return fmt.Errorf("mailer: DATA: %w", err)
	}
	defer wc.Close()
	if _, err := wc.Write(msg); err != nil {
		return fmt.Errorf("mailer: write: %w", err)
	}
	return nil
}

// buildMIME constructs a multipart/alternative MIME message.
func buildMIME(from, to, subject, htmlBody, textBody string) []byte {
	boundary := "----=archivmail_boundary_20260101"
	var b strings.Builder

	b.WriteString("From: " + from + "\r\n")
	b.WriteString("To: " + to + "\r\n")
	b.WriteString("Subject: " + subject + "\r\n")
	b.WriteString("MIME-Version: 1.0\r\n")
	b.WriteString(`Content-Type: multipart/alternative; boundary="` + boundary + `"` + "\r\n")
	b.WriteString("\r\n")

	// Plaintext part
	b.WriteString("--" + boundary + "\r\n")
	b.WriteString("Content-Type: text/plain; charset=UTF-8\r\n")
	b.WriteString("Content-Transfer-Encoding: quoted-printable\r\n")
	b.WriteString("\r\n")
	b.WriteString(textBody + "\r\n")

	// HTML part
	b.WriteString("--" + boundary + "\r\n")
	b.WriteString("Content-Type: text/html; charset=UTF-8\r\n")
	b.WriteString("Content-Transfer-Encoding: quoted-printable\r\n")
	b.WriteString("\r\n")
	b.WriteString(htmlBody + "\r\n")

	b.WriteString("--" + boundary + "--\r\n")
	return []byte(b.String())
}
