package infra

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"mime"
	"mime/multipart"
	"net/smtp"
	"net/textproto"
	"strings"
	"time"
)

// SMTPSend serializes msg to MIME and sends it using the given smtp.Client.
// Supports plain text, HTML, and mixed (with attachments) content types.
// The client must be authenticated (obtained from SMTPConnect).
func SMTPSend(client *smtp.Client, msg EmailMessage) error {
	if err := client.Mail(extractAddr(msg.From)); err != nil {
		return fmt.Errorf("smtp_send: MAIL FROM: %w", err)
	}

	allTo := append(append([]string{}, msg.To...), msg.CC...)
	allTo = append(allTo, msg.BCC...)
	for _, addr := range allTo {
		if err := client.Rcpt(extractAddr(addr)); err != nil {
			return fmt.Errorf("smtp_send: RCPT TO %s: %w", addr, err)
		}
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("smtp_send: DATA: %w", err)
	}
	defer w.Close()

	body, err := buildMIME(msg)
	if err != nil {
		return fmt.Errorf("smtp_send: build mime: %w", err)
	}

	if _, err := w.Write(body); err != nil {
		return fmt.Errorf("smtp_send: write: %w", err)
	}
	return nil
}

// extractAddr extracts the bare email address from a possibly formatted string
// like "Alice <alice@example.com>" → "alice@example.com".
func extractAddr(addr string) string {
	if i := strings.Index(addr, "<"); i >= 0 {
		if j := strings.Index(addr[i:], ">"); j >= 0 {
			return addr[i+1 : i+j]
		}
	}
	return strings.TrimSpace(addr)
}

// buildMIME constructs the full MIME email bytes.
func buildMIME(msg EmailMessage) ([]byte, error) {
	var buf bytes.Buffer

	// Headers
	buf.WriteString("Date: " + time.Now().UTC().Format(time.RFC1123Z) + "\r\n")
	buf.WriteString("From: " + msg.From + "\r\n")
	buf.WriteString("To: " + strings.Join(msg.To, ", ") + "\r\n")
	if len(msg.CC) > 0 {
		buf.WriteString("Cc: " + strings.Join(msg.CC, ", ") + "\r\n")
	}
	buf.WriteString("Subject: " + mime.QEncoding.Encode("utf-8", msg.Subject) + "\r\n")
	buf.WriteString("MIME-Version: 1.0\r\n")
	for k, v := range msg.Headers {
		buf.WriteString(k + ": " + v + "\r\n")
	}

	hasText := msg.BodyText != ""
	hasHTML := msg.BodyHTML != ""
	hasAtts := len(msg.Attachments) > 0

	switch {
	case hasAtts:
		// multipart/mixed: text or html parts + attachments
		mw := multipart.NewWriter(&buf)
		buf.WriteString("Content-Type: multipart/mixed; boundary=\"" + mw.Boundary() + "\"\r\n\r\n")

		if hasText && hasHTML {
			// nested multipart/alternative inside mixed
			var altBuf bytes.Buffer
			aw := multipart.NewWriter(&altBuf)
			writeTextPart(aw, msg.BodyText)
			writeHTMLPart(aw, msg.BodyHTML)
			aw.Close()
			altHeader := textproto.MIMEHeader{}
			altHeader.Set("Content-Type", "multipart/alternative; boundary=\""+aw.Boundary()+"\"")
			pw, _ := mw.CreatePart(altHeader)
			pw.Write(altBuf.Bytes())
		} else if hasHTML {
			writeHTMLPart(mw, msg.BodyHTML)
		} else {
			writeTextPart(mw, msg.BodyText)
		}

		for _, att := range msg.Attachments {
			writeAttachment(mw, att)
		}
		mw.Close()

	case hasText && hasHTML:
		mw := multipart.NewWriter(&buf)
		buf.WriteString("Content-Type: multipart/alternative; boundary=\"" + mw.Boundary() + "\"\r\n\r\n")
		writeTextPart(mw, msg.BodyText)
		writeHTMLPart(mw, msg.BodyHTML)
		mw.Close()

	case hasHTML:
		buf.WriteString("Content-Type: text/html; charset=utf-8\r\n")
		buf.WriteString("Content-Transfer-Encoding: quoted-printable\r\n\r\n")
		buf.WriteString(msg.BodyHTML)

	default:
		buf.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
		buf.WriteString("Content-Transfer-Encoding: quoted-printable\r\n\r\n")
		buf.WriteString(msg.BodyText)
	}

	return buf.Bytes(), nil
}

func writeTextPart(mw *multipart.Writer, body string) {
	h := textproto.MIMEHeader{}
	h.Set("Content-Type", "text/plain; charset=utf-8")
	h.Set("Content-Transfer-Encoding", "quoted-printable")
	pw, _ := mw.CreatePart(h)
	pw.Write([]byte(body))
}

func writeHTMLPart(mw *multipart.Writer, body string) {
	h := textproto.MIMEHeader{}
	h.Set("Content-Type", "text/html; charset=utf-8")
	h.Set("Content-Transfer-Encoding", "quoted-printable")
	pw, _ := mw.CreatePart(h)
	pw.Write([]byte(body))
}

func writeAttachment(mw *multipart.Writer, att EmailAttachment) {
	h := textproto.MIMEHeader{}
	h.Set("Content-Type", att.ContentType)
	h.Set("Content-Transfer-Encoding", "base64")
	h.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, att.Filename))
	pw, _ := mw.CreatePart(h)
	enc := base64.NewEncoder(base64.StdEncoding, pw)
	enc.Write(att.Data)
	enc.Close()
}
