// Package diplomail owns the diplomatic-mail subsystem of the Galaxy
// backend service. Messages live in the lobby-side domain (their
// storage and lifecycle are tied to a game), but they are surfaced
// in-game: lobby exposes only an unread-count badge per game while the
// in-game mail view reads and writes through this package.
//
// Stage A implements the personal single-recipient subset:
//
//   - send/read/mark-read/soft-delete handlers for a player addressing
//     one other active member of the game;
//   - a push event (`diplomail.message.received`) materialised through
//     the existing notification pipeline so the recipient gets a live
//     toast when online;
//   - an unread-counts endpoint that drives the lobby badge.
//
// Later stages add admin/owner/system mail, lifecycle hooks, paid-tier
// player broadcasts, multi-game broadcasts, bulk purge, and the
// language-detection / translation cache.
package diplomail

import (
	"time"

	"galaxy/backend/internal/config"
	"galaxy/backend/internal/diplomail/detector"
	"galaxy/backend/internal/diplomail/translator"

	"go.uber.org/zap"
)

// Kind values stored verbatim in `diplomail_messages.kind`. The schema
// CHECK constraint pins this to the closed set declared below.
const (
	// KindPersonal is a replyable player-to-player message. The
	// sender is always a `sender_kind='player'`.
	KindPersonal = "personal"

	// KindAdmin is a non-replyable administrative notification.
	// The sender is either a human admin (`sender_kind='admin'`)
	// or the system itself (`sender_kind='system'`).
	KindAdmin = "admin"
)

// Sender kind values stored verbatim in `diplomail_messages.sender_kind`.
const (
	// SenderKindPlayer marks the sender as an end-user account.
	// `sender_user_id` and `sender_username` carry the player's id
	// and immutable `accounts.user_name`.
	SenderKindPlayer = "player"

	// SenderKindAdmin marks the sender as a site administrator.
	// `sender_username` carries `admin_accounts.username`.
	SenderKindAdmin = "admin"

	// SenderKindSystem marks the sender as the service itself
	// (lifecycle hooks). Both id and username are NULL.
	SenderKindSystem = "system"
)

// Broadcast scope values stored verbatim in
// `diplomail_messages.broadcast_scope`. Stage A only emits `single`;
// Stage B / C add `game_broadcast` and `multi_game_broadcast`.
const (
	BroadcastScopeSingle             = "single"
	BroadcastScopeGameBroadcast      = "game_broadcast"
	BroadcastScopeMultiGameBroadcast = "multi_game_broadcast"
)

// LangUndetermined is the BCP 47 placeholder stored in
// `diplomail_messages.body_lang` when language detection has not yet
// been performed or could not produce a result. Stage A writes this
// value unconditionally; Stage D replaces it with the detected tag.
const LangUndetermined = "und"

// Service is the diplomatic-mail entry point. Every public method is
// goroutine-safe; concurrency safety is delegated to Postgres for
// persisted state.
type Service struct {
	deps Deps
}

// NewService constructs a Service from deps. Logger and Now are
// defaulted; Store must be non-nil and Memberships must be non-nil
// because every send path queries the active membership roster.
func NewService(deps Deps) *Service {
	if deps.Logger == nil {
		deps.Logger = zap.NewNop()
	}
	deps.Logger = deps.Logger.Named("diplomail")
	if deps.Now == nil {
		deps.Now = time.Now
	}
	if deps.Notification == nil {
		deps.Notification = NewNoopNotificationPublisher(deps.Logger)
	}
	if deps.Detector == nil {
		deps.Detector = detector.NoopDetector{}
	}
	if deps.Translator == nil {
		deps.Translator = translator.NewNoop()
	}
	if deps.Config.MaxBodyBytes <= 0 {
		deps.Config.MaxBodyBytes = 4096
	}
	if deps.Config.MaxSubjectBytes < 0 {
		deps.Config.MaxSubjectBytes = 256
	}
	return &Service{deps: deps}
}

// Config returns the service's runtime configuration. Tests and the
// HTTP layer occasionally surface the limits to clients (the OpenAPI
// schema documents them too).
func (s *Service) Config() config.DiplomailConfig {
	if s == nil {
		return config.DiplomailConfig{}
	}
	return s.deps.Config
}

// Logger returns the package-named logger. Used by the optional async
// worker and by tests asserting on log output.
func (s *Service) Logger() *zap.Logger {
	if s == nil {
		return zap.NewNop()
	}
	return s.deps.Logger
}

// nowUTC returns the configured clock normalised to UTC. Matches the
// convention used everywhere else in `backend` so persisted
// timestamps compare cleanly regardless of host timezone.
func (s *Service) nowUTC() time.Time {
	return s.deps.Now().UTC()
}
