package lib

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"html/template"
	"log"
	"net"
	"net/smtp"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

// Mailer handles sending emails via SMTP.
type Mailer struct {
	Host      string
	Port      int
	User      string
	Pass      string
	From      string
	FromName  string
	templates *template.Template
	enabled   bool
}

// NewMailer creates a new Mailer from environment variables.
// If SMTP_HOST is empty, returns a no-op mailer.
func NewMailer(cfg *Config) *Mailer {
	host := os.Getenv("SMTP_HOST")
	if host == "" {
		return &Mailer{enabled: false}
	}

	portStr := os.Getenv("SMTP_PORT")
	port := 587 // default
	if portStr != "" {
		if p, err := strconv.Atoi(portStr); err == nil {
			port = p
		}
	}

	from := os.Getenv("SMTP_FROM")
	if from == "" {
		from = "noreply@muskepo.com"
	}

	fromName := os.Getenv("SMTP_FROM_NAME")
	if fromName == "" {
		fromName = "Dealspace"
	}

	m := &Mailer{
		Host:     host,
		Port:     port,
		User:     os.Getenv("SMTP_USER"),
		Pass:     os.Getenv("SMTP_PASS"),
		From:     from,
		FromName: fromName,
		enabled:  true,
	}

	return m
}

// LoadTemplates loads email templates from the given directory.
// Templates should be in portal/emails/ directory.
func (m *Mailer) LoadTemplates(templateDir string) error {
	if !m.enabled {
		return nil
	}

	// Parse base template first, then all others
	pattern := filepath.Join(templateDir, "*.html")
	tmpl, err := template.New("").Funcs(emailFuncs()).ParseGlob(pattern)
	if err != nil {
		return fmt.Errorf("parse email templates: %w", err)
	}
	m.templates = tmpl
	return nil
}

// emailFuncs returns template functions for email templates.
func emailFuncs() template.FuncMap {
	return template.FuncMap{
		"gt": func(a, b int) bool { return a > b },
		"sub": func(a, b int) int { return a - b },
		"truncate": func(s string, n int) string {
			if len(s) <= n {
				return s
			}
			return s[:n] + "..."
		},
	}
}

// Enabled returns true if the mailer is configured and can send emails.
func (m *Mailer) Enabled() bool {
	return m.enabled
}

// HARD WHITELIST — only these domains receive real emails. Everything else is silently dropped.
// DO NOT REMOVE THIS WITHOUT JOHAN'S EXPLICIT APPROVAL.
func emailAllowed(to string) bool {
	to = strings.ToLower(strings.TrimSpace(to))
	return strings.HasSuffix(to, "@muskepo.com") || strings.HasSuffix(to, "@jongsma.me")
}

// Send sends an email with the given HTML body using explicit STARTTLS.
func (m *Mailer) Send(to, subject, htmlBody string) error {
	if !m.enabled {
		return nil // no-op
	}

	if !emailAllowed(to) {
		log.Printf("MAIL BLOCKED (not whitelisted): to=%s subject=%s", to, subject)
		return nil
	}

	log.Printf("MAIL SENDING: to=%s subject=%s", to, subject)
	msg := m.buildMessage(to, subject, htmlBody)
	addr := fmt.Sprintf("%s:%d", m.Host, m.Port)

	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return fmt.Errorf("dial %s: %w", addr, err)
	}
	defer conn.Close()

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

	if err = client.StartTLS(&tls.Config{ServerName: m.Host}); err != nil {
		return fmt.Errorf("starttls: %w", err)
	}
	if m.User != "" && m.Pass != "" {
		if err = client.Auth(smtp.PlainAuth("", m.User, m.Pass, m.Host)); err != nil {
			return fmt.Errorf("auth: %w", err)
		}
	}
	if err = client.Mail(m.From); err != nil {
		return fmt.Errorf("mail from: %w", err)
	}
	if err = client.Rcpt(to); err != nil {
		return fmt.Errorf("rcpt to %s: %w", to, err)
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("data: %w", err)
	}
	if _, err = w.Write(msg); err != nil {
		return fmt.Errorf("write: %w", err)
	}
	return w.Close()
}

// SendTemplate renders a template and sends it as an email.
func (m *Mailer) SendTemplate(to, subject, tmplName string, data any) error {
	if !m.enabled {
		return nil // no-op
	}

	if m.templates == nil {
		return fmt.Errorf("templates not loaded")
	}

	// Render template
	var buf bytes.Buffer
	if err := m.templates.ExecuteTemplate(&buf, tmplName, data); err != nil {
		return fmt.Errorf("render template %s: %w", tmplName, err)
	}

	return m.Send(to, subject, buf.String())
}

// RenderTemplate renders a template to a string without sending.
// Useful for testing and previewing emails.
func (m *Mailer) RenderTemplate(tmplName string, data any) (string, error) {
	if m.templates == nil {
		return "", fmt.Errorf("templates not loaded")
	}

	var buf bytes.Buffer
	if err := m.templates.ExecuteTemplate(&buf, tmplName, data); err != nil {
		return "", fmt.Errorf("render template %s: %w", tmplName, err)
	}

	return buf.String(), nil
}

// buildMessage constructs an RFC 2822 email message.
func (m *Mailer) buildMessage(to, subject, htmlBody string) []byte {
	var buf bytes.Buffer

	// Headers
	buf.WriteString("MIME-Version: 1.0\r\n")
	buf.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n")
	buf.WriteString(fmt.Sprintf("From: %s <%s>\r\n", m.FromName, m.From))
	buf.WriteString(fmt.Sprintf("To: %s\r\n", to))
	buf.WriteString(fmt.Sprintf("Subject: %s\r\n", m.encodeSubject(subject)))
	buf.WriteString("\r\n")

	// Body
	buf.WriteString(htmlBody)

	return buf.Bytes()
}

// encodeSubject encodes the subject line for non-ASCII characters.
func (m *Mailer) encodeSubject(subject string) string {
	// Check if subject contains non-ASCII
	for _, r := range subject {
		if r > 127 {
			// Use RFC 2047 encoding
			return "=?UTF-8?B?" + base64Encode(subject) + "?="
		}
	}
	return subject
}

// base64Encode encodes a string to base64.
func base64Encode(s string) string {
	const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
	data := []byte(s)
	var result strings.Builder
	for i := 0; i < len(data); i += 3 {
		var n uint32
		remain := len(data) - i
		if remain >= 3 {
			n = uint32(data[i])<<16 | uint32(data[i+1])<<8 | uint32(data[i+2])
			result.WriteByte(base64Chars[n>>18&63])
			result.WriteByte(base64Chars[n>>12&63])
			result.WriteByte(base64Chars[n>>6&63])
			result.WriteByte(base64Chars[n&63])
		} else if remain == 2 {
			n = uint32(data[i])<<16 | uint32(data[i+1])<<8
			result.WriteByte(base64Chars[n>>18&63])
			result.WriteByte(base64Chars[n>>12&63])
			result.WriteByte(base64Chars[n>>6&63])
			result.WriteByte('=')
		} else {
			n = uint32(data[i]) << 16
			result.WriteByte(base64Chars[n>>18&63])
			result.WriteByte(base64Chars[n>>12&63])
			result.WriteString("==")
		}
	}
	return result.String()
}

// ---- Email Data Structures ----

// InviteData is the data for invite.html template.
type InviteData struct {
	InviterName   string
	InviterOrg    string
	ProjectName   string
	InviteURL     string
	RecipientName string
	Role          string
	ExpiresIn     string
}

// TasksAssignedData is the data for tasks_assigned.html template.
type TasksAssignedData struct {
	RecipientName string
	ProjectName   string
	Count         int
	Tasks         []TaskItem
	TasksURL      string
}

// TaskItem represents a single task in the tasks list.
type TaskItem struct {
	Title    string
	DueDate  string
	Priority string
}

// AnswerSubmittedData is the data for answer_submitted.html template.
type AnswerSubmittedData struct {
	RecipientName  string
	AnswererName   string
	RequestTitle   string
	WorkstreamName string
	AnswerPreview  string
	ReviewURL      string
}

// AnswerApprovedData is the data for answer_approved.html template.
type AnswerApprovedData struct {
	RecipientName string
	RequestTitle  string
	Published     bool
	DataRoomURL   string
}

// AnswerRejectedData is the data for answer_rejected.html template.
type AnswerRejectedData struct {
	RecipientName string
	RequestTitle  string
	Reason        string
	RequestURL    string
}

// RequestForwardedData is the data for request_forwarded.html template.
type RequestForwardedData struct {
	RecipientName string
	SenderName    string
	RequestTitle  string
	RequestURL    string
	DueDate       string
	HasDueDate    bool
}

// WelcomeData is the data for welcome.html template.
type WelcomeData struct {
	RecipientName string
	TasksURL      string
}
