package mail

import (
	"bytes"
	"fmt"
	"html/template"
	"net/mail"
	"net/url"
	"strings"

	"git.kirsle.net/apps/gophertype/pkg/console"
	"git.kirsle.net/apps/gophertype/pkg/markdown"
	"git.kirsle.net/apps/gophertype/pkg/models"
	"git.kirsle.net/apps/gophertype/pkg/responses"
	"git.kirsle.net/apps/gophertype/pkg/settings"
	"github.com/microcosm-cc/bluemonday"
	gomail "gopkg.in/gomail.v2"
)

// Email configuration.
type Email struct {
	To             string
	ReplyTo        string
	Admin          bool /* admin view of the email */
	Subject        string
	UnsubscribeURL string
	Data           map[string]interface{}

	Template string
}

// SendEmail sends an email.
func SendEmail(email Email) {
	s := settings.Current

	// Suppress sending any mail when no mail settings are configured, but go
	// through the motions -- great for local dev.
	var doNotMail bool
	if !s.MailEnabled || s.MailHost == "" || s.MailPort == 0 || s.MailSender == "" {
		console.Info("Suppressing email: not completely configured")
		doNotMail = true
	}

	// Resolve the template.
	tmpl, err := responses.GetFile(email.Template)
	if err != nil {
		console.Error("SendEmail: %s", err.Error())
		return
	}

	// Render the template to HTML.
	var html bytes.Buffer
	t := template.New(email.Template)
	t, err = t.Parse(string(tmpl))
	if err != nil {
		console.Error("SendEmail: template parsing error: %s", err.Error())
	}

	// Execute the template.
	err = t.ExecuteTemplate(&html, email.Template, email)
	if err != nil {
		console.Error("SendEmail: template execution error: %s", err.Error())
	}

	// Condense the body down to plain text, lazily. Who even has a plain text
	// email client anymore?
	rawLines := strings.Split(
		bluemonday.StrictPolicy().Sanitize(html.String()),
		"\n",
	)
	var lines []string
	for _, line := range rawLines {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			continue
		}
		lines = append(lines, line)
	}
	plaintext := strings.Join(lines, "\n\n")

	// If we're not actually going to send the mail, this is a good place to stop.
	if doNotMail {
		console.Info("Not going to send an email.")
		console.Debug("The message was going to be:\n%s", plaintext)
		return
	}

	m := gomail.NewMessage()
	m.SetHeader("From", fmt.Sprintf("%s <%s>", s.Title, s.MailSender))
	m.SetHeader("To", email.To)
	if email.ReplyTo != "" {
		m.SetHeader("Reply-To", email.ReplyTo)
	}
	m.SetHeader("Subject", email.Subject)
	m.SetBody("text/plain", plaintext)
	m.AddAlternative("text/html", html.String())

	d := gomail.NewDialer(s.MailHost, s.MailPort, s.MailUsername, s.MailPassword)

	console.Info("SendEmail: %s (%s) to %s", email.Subject, email.Template, email.To)
	if err := d.DialAndSend(m); err != nil {
		console.Error("SendEmail: %s", err.Error())
	}
}

// EmailAdmins sends an e-mail to all admin user email addresses.
func EmailAdmins(email Email) {
	if adminEmails, err := models.Users.ListAdminEmails(); err == nil {
		email.To = strings.Join(adminEmails, ", ")
		email.Admin = true
		console.Info("Mail site admin '%s' about email '%s'", email.To, email.Subject)
		SendEmail(email)
	}
}

// NotifyComment sends notification emails about comments.
func NotifyComment(subject string, originURL string, c models.Comment) {
	s := settings.Current
	if s.BaseURL == "" {
		console.Error("Can't send comment notification because the site URL is not configured")
		return
	}

	// Prepare the email payload.
	email := Email{
		Template: "_builtin/email/comment.gohtml",
		Subject:  "Comment Added: " + subject,
		Data: map[string]interface{}{
			"Name":    c.Name,
			"Subject": subject,
			"Body":    template.HTML(markdown.RenderMarkdown(c.Body)),
			"URL":     strings.Trim(s.BaseURL, "/") + originURL,
			"QuickDelete": fmt.Sprintf("%s/comments/quick-delete?d=%s&next=%s",
				strings.Trim(s.BaseURL, "/"),
				url.QueryEscape(c.DeleteToken),
				url.QueryEscape(strings.Trim(s.BaseURL, "/")+originURL),
			),
		},
	}

	// Email the site admins.
	if adminEmails, err := models.Users.ListAdminEmails(); err == nil {
		email.To = strings.Join(adminEmails, ", ")
		email.Admin = true
		console.Info("Mail site admin '%s' about comment notification on '%s'", email.To, c.Thread)
		SendEmail(email)
	}

	// // Email the subscribers.
	email.Admin = false
	if subscribers, err := models.Comments.GetSubscribers(c.Thread); err == nil {
		email.Subject = "A new comment has been added to: " + subject

		for _, to := range subscribers {
			// Don't email to the writer of the comment.
			if to == c.Email {
				continue
			}
			email.To = to
			email.UnsubscribeURL = fmt.Sprintf("%s/comments/subscription?t=%s&e=%s",
				strings.Trim(s.BaseURL, "/"),
				url.QueryEscape(c.Thread),
				url.QueryEscape(to),
			)
			console.Info("Mail subscriber '%s' about comment notification on '%s'", email.To, c.Thread)
			SendEmail(email)
		}
	}
}

// ParseAddress parses an email address.
func ParseAddress(addr string) (*mail.Address, error) {
	return mail.ParseAddress(addr)
}
