//go:build quickstart

// Run this using docker-compose.yml, see Makefile.

package main

import (
	"crypto/tls"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/mjl-/mox/dns"
	"github.com/mjl-/mox/imapclient"
	"github.com/mjl-/mox/mlog"
	"github.com/mjl-/mox/mox-"
	"github.com/mjl-/mox/sasl"
	"github.com/mjl-/mox/smtpclient"
)

var xlog = mlog.New("quickstart")

func tcheck(t *testing.T, err error, msg string) {
	t.Helper()
	if err != nil {
		t.Fatalf("%s: %s", msg, err)
	}
}

func TestDeliver(t *testing.T) {
	mlog.Logfmt = true

	hostname, err := os.Hostname()
	tcheck(t, err, "hostname")
	ourHostname, err := dns.ParseDomain(hostname)
	tcheck(t, err, "parse hostname")

	// Deliver submits a message over submissions, and checks with imap idle if the
	// message is received by the destination mail server.
	deliver := func(desthost, mailfrom, password, rcptto, imaphost, imapuser, imappassword string) {
		t.Helper()

		// Connect to IMAP, execute IDLE command, which will return on deliver message.
		// TLS certificates work because the container has the CA certificates configured.
		imapconn, err := tls.Dial("tcp", imaphost+":993", nil)
		tcheck(t, err, "dial imap")
		defer imapconn.Close()

		imaperr := make(chan error, 1)
		go func() {
			go func() {
				x := recover()
				if x == nil {
					return
				}
				imaperr <- x.(error)
			}()
			xcheck := func(err error, format string) {
				if err != nil {
					panic(fmt.Errorf("%s: %w", format, err))
				}
			}

			imapc, err := imapclient.New(imapconn, false)
			xcheck(err, "new imapclient")

			_, _, err = imapc.Login(imapuser, imappassword)
			xcheck(err, "imap login")

			_, _, err = imapc.Select("Inbox")
			xcheck(err, "imap select inbox")

			err = imapc.Commandf("", "idle")
			xcheck(err, "write imap idle command")
			_, _, _, err = imapc.ReadContinuation()
			xcheck(err, "read imap continuation")

			done := make(chan error)
			go func() {
				defer func() {
					x := recover()
					if x != nil {
						done <- fmt.Errorf("%v", x)
					}
				}()
				untagged, err := imapc.ReadUntagged()
				if err != nil {
					done <- err
					return
				}
				if _, ok := untagged.(imapclient.UntaggedExists); !ok {
					done <- fmt.Errorf("expected imapclient.UntaggedExists, got %#v", untagged)
					return
				}
				done <- nil
			}()

			period := 30 * time.Second
			timer := time.NewTimer(period)
			defer timer.Stop()
			select {
			case err = <-done:
			case <-timer.C:
				err = fmt.Errorf("nothing within %v", period)
			}
			xcheck(err, "waiting for imap untagged response to idle")
			imaperr <- nil
		}()

		conn, err := tls.Dial("tcp", desthost+":465", nil)
		tcheck(t, err, "dial submission")
		defer conn.Close()

		msg := fmt.Sprintf(`From: <%s>
To: <%s>
Subject: test message

This is the message.
`, mailfrom, rcptto)
		msg = strings.ReplaceAll(msg, "\n", "\r\n")
		auth := []sasl.Client{sasl.NewClientPlain(mailfrom, password)}
		c, err := smtpclient.New(mox.Context, xlog, conn, smtpclient.TLSSkip, ourHostname, dns.Domain{ASCII: desthost}, auth)
		tcheck(t, err, "smtp hello")
		err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false)
		tcheck(t, err, "deliver with smtp")
		err = c.Close()
		tcheck(t, err, "close smtpclient")

		err = <-imaperr
		tcheck(t, err, "imap idle")
	}

	xlog.Print("submitting email to moxacmepebble, waiting for imap notification at moxmail2, takes time because first-time sender")
	t0 := time.Now()
	deliver("moxacmepebble.mox1.example", "moxtest1@mox1.example", "accountpass1234", "moxtest2@mox2.example", "moxmail2.mox2.example", "moxtest2@mox2.example", "accountpass4321")
	xlog.Print("success", mlog.Field("duration", time.Since(t0)))

	xlog.Print("submitting email to moxmail2, waiting for imap notification at moxacmepebble, takes time because first-time sender")
	t0 = time.Now()
	deliver("moxmail2.mox2.example", "moxtest2@mox2.example", "accountpass4321", "moxtest1@mox1.example", "moxacmepebble.mox1.example", "moxtest1@mox1.example", "accountpass1234")
	xlog.Print("success", mlog.Field("duration", time.Since(t0)))
}
