package email

import (
	"bytes"
	"fmt"
	"mime"
	"mime/quotedprintable"
	"net/smtp"
	"regexp"
	"strings"
	"time"

	"git.handmade.network/hmn/hmn/src/config"
	"git.handmade.network/hmn/hmn/src/hmnurl"
	"git.handmade.network/hmn/hmn/src/oops"
	"git.handmade.network/hmn/hmn/src/perf"
	"git.handmade.network/hmn/hmn/src/templates"
)

// TODO(asaf): Adjust this once we test on the server
const ExpectedEmailSendDuration = time.Millisecond * 1500

type RegistrationEmailData struct {
	Name                    string
	HomepageUrl             string
	CompleteRegistrationUrl string
}

func SendRegistrationEmail(toAddress string, toName string, username string, completionToken string, perf *perf.RequestPerf) error {
	perf.StartBlock("EMAIL", "Registration email")

	perf.StartBlock("EMAIL", "Rendering template")
	contents, err := renderTemplate("email_registration.html", RegistrationEmailData{
		Name:                    toName,
		HomepageUrl:             hmnurl.BuildHomepage(),
		CompleteRegistrationUrl: hmnurl.BuildEmailConfirmation(username, completionToken),
	})
	if err != nil {
		return err
	}
	perf.EndBlock()

	perf.StartBlock("EMAIL", "Sending email")
	err = sendMail(toAddress, toName, "[handmade.network] Registration confirmation", contents)
	if err != nil {
		return oops.New(err, "Failed to send email")
	}
	perf.EndBlock()

	perf.EndBlock()

	return nil
}

type PasswordResetEmailData struct {
	Name               string
	DoPasswordResetUrl string
	Expiration         time.Time
}

func SendPasswordReset(toAddress string, toName string, username string, resetToken string, expiration time.Time, perf *perf.RequestPerf) error {
	perf.StartBlock("EMAIL", "Password reset email")

	perf.StartBlock("EMAIL", "Rendering template")
	contents, err := renderTemplate("email_password_reset.html", PasswordResetEmailData{
		Name:               toName,
		DoPasswordResetUrl: hmnurl.BuildDoPasswordReset(username, resetToken),
		Expiration:         expiration,
	})
	if err != nil {
		return err
	}
	perf.EndBlock()

	perf.StartBlock("EMAIL", "Sending email")
	err = sendMail(toAddress, toName, "[handmade.network] Your password reset request", contents)
	if err != nil {
		return oops.New(err, "Failed to send email")
	}
	perf.EndBlock()

	perf.EndBlock()

	return nil
}

var EmailRegex = regexp.MustCompile(`^[^:\p{Cc} ]+@[^:\p{Cc} ]+\.[^:\p{Cc} ]+$`)

func IsEmail(address string) bool {
	return EmailRegex.Match([]byte(address))
}

func renderTemplate(name string, data interface{}) (string, error) {
	var buffer bytes.Buffer
	template, hasTemplate := templates.Templates[name]
	if !hasTemplate {
		return "", oops.New(nil, "Template not found: %s", name)
	}
	err := template.Execute(&buffer, data)
	if err != nil {
		return "", oops.New(err, "Failed to render template for email")
	}
	contentString := string(buffer.Bytes())
	contentString = strings.ReplaceAll(contentString, "\n", "\r\n")
	return contentString, nil
}

func sendMail(toAddress, toName, subject, contentHtml string) error {
	if config.Config.Email.ForceToAddress != "" {
		toAddress = config.Config.Email.ForceToAddress
	}
	contents := prepMailContents(
		makeHeaderAddress(toAddress, toName),
		makeHeaderAddress(config.Config.Email.FromAddress, config.Config.Email.FromName),
		subject,
		contentHtml,
	)
	return smtp.SendMail(
		fmt.Sprintf("%s:%d", config.Config.Email.ServerAddress, config.Config.Email.ServerPort),
		smtp.PlainAuth("", config.Config.Email.FromAddress, config.Config.Email.FromAddressPassword, config.Config.Email.ServerAddress),
		config.Config.Email.FromAddress,
		[]string{toAddress},
		contents,
	)
}

func makeHeaderAddress(email, fullname string) string {
	if fullname != "" {
		encoded := mime.BEncoding.Encode("utf-8", fullname)
		if encoded == fullname {
			encoded = strings.ReplaceAll(encoded, `"`, `\"`)
			encoded = fmt.Sprintf("\"%s\"", encoded)
		}
		return fmt.Sprintf("%s <%s>", encoded, email)
	} else {
		return email
	}
}

func prepMailContents(toLine string, fromLine string, subject string, contentHtml string) []byte {
	var builder strings.Builder

	builder.WriteString(fmt.Sprintf("To: %s\r\n", toLine))
	builder.WriteString(fmt.Sprintf("From: %s\r\n", fromLine))
	builder.WriteString(fmt.Sprintf("Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z)))
	builder.WriteString(fmt.Sprintf("Subject: %s\r\n", mime.QEncoding.Encode("utf-8", subject)))
	builder.WriteString("Content-Type: text/html; charset=UTF-8\r\n")
	builder.WriteString("Content-Transfer-Encoding: quoted-printable\r\n")
	builder.WriteString("\r\n")
	writer := quotedprintable.NewWriter(&builder)
	writer.Write([]byte(contentHtml))
	writer.Close()
	builder.WriteString("\r\n")

	return []byte(builder.String())
}
