package lobby

import (
	"context"

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

// EntitlementProvider is the read-only view the lobby needs over the
// user-domain entitlement snapshot. The canonical implementation is
// `*user.Service` exposing `GetEntitlement(ctx, userID)`; tests substitute
// a fake.
//
// `MaxRegisteredRaceNames` is the only field consumed by when
// the caller attempts to register a `pending_registration` row the lobby
// counts already-`registered` rows for that user against this limit.
type EntitlementProvider interface {
	GetMaxRegisteredRaceNames(ctx context.Context, userID uuid.UUID) (int32, error)
}

// RuntimeGateway is the outbound surface the lobby uses to ask the runtime
// module to start, pause, resume, or stop an engine container. The real
// implementation lives in `backend/internal/runtime` ; until
// then `NewNoopRuntimeGateway` ships a logger-only stub that pretends the
// request was accepted so the lobby state machine stays exercisable
// end-to-end.
type RuntimeGateway interface {
	StartGame(ctx context.Context, gameID uuid.UUID) error
	StopGame(ctx context.Context, gameID uuid.UUID) error
	PauseGame(ctx context.Context, gameID uuid.UUID) error
	ResumeGame(ctx context.Context, gameID uuid.UUID) error
}

// RuntimeJobResult is the inbound shape used by the runtime reconciler
// when a labelled container that lobby believes is alive has
// disappeared. The wiring connects `Service.OnRuntimeJobResult` against
// this type; the no-op consumer logs the event at debug level.
type RuntimeJobResult struct {
	Op string
	Status string
	Message string
}

// NotificationPublisher is the outbound surface the lobby uses to fan out
// notification intents (invite received, application submitted, race name
// promoted, etc.). The real implementation lives in
// `backend/internal/notification` ; until then
// `NewNoopNotificationPublisher` ships a logger-only stub.
type NotificationPublisher interface {
	PublishLobbyEvent(ctx context.Context, intent LobbyNotification) error
}

// DiplomailPublisher is the outbound surface the lobby uses to drop a
// durable system mail entry whenever a game-state or
// membership-state transition needs to land in the affected players'
// inboxes. The real implementation in `cmd/backend/main` adapts the
// `*diplomail.Service.PublishLifecycle` call; tests and partial
// wiring fall back to `NewNoopDiplomailPublisher`.
type DiplomailPublisher interface {
	PublishLifecycle(ctx context.Context, event LifecycleEvent) error
}

// LifecycleEvent is the open shape carried by a system-mail intent.
// `Kind` is one of the lobby-internal constants
// (`LifecycleKindGamePaused`, etc.). `TargetUser` is populated only
// for membership-scoped events; the publisher derives the game-scoped
// recipient set itself.
type LifecycleEvent struct {
	GameID     uuid.UUID
	Kind       string
	Actor      string
	Reason     string
	TargetUser *uuid.UUID
}

// Lifecycle-event kinds the lobby emits.
const (
	LifecycleKindGamePaused        = "game.paused"
	LifecycleKindGameCancelled     = "game.cancelled"
	LifecycleKindMembershipRemoved = "membership.removed"
	LifecycleKindMembershipBlocked = "membership.blocked"
)

// LobbyNotification is the open shape carried by a notification intent.
// The implementation emits a small set of `Kind` values matching the catalog in
// `backend/README.md` §10. The `Payload` map is the kind-specific data
// blob; recipients are the user_ids the intent should reach.
//
// The struct lives in the lobby package on purpose: it is the producer
// vocabulary. The implementation will reuse it as the notification.Submit input
// (or wrap it in a domain-side type, if more channels show up).
type LobbyNotification struct {
	Kind string
	IdempotencyKey string
	Recipients []uuid.UUID
	Payload map[string]any
}

// NewNoopRuntimeGateway returns a RuntimeGateway that logs every call at
// debug level and returns nil. The lobby state machine treats the no-op
// as "request was accepted asynchronously" — the game stays in `starting`
// until the canonical implementation wires real `runtime` / `OnRuntimeSnapshot` interactions.
func NewNoopRuntimeGateway(logger *zap.Logger) RuntimeGateway {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &noopRuntimeGateway{logger: logger.Named("lobby.runtime.noop")}
}

type noopRuntimeGateway struct {
	logger *zap.Logger
}

func (g *noopRuntimeGateway) StartGame(_ context.Context, gameID uuid.UUID) error {
	g.logger.Debug("noop start-game", zap.String("game_id", gameID.String()))
	return nil
}

func (g *noopRuntimeGateway) StopGame(_ context.Context, gameID uuid.UUID) error {
	g.logger.Debug("noop stop-game", zap.String("game_id", gameID.String()))
	return nil
}

func (g *noopRuntimeGateway) PauseGame(_ context.Context, gameID uuid.UUID) error {
	g.logger.Debug("noop pause-game", zap.String("game_id", gameID.String()))
	return nil
}

func (g *noopRuntimeGateway) ResumeGame(_ context.Context, gameID uuid.UUID) error {
	g.logger.Debug("noop resume-game", zap.String("game_id", gameID.String()))
	return nil
}

// NewNoopNotificationPublisher returns a NotificationPublisher that logs
// every call at debug level and returns nil. The implementation will swap in a
// real publisher backed by `notification.Submit`.
func NewNoopNotificationPublisher(logger *zap.Logger) NotificationPublisher {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &noopNotificationPublisher{logger: logger.Named("lobby.notify.noop")}
}

type noopNotificationPublisher struct {
	logger *zap.Logger
}

func (p *noopNotificationPublisher) PublishLobbyEvent(_ context.Context, intent LobbyNotification) error {
	p.logger.Debug("noop notification",
		zap.String("kind", intent.Kind),
		zap.String("idempotency_key", intent.IdempotencyKey),
		zap.Int("recipients", len(intent.Recipients)),
	)
	return nil
}

// NewNoopDiplomailPublisher returns a DiplomailPublisher that logs
// every call at debug level and returns nil. Used by tests and by
// the lobby Service factory when the Deps.Diplomail field is left
// nil.
func NewNoopDiplomailPublisher(logger *zap.Logger) DiplomailPublisher {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &noopDiplomailPublisher{logger: logger.Named("lobby.diplomail.noop")}
}

type noopDiplomailPublisher struct {
	logger *zap.Logger
}

func (p *noopDiplomailPublisher) PublishLifecycle(_ context.Context, event LifecycleEvent) error {
	p.logger.Debug("noop diplomail lifecycle",
		zap.String("kind", event.Kind),
		zap.String("game_id", event.GameID.String()),
	)
	return nil
}
