// Package mail implements the durable mail outbox documented in
// `backend/PLAN.md` §5.6 and `backend/README.md` §9. Producers call
// EnqueueLoginCode or EnqueueTemplate; the rows land in
// `backend.mail_deliveries` together with their payload and recipients.
// A single Worker goroutine claims due rows with
// `SELECT … FOR UPDATE SKIP LOCKED`, sends them through SMTP via the
// `wneessen/go-mail` library, records every attempt, and dead-letters
// rows that exceed the configured maximum.
//
// Until The implementation lands the notification module, the AdminNotifier
// dependency is satisfied by NewNoopAdminNotifier — same pattern auth
// uses for LoginCodeMailer and SessionInvalidator.
package mail

import (
	"context"
	"galaxy/backend/internal/config"
	"time"

	"go.uber.org/zap"
)

// Service is the mail-domain entry point. It wires the persistence
// store, the SMTP sender, the admin-notification publisher used on
// dead-letter, the runtime configuration, and a structured logger.
type Service struct {
	deps Deps
}

// NewService constructs a Service from deps. A nil Now defaults to
// time.Now; a nil Logger defaults to zap.NewNop. Store and SMTP must be
// supplied — calling Service methods with either nil panics on first
// use, matching how the rest of `internal/*` signals missing wiring.
func NewService(deps Deps) *Service {
	if deps.Now == nil {
		deps.Now = time.Now
	}
	if deps.Logger == nil {
		deps.Logger = zap.NewNop()
	}
	deps.Logger = deps.Logger.Named("mail")
	if deps.Admin == nil {
		deps.Admin = NewNoopAdminNotifier(deps.Logger)
	}
	return &Service{deps: deps}
}

// Backoff parameters for the worker retry schedule. The values match
// the trade-off documented in `backend/README.md` and `backend/docs/`: a 5
// second base, ×2 growth, capped at one hour, with ±25% jitter.
const (
	backoffBase = 5 * time.Second
	backoffFactor = 2.0
	backoffMax = time.Hour
	backoffJitter = 0.25
)

// Status values stored in `mail_deliveries.status`. Mirrored by the
// CHECK constraint added in migration 00001.
const (
	StatusPending = "pending"
	StatusRetrying = "retrying"
	StatusSent = "sent"
	StatusDeadLettered = "dead_lettered"
)

// Outcome values stored in `mail_attempts.outcome`. Mirrored by the
// CHECK constraint added in migration 00001.
const (
	OutcomeSuccess = "success"
	OutcomeTransientError = "transient_error"
	OutcomePermanentError = "permanent_error"
)

// Recipient kinds stored in `mail_recipients.kind`. The 5.6
// implementation only emits 'to'; cc/bcc/reply_to remain available
// for future producers.
const (
	RecipientKindTo = "to"
)

// Config returns the runtime mail configuration. Worker uses it to
// schedule the scan loop and bound retries.
func (s *Service) Config() config.MailConfig {
	return s.deps.Config
}

// Stats returns the live count of `mail_deliveries` rows grouped by
// status. The metricsapi server reads this through the Service so
// `mail_outbox_depth{state}` (README §15) does not require the worker
// to publish gauges from inside its hot path.
func (s *Service) Stats(ctx context.Context) (map[string]int64, error) {
	return s.deps.Store.CountByStatus(ctx)
}
