package auth

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

// SMTPConfig holds SMTP server configuration
type SMTPConfig struct {
	Host     string
	Port     string
	Username string
	Password string
	From     string
}

// SMTPSender sends emails via SMTP
type SMTPSender struct {
	config SMTPConfig
}

// NewSMTPSender creates a new SMTP sender with the given configuration
func NewSMTPSender(config SMTPConfig) *SMTPSender {
	return &SMTPSender{config: config}
}

// heloHostname returns the hostname to use in HELO/EHLO.
// Using the SMTP server host is generally safe; alternatively we could
// use the sender's domain extracted from the From address.
func heloHostname(from, smtpHost string) string {
	// Try to extract domain from the From address
	if at := strings.LastIndex(from, "@"); at >= 0 {
		domain := from[at+1:]
		// Only use it if it looks plausible (contains a dot)
		if strings.Contains(domain, ".") {
			return domain
		}
	}
	// Fallback to the SMTP host's domain (strip port if present)
	if host, _, err := net.SplitHostPort(smtpHost); err == nil {
		return host
	}
	return smtpHost
}

// sendMail is a custom replacement for smtp.SendMail that allows
// setting the HELO/EHLO hostname explicitly. Some SMTP servers
// reject messages when the HELO doesn't match a valid domain.
func (s *SMTPSender) sendMail(to []string, msg []byte) error {
	addr := fmt.Sprintf("%s:%s", s.config.Host, s.config.Port)
	helo := heloHostname(s.config.From, s.config.Host)

	// Connect to the SMTP server
	conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
	if err != nil {
		return fmt.Errorf("failed to connect to SMTP server: %w", err)
	}
	defer conn.Close()

	// Create SMTP client
	client, err := smtp.NewClient(conn, s.config.Host)
	if err != nil {
		return fmt.Errorf("failed to create SMTP client: %w", err)
	}
	defer client.Close()

	// Send HELO/EHLO with the proper hostname
	if err := client.Hello(helo); err != nil {
		return fmt.Errorf("failed to send HELO: %w", err)
	}

	// Upgrade to TLS if supported (required for port 587 STARTTLS)
	// StartTLS internally re-sends EHLO per RFC 3207 after upgrading
	if ok, _ := client.Extension("STARTTLS"); ok {
		tlsConfig := &tls.Config{
			ServerName: s.config.Host,
		}
		if err := client.StartTLS(tlsConfig); err != nil {
			return fmt.Errorf("STARTTLS upgrade failed: %w", err)
		}
	}

	// Authenticate
	auth := smtp.PlainAuth("", s.config.Username, s.config.Password, s.config.Host)
	if err := client.Auth(auth); err != nil {
		return fmt.Errorf("SMTP authentication failed: %w", err)
	}

	// Set sender
	if err := client.Mail(s.config.From); err != nil {
		return fmt.Errorf("failed to set sender: %w", err)
	}

	// Set recipients
	for _, recipient := range to {
		if err := client.Rcpt(recipient); err != nil {
			return fmt.Errorf("failed to set recipient %s: %w", recipient, err)
		}
	}

	// Send message body
	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("failed to start data transfer: %w", err)
	}
	if _, err := w.Write(msg); err != nil {
		return fmt.Errorf("failed to write message body: %w", err)
	}
	if err := w.Close(); err != nil {
		return fmt.Errorf("failed to close message body: %w", err)
	}

	return client.Quit()
}

// SendOTP sends an OTP email to the recipient
func (s *SMTPSender) SendOTP(to, otp string) error {
	subject := "Your inBOXER Login Code"
	body := fmt.Sprintf(`Your one-time password for inBOXER is: %s

This code will expire in 10 minutes.

If you didn't request this code, please ignore this email.`, otp)

	msg := []byte(fmt.Sprintf("From: %s\r\n"+
		"To: %s\r\n"+
		"Subject: %s\r\n"+
		"\r\n"+
		"%s\r\n", s.config.From, to, subject, body))

	return s.sendMail([]string{to}, msg)
}

// SendWelcome sends a welcome email after successful registration
func (s *SMTPSender) SendWelcome(to string) error {
	subject := "Welcome to inBOXER"
	body := `Welcome to inBOXER!

Your email account has been successfully set up. You can now log in to the dashboard to configure your email settings and start organizing your inbox.

Thank you for using inBOXER!`

	msg := []byte(fmt.Sprintf("From: %s\r\n"+
		"To: %s\r\n"+
		"Subject: %s\r\n"+
		"\r\n"+
		"%s\r\n", s.config.From, to, subject, body))

	return s.sendMail([]string{to}, msg)
}

// ValidateConfig checks if SMTP configuration is valid
func (s *SMTPConfig) ValidateConfig() error {
	var errors []string
	if s.Host == "" {
		errors = append(errors, "SMTP host is required")
	}
	if s.Port == "" {
		errors = append(errors, "SMTP port is required")
	}
	if s.Username == "" {
		errors = append(errors, "SMTP username is required")
	}
	if s.Password == "" {
		errors = append(errors, "SMTP password is required")
	}
	if s.From == "" {
		errors = append(errors, "From address is required")
	}

	if len(errors) > 0 {
		return fmt.Errorf("invalid SMTP configuration: %s", strings.Join(errors, ", "))
	}
	return nil
}