package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/smtp"
	"os"
	"runtime"
	"strings"
	"time"

	"launchpad.net/email-reminder/internal/config"
	"launchpad.net/email-reminder/internal/events"
	"launchpad.net/email-reminder/internal/util"
)

type Notifier interface {
	Send(body, subject string, recipients []util.EmailRecipient) []error
	SendDebug(now time.Time, event events.Event, when string, failures []error, defaultRecipient util.EmailRecipient)
}

type SmtpNotifier struct {
	Conf config.Config
}

func (n *SmtpNotifier) Send(body, subject string, recipients []util.EmailRecipient) []error {
	var errs []error

	for _, to := range recipients {
		fullBody := util.FullBody(body, to, config.VersionNumber)
		if err := sendEmail(fullBody, subject, to, n.Conf); err != nil {
			if n.Conf.Verbose {
				log.Printf("Failed to send reminder to %s: %v", to.NameAndEmail(), err)
			}
			errs = append(errs, err)
		}
	}

	return errs
}

func (n *SmtpNotifier) SendDebug(now time.Time, event events.Event, when string, failures []error, defaultRecipient util.EmailRecipient) {
	eventName := strings.TrimSpace(event.Name)
	if eventName == "" {
		eventName = event.String()
	}

	occurrenceLine := "Since this event is OCCURRING TODAY, you should really check your reminders manually."
	if when != "today" {
		occurrenceLine = fmt.Sprintf("Since this event is scheduled for %s, please double-check your reminders manually.", when)
	}

	if n.Conf.Verbose {
		log.Printf("Sending debug email to %s for %s", defaultRecipient.Email, event)
	}

	body := fmt.Sprintf(`WARNING: Due to an error, the email reminder for the event "%s" could not be processed and all I could do was to let you know that there was a problem.

%s

Please forward this email to the email-reminder author so that this problem can be fixed in future versions:

  Francois Marier <francois@fmarier.org>

Thanks!

--------------------------------------------------------------
%s`, eventName, occurrenceLine, composeDebugInfo(now, event, failures))
	subject := fmt.Sprintf("Email-reminder ERROR: %s", eventName)

	if err := sendEmail(body, subject, defaultRecipient, n.Conf); err != nil {
		log.Printf("Could not send debug email for %s: %v", event, err)
	}
}

func sendEmail(body string, subject string, recipient util.EmailRecipient, conf config.Config) error {
	msg := util.AssembleRawMessage(body, subject, recipient.NameAndEmail(), conf.MailFrom, config.VersionNumber)
	if conf.Verbose {
		log.Println(msg)
	}
	if conf.Simulate {
		return nil
	}

	var auth smtp.Auth
	if conf.SmtpUsername != "" && conf.SmtpPassword != "" {
		auth = smtp.PlainAuth("", conf.SmtpUsername, conf.SmtpPassword, conf.SmtpServer)
	}
	if conf.SmtpSsl {
		if err := util.SendMailImplicitTLS(conf.SmtpServer, auth, conf.MailFrom, []string{recipient.Email}, []byte(msg)); err != nil {
			return fmt.Errorf("could not send '%s' email (TLS) to '%s': %w", subject, recipient.Email, err)
		}
	} else if err := util.SendMailPlaintext(conf.SmtpServer, auth, conf.MailFrom, []string{recipient.Email}, []byte(msg)); err != nil {
		return fmt.Errorf("could not send '%s' email to '%s': %w", subject, recipient.Email, err)
	}
	return nil
}

func composeDebugInfo(now time.Time, event events.Event, failures []error) string {
	info := []string{
		fmt.Sprintf("Email-reminder: %s", config.VersionNumber),
		fmt.Sprintf("Go: %s (%s/%s)", runtime.Version(), runtime.GOOS, runtime.GOARCH),
	}

	if hostname, err := os.Hostname(); err == nil && strings.TrimSpace(hostname) != "" {
		info = append(info, fmt.Sprintf("Hostname: '%s'", hostname))
	}

	if pathVar, ok := os.LookupEnv("PATH"); ok {
		info = append(info, fmt.Sprintf("PATH: '%q'", pathVar))
	} else {
		info = append(info, "PATH: <unset>")
	}

	if homeVar, ok := os.LookupEnv("HOME"); ok {
		info = append(info, fmt.Sprintf("HOME: '%q'", homeVar))
	} else {
		info = append(info, "HOME: <unset>")
	}

	if output, err := util.SecureExecCommand("/usr/bin/lsb_release", "-s", "-d"); err == nil {
		if trimmed := strings.TrimSpace(string(output)); trimmed != "" {
			info = append(info, fmt.Sprintf("OS: %s", trimmed))
		}
	}

	if output, err := util.SecureExecCommand("/usr/bin/uname", "-a"); err == nil {
		if trimmed := strings.TrimSpace(string(output)); trimmed != "" {
			info = append(info, fmt.Sprintf("Kernel: %s", trimmed))
		}
	}

	info = append(info, fmt.Sprintf("Time: %s", now.Format(time.RFC3339)))
	info = append(info, "Error(s):")

	for _, failure := range failures {
		info = append(info, fmt.Sprintf("- %v", failure))
	}

	if data, err := json.MarshalIndent(event, "", "  "); err == nil {
		info = append(info, "Event:")
		info = append(info, string(data))
	} else {
		info = append(info, fmt.Sprintf("Event: <unable to serialize: %v>", err))
	}

	return strings.Join(info, "\n")
}
