package mailer

import (
	"context"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"net"
	"net/smtp"
	"strings"
	"time"
)

var ErrInvalidConfig = errors.New("invalid smtp config")

type Mailer struct {
	cfg Config
}

type Message struct {
	To          []string
	Subject     string
	Body        string
	BodyHTML    string
	Attachments []Attachment
}

type Attachment struct {
	FileName    string
	ContentType string
	Data        []byte
}

func New(cfg Config) *Mailer {
	return &Mailer{cfg: cfg}
}

func (m *Mailer) Send(ctx context.Context, msg Message) error {
	if len(msg.To) == 0 {
		return errors.New("recipient missing")
	}
	if strings.TrimSpace(msg.Subject) == "" {
		return errors.New("subject missing")
	}

	// timeout kontrolü (ctx)
	deadline, ok := ctx.Deadline()
	if !ok {
		// default timeout
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, 20*time.Second)
		defer cancel()
		deadline, _ = ctx.Deadline()
	}
	timeout := time.Until(deadline)
	if timeout <= 0 {
		return context.DeadlineExceeded
	}

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

	dialer := net.Dialer{Timeout: timeout}
	conn, err := dialer.DialContext(ctx, "tcp", addr)
	if err != nil {
		return fmt.Errorf("smtp dial: %w", err)
	}
	defer conn.Close()

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

	// STARTTLS
	if m.cfg.StartTLS {
		tlsCfg := &tls.Config{
			ServerName: m.cfg.Host,
			MinVersion: tls.VersionTLS12,
		}
		if err := c.StartTLS(tlsCfg); err != nil {
			return fmt.Errorf("starttls: %w", err)
		}
	}

	// AUTH
	auth := smtp.PlainAuth("", m.cfg.Username, m.cfg.Password, m.cfg.Host)
	if err := c.Auth(auth); err != nil {
		return fmt.Errorf("smtp auth: %w", err)
	}

	// MAIL FROM
	if err := c.Mail(m.cfg.From); err != nil {
		return fmt.Errorf("mail from: %w", err)
	}

	// RCPT TO
	for _, rcpt := range msg.To {
		rcpt = strings.TrimSpace(rcpt)
		if rcpt == "" {
			continue
		}
		if err := c.Rcpt(rcpt); err != nil {
			return fmt.Errorf("rcpt %s: %w", rcpt, err)
		}
	}

	w, err := c.Data()
	if err != nil {
		return fmt.Errorf("data: %w", err)
	}

	// basit mime
	contentType := "text/plain; charset=UTF-8"
	body := msg.Body
	if strings.TrimSpace(msg.BodyHTML) != "" {
		contentType = "text/html; charset=UTF-8"
		body = msg.BodyHTML
	}

	raw := buildMIME(m.cfg.From, msg.To, msg.Subject, contentType, body)

	_, writeErr := w.Write([]byte(raw))
	closeErr := w.Close()

	if writeErr != nil {
		return fmt.Errorf("write body: %w", writeErr)
	}
	if closeErr != nil {
		return fmt.Errorf("close body: %w", closeErr)
	}

	if err := c.Quit(); err != nil {
		return fmt.Errorf("quit: %w", err)
	}

	return nil
}

func buildMIME(from string, to []string, subject, contentType, body string) string {
	// Encode Subject to UTF-8
	encodedSubject := "=?UTF-8?B?" + base64.StdEncoding.EncodeToString([]byte(subject)) + "?="

	headers := []string{
		"From: " + from,
		"To: " + strings.Join(to, ", "),
		"Subject: " + encodedSubject,
		"MIME-Version: 1.0",
		"Content-Type: " + contentType,
		"",
	}
	return strings.Join(headers, "\r\n") + "\r\n" + body + "\r\n"
}

type MailerInterface interface {
	Send(ctx context.Context, msg Message) error
}
