package mail_test

import (
	"context"
	"errors"
	"testing"
	"time"

	"galaxy/backend/internal/config"
	"galaxy/backend/internal/mail"

	"github.com/google/uuid"
	"go.uber.org/zap/zaptest"
)

func TestAdminListPagination(t *testing.T) {
	t.Parallel()
	db := startPostgres(t)
	svc := mail.NewService(mail.Deps{
		Store:  mail.NewStore(db),
		SMTP:   newRecordingSender(),
		Config: config.MailConfig{WorkerInterval: time.Hour, MaxAttempts: 3},
		Logger: zaptest.NewLogger(t),
	})

	const total = 7
	for i := range total {
		if err := svc.EnqueueLoginCode(context.Background(), "a@example.test", "1234"+string(rune('0'+i)), 5*time.Minute); err != nil {
			t.Fatalf("enqueue %d: %v", i, err)
		}
	}

	page, err := svc.AdminListDeliveries(context.Background(), 1, 3)
	if err != nil {
		t.Fatalf("list page 1: %v", err)
	}
	if len(page.Items) != 3 {
		t.Fatalf("page1 size=%d want 3", len(page.Items))
	}
	if page.Total != total {
		t.Fatalf("page1 total=%d want %d", page.Total, total)
	}

	page, err = svc.AdminListDeliveries(context.Background(), 3, 3)
	if err != nil {
		t.Fatalf("list page 3: %v", err)
	}
	if len(page.Items) != 1 {
		t.Fatalf("page3 size=%d want 1", len(page.Items))
	}
}

func TestAdminGetDeliveryNotFound(t *testing.T) {
	t.Parallel()
	db := startPostgres(t)
	svc := mail.NewService(mail.Deps{
		Store:  mail.NewStore(db),
		SMTP:   newRecordingSender(),
		Config: config.MailConfig{WorkerInterval: time.Hour, MaxAttempts: 3},
		Logger: zaptest.NewLogger(t),
	})
	if _, err := svc.AdminGetDelivery(context.Background(), uuid.New()); !errors.Is(err, mail.ErrDeliveryNotFound) {
		t.Fatalf("get missing: want ErrDeliveryNotFound, got %v", err)
	}
}

func TestAdminResendStateMatrix(t *testing.T) {
	t.Parallel()
	db := startPostgres(t)
	sender := newRecordingSender()
	// Match the number of Send calls the matrix triggers (initial
	// success path + resend re-send for the dead-lettered row).
	sender.behaviour = []func(mail.OutboundMessage) error{
		func(mail.OutboundMessage) error { return errors.New("transient #1") },
		func(mail.OutboundMessage) error { return errors.New("transient #2") },
		func(mail.OutboundMessage) error { return nil }, // sent path
	}
	clock := time.Now().UTC().Add(-2 * time.Hour) // bring next_attempt_at into the past
	svc := mail.NewService(mail.Deps{
		Store:  mail.NewStore(db),
		SMTP:   sender,
		Config: config.MailConfig{WorkerInterval: time.Hour, MaxAttempts: 2},
		Now:    func() time.Time { return clock },
		Logger: zaptest.NewLogger(t),
	})
	worker := mail.NewWorker(svc)

	// 1. Drive a row to dead-lettered (two failures with MaxAttempts=2).
	if err := svc.EnqueueLoginCode(context.Background(), "dead@example.test", "111111", 5*time.Minute); err != nil {
		t.Fatalf("enqueue dead: %v", err)
	}
	if err := worker.Tick(context.Background()); err != nil {
		t.Fatalf("tick #1: %v", err)
	}
	if err := worker.Tick(context.Background()); err != nil {
		t.Fatalf("tick #2: %v", err)
	}
	deadList, err := svc.AdminListDeliveries(context.Background(), 1, 5)
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(deadList.Items) != 1 || deadList.Items[0].Status != mail.StatusDeadLettered {
		t.Fatalf("want 1 dead-lettered row, got %+v", deadList.Items)
	}
	deadID := deadList.Items[0].DeliveryID

	// 2. Resend the dead-lettered row -> 200, status flips to pending,
	//    attempts=0.
	resent, err := svc.AdminResendDelivery(context.Background(), deadID)
	if err != nil {
		t.Fatalf("resend dead: %v", err)
	}
	if resent.Status != mail.StatusPending {
		t.Fatalf("status after resend=%q want pending", resent.Status)
	}
	if resent.Attempts != 0 {
		t.Fatalf("attempts after resend=%d want 0", resent.Attempts)
	}

	// 3. Drive the worker once more — third Send call returns nil so
	//    the row transitions to sent.
	if err := worker.Tick(context.Background()); err != nil {
		t.Fatalf("tick post-resend: %v", err)
	}
	d, err := svc.AdminGetDelivery(context.Background(), deadID)
	if err != nil {
		t.Fatalf("get after send: %v", err)
	}
	if d.Status != mail.StatusSent {
		t.Fatalf("status=%q want sent", d.Status)
	}

	// 4. Resend on `sent` -> ErrResendOnSent.
	if _, err := svc.AdminResendDelivery(context.Background(), deadID); !errors.Is(err, mail.ErrResendOnSent) {
		t.Fatalf("resend on sent: want ErrResendOnSent, got %v", err)
	}

	// 5. Resend on missing -> ErrDeliveryNotFound.
	if _, err := svc.AdminResendDelivery(context.Background(), uuid.New()); !errors.Is(err, mail.ErrDeliveryNotFound) {
		t.Fatalf("resend missing: want ErrDeliveryNotFound, got %v", err)
	}
}

func TestServiceStats(t *testing.T) {
	t.Parallel()
	db := startPostgres(t)
	svc := mail.NewService(mail.Deps{
		Store:  mail.NewStore(db),
		SMTP:   newRecordingSender(),
		Config: config.MailConfig{WorkerInterval: time.Hour, MaxAttempts: 3},
		Logger: zaptest.NewLogger(t),
	})
	for i := range 3 {
		if err := svc.EnqueueLoginCode(context.Background(), "stats@example.test", "55555"+string(rune('0'+i)), 5*time.Minute); err != nil {
			t.Fatalf("enqueue: %v", err)
		}
	}
	stats, err := svc.Stats(context.Background())
	if err != nil {
		t.Fatalf("stats: %v", err)
	}
	if stats[mail.StatusPending] != 3 {
		t.Fatalf("pending=%d want 3", stats[mail.StatusPending])
	}
	if _, ok := stats[mail.StatusSent]; !ok {
		t.Fatal("Stats must always return all four buckets")
	}
}
