package deliberi

import (
	"context"
	"io"
	"log/slog"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"tangled.org/core/deliberi/config"
	deldb "tangled.org/core/deliberi/db"
	"tangled.org/core/deliberi/mailer"
	"tangled.org/core/deliberi/models"
	"tangled.org/core/idresolver"
)

type sentMail struct{ sent []mailer.Message }

func (s *sentMail) Send(m mailer.Message) error { s.sent = append(s.sent, m); return nil }
func (s *sentMail) Domain() string              { return "notifs.example" }

func TestEmailsAboutOneTicketShareAThread(t *testing.T) {
	const recipient = "did:plc:recipient"
	const issue = "at://did:plc:repo/org.tangled.track.ticket/issue1"
	const other = "at://did:plc:repo/org.tangled.track.ticket/issue2"

	database, err := deldb.Make(context.Background(), filepath.Join(t.TempDir(), "x.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { database.Close() })
	if err := deldb.AddEmail(database, models.Email{Did: recipient, Address: "r@example.com", Verified: true, Primary: true}); err != nil {
		t.Fatal(err)
	}
	prefs := models.DefaultNotificationPreferences(syntax.DID(recipient))
	prefs.EmailNotifications = true
	if err := deldb.UpsertNotificationPreferences(database, prefs); err != nil {
		t.Fatal(err)
	}

	sender := &sentMail{}
	d := NewDispatcher(database, sender, config.ResendConfig{}, "https://tangled.test",
		idresolver.DefaultResolver("http://127.0.0.1:1"), slog.New(slog.NewTextHandler(io.Discard, nil)), true)

	start := time.Now().Add(-time.Hour)
	seq := 0
	notify := func(entity, title string, typ models.NotificationType) {
		seq++
		n := &models.Notification{
			RecipientDid: recipient,
			AtUri:        entity + "#" + string(rune('a'+seq)),
			Type:         typ,
			ActorDid:     "did:plc:actor",
			EntityAt:     entity,
			EntityKind:   models.EntityKindIssue,
			EntityTitle:  title,
			Created:      start.Add(time.Duration(seq) * time.Minute),
		}
		if err := deldb.CreateNotification(database, n); err != nil {
			t.Fatal(err)
		}
	}

	notify(issue, "Crash on start", models.NotificationTypeIssueCommented)
	notify(issue, "Crash on start", models.NotificationTypeIssueCommented)
	notify(other, "Docs typo", models.NotificationTypeIssueCreated)
	d.dispatch(context.Background())

	notify(issue, "Crash on start", models.NotificationTypeIssueClosed)
	notify(issue, "Crash on start", models.NotificationTypeIssueReopen)
	d.dispatch(context.Background())

	if len(sender.sent) != 3 {
		t.Fatalf("sent %d emails, want one per ticket per batch (3)", len(sender.sent))
	}
	var issueMails []mailer.Message
	var otherMail mailer.Message
	for _, m := range sender.sent {
		if strings.Contains(m.Subject, "Docs typo") {
			otherMail = m
		} else {
			issueMails = append(issueMails, m)
		}
	}
	if len(issueMails) != 2 {
		t.Fatalf("issue emails = %d, want 2", len(issueMails))
	}
	first, second := issueMails[0], issueMails[1]
	if first.Subject != second.Subject {
		t.Errorf("subjects differ within a thread: %q vs %q", first.Subject, second.Subject)
	}
	root := first.Headers["References"]
	if root == "" || second.Headers["References"] != root || second.Headers["In-Reply-To"] != root {
		t.Errorf("issue emails don't share a thread root: %v / %v", first.Headers, second.Headers)
	}
	if otherMail.Headers["References"] == root {
		t.Errorf("a different ticket joined the same thread")
	}
	if !strings.Contains(first.Text, "commented") || strings.Contains(first.Text, "closed") {
		t.Errorf("first batch should hold only the comments:\n%s", first.Text)
	}
	closed, reopened := strings.Index(second.Text, "closed this issue"), strings.Index(second.Text, "reopened this issue")
	if closed < 0 || reopened < 0 || closed > reopened {
		t.Errorf("second batch should list close then reopen:\n%s", second.Text)
	}

	d.dispatch(context.Background())
	if len(sender.sent) != 3 {
		t.Errorf("a quiet tick resent mail: %d", len(sender.sent))
	}
}
