package notifications

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/smtp"
	"strings"
	"time"

	"switchboard-core/store"
)

// SMTPConfig holds SMTP connection settings, loaded from platform_settings.
type SMTPConfig struct {
	Host     string `json:"smtp_host"`
	Port     int    `json:"smtp_port"`
	User     string `json:"smtp_user"`
	Password string `json:"smtp_password"` // decrypted at load time
	From     string `json:"smtp_from"`
	TLS      bool   `json:"smtp_tls"`
}

// EmailTransport sends notification emails via SMTP.
type EmailTransport struct {
	config SMTPConfig
}

// NewEmailTransport creates an email transport. Returns nil if config is invalid.
func NewEmailTransport(cfg SMTPConfig) *EmailTransport {
	if cfg.Host == "" || cfg.Port == 0 || cfg.From == "" {
		return nil
	}
	return &EmailTransport{config: cfg}
}

// Send delivers an email with both HTML and plaintext bodies.
// Uses multipart/alternative MIME for email client compatibility.
func (t *EmailTransport) Send(ctx context.Context, to, subject, htmlBody, textBody string) error {
	if to == "" {
		return fmt.Errorf("recipient email is empty")
	}

	addr := fmt.Sprintf("%s:%d", t.config.Host, t.config.Port)
	boundary := fmt.Sprintf("==boundary_%d==", time.Now().UnixNano())

	// Build MIME message
	var msg strings.Builder
	msg.WriteString(fmt.Sprintf("From: %s\r\n", t.config.From))
	msg.WriteString(fmt.Sprintf("To: %s\r\n", to))
	msg.WriteString(fmt.Sprintf("Subject: %s\r\n", subject))
	msg.WriteString("MIME-Version: 1.0\r\n")
	msg.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=\"%s\"\r\n", boundary))
	msg.WriteString("\r\n")

	// Plaintext part
	msg.WriteString(fmt.Sprintf("--%s\r\n", boundary))
	msg.WriteString("Content-Type: text/plain; charset=UTF-8\r\n\r\n")
	msg.WriteString(textBody)
	msg.WriteString("\r\n")

	// HTML part
	msg.WriteString(fmt.Sprintf("--%s\r\n", boundary))
	msg.WriteString("Content-Type: text/html; charset=UTF-8\r\n\r\n")
	msg.WriteString(htmlBody)
	msg.WriteString("\r\n")

	msg.WriteString(fmt.Sprintf("--%s--\r\n", boundary))

	// Connect with timeout
	dialer := net.Dialer{Timeout: 10 * time.Second}
	conn, err := dialer.DialContext(ctx, "tcp", addr)
	if err != nil {
		return fmt.Errorf("smtp dial: %w", err)
	}

	var client *smtp.Client
	if t.config.TLS {
		// Implicit TLS (port 465)
		tlsConn := tls.Client(conn, &tls.Config{ServerName: t.config.Host})
		client, err = smtp.NewClient(tlsConn, t.config.Host)
	} else {
		client, err = smtp.NewClient(conn, t.config.Host)
	}
	if err != nil {
		conn.Close()
		return fmt.Errorf("smtp client: %w", err)
	}
	defer client.Close()

	// STARTTLS for non-implicit TLS on port 587
	if !t.config.TLS {
		if ok, _ := client.Extension("STARTTLS"); ok {
			if err := client.StartTLS(&tls.Config{ServerName: t.config.Host}); err != nil {
				return fmt.Errorf("smtp starttls: %w", err)
			}
		}
	}

	// Authenticate if credentials provided
	if t.config.User != "" && t.config.Password != "" {
		auth := smtp.PlainAuth("", t.config.User, t.config.Password, t.config.Host)
		if err := client.Auth(auth); err != nil {
			return fmt.Errorf("smtp auth: %w", err)
		}
	}

	// Send
	if err := client.Mail(t.config.From); err != nil {
		return fmt.Errorf("smtp MAIL: %w", err)
	}
	if err := client.Rcpt(to); err != nil {
		return fmt.Errorf("smtp RCPT: %w", err)
	}
	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("smtp DATA: %w", err)
	}
	if _, err := w.Write([]byte(msg.String())); err != nil {
		return fmt.Errorf("smtp write: %w", err)
	}
	if err := w.Close(); err != nil {
		return fmt.Errorf("smtp close data: %w", err)
	}

	return client.Quit()
}

// SendAsync wraps Send in a goroutine so notification delivery isn't blocked.
func (t *EmailTransport) SendAsync(to, subject, htmlBody, textBody string) {
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()
		if err := t.Send(ctx, to, subject, htmlBody, textBody); err != nil {
			log.Printf("[email] failed to send to %s: %v", to, err)
		}
	}()
}

// LoadSMTPConfig reads SMTP settings from global config and returns an SMTPConfig.
// Returns nil if email is not enabled or config is missing.
// The vault parameter is optional; if provided, it decrypts the SMTP password.
func LoadSMTPConfig(gc store.GlobalConfigStore, vault interface{}) (*SMTPConfig, error) {
	raw, err := gc.Get(context.Background(), "notifications")
	if err != nil {
		return nil, fmt.Errorf("no notification settings: %w", err)
	}

	// Check if email is enabled
	enabled, _ := raw["email_enabled"].(bool)
	if !enabled {
		return nil, nil
	}

	cfg := SMTPConfig{
		Host: stringVal(raw, "smtp_host"),
		Port: intVal(raw, "smtp_port", 587),
		User: stringVal(raw, "smtp_user"),
		From: stringVal(raw, "smtp_from"),
		TLS:  boolVal(raw, "smtp_tls"),
	}

	// Password: try decrypted value first, then raw
	cfg.Password = stringVal(raw, "smtp_password")

	if cfg.Host == "" {
		return nil, fmt.Errorf("smtp_host is required")
	}
	if cfg.From == "" {
		return nil, fmt.Errorf("smtp_from is required")
	}

	return &cfg, nil
}

func stringVal(m map[string]interface{}, key string) string {
	if v, ok := m[key].(string); ok {
		return v
	}
	return ""
}

func intVal(m map[string]interface{}, key string, def int) int {
	switch v := m[key].(type) {
	case float64:
		return int(v)
	case int:
		return v
	}
	return def
}

func boolVal(m map[string]interface{}, key string) bool {
	v, _ := m[key].(bool)
	return v
}
