package runtime

import (
	"context"
	"time"

	"galaxy/backend/internal/config"
	"galaxy/backend/internal/dockerclient"
	"galaxy/backend/internal/engineclient"

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

// LobbyConsumer is the inbound surface the runtime uses to publish
// snapshots and adoption / removal events back into lobby. The
// canonical implementation is `*lobby.Service`; tests substitute a
// hand-rolled fake that records the calls.
//
// The interface is intentionally narrow: runtime only forwards
// data-plane events. Lobby owns every status transition that follows
// from the snapshot.
type LobbyConsumer interface {
	// OnRuntimeSnapshot is invoked synchronously after every successful
	// engine read or health-probe transition. Lobby maps the snapshot
	// into its `games.runtime_snapshot` projection and may transition
	// the game's lifecycle status.
	OnRuntimeSnapshot(ctx context.Context, gameID uuid.UUID, snapshot LobbySnapshot) error

	// OnRuntimeJobResult is invoked by the reconciler when a labelled
	// container that lobby believes is alive has disappeared. Lobby
	// reacts by cancelling the game (the engine container is gone).
	OnRuntimeJobResult(ctx context.Context, gameID uuid.UUID, result JobResult) error
}

// LobbySnapshot is the runtime → lobby DTO. It is the runtime's view
// of the engine status response, plus the per-player observations
// lobby needs for capable-finish promotion.
//
// The structure intentionally mirrors `lobby.RuntimeSnapshot` in
// shape; runtime keeps its own version so the two packages do not
// import each other directly. The cmd/backend wiring layer adapts
// between them.
type LobbySnapshot struct {
	CurrentTurn int32
	RuntimeStatus string
	EngineHealth string
	ObservedAt time.Time
	PlayerStats []LobbyPlayerStats
}

// LobbyPlayerStats is the per-player observation read from a runtime
// snapshot. `MaxPlanets` / `MaxPopulation` are the per-snapshot
// running maxima; lobby aggregates across the game lifetime.
type LobbyPlayerStats struct {
	UserID uuid.UUID
	InitialPlanets int32
	InitialPopulation int32
	CurrentPlanets int32
	CurrentPopulation int32
	MaxPlanets int32
	MaxPopulation int32
}

// JobResult is the outcome envelope passed to
// `LobbyConsumer.OnRuntimeJobResult`. The reconciler produces it on
// adoption / removal events; future job paths (start, stop, restart)
// may reuse the same envelope.
type JobResult struct {
	Op string
	Status string
	Message string
}

// NotificationPublisher is the outbound surface runtime uses to emit
// admin-channel notifications enumerated under `runtime.*` in
// `backend/README.md` §10. The real implementation lives in
// `backend/internal/notification` ; until then
// `NewNoopNotificationPublisher` ships a logger-only stub so the
// runtime path stays callable end-to-end during tests.
//
// Kind must be one of `runtime.image_pull_failed`,
// `runtime.container_start_failed`, or `runtime.start_config_invalid`.
// Payload carries the kind-specific fields documented in the catalog.
// The IdempotencyKey is supplied by the caller and feeds the
// notification UNIQUE(kind, idempotency_key) constraint.
type NotificationPublisher interface {
	PublishRuntimeEvent(ctx context.Context, kind, idempotencyKey string, payload map[string]any) error
}

// NewNoopNotificationPublisher returns a NotificationPublisher that
// logs every event at info level and returns nil. The implementation swaps in
// the real `*notification.Service` adapter.
func NewNoopNotificationPublisher(logger *zap.Logger) NotificationPublisher {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &noopNotificationPublisher{logger: logger.Named("runtime.notify.noop")}
}

type noopNotificationPublisher struct {
	logger *zap.Logger
}

func (p *noopNotificationPublisher) PublishRuntimeEvent(_ context.Context, kind, idempotencyKey string, payload map[string]any) error {
	p.logger.Info("runtime event (noop publisher)",
		zap.String("kind", kind),
		zap.String("idempotency_key", idempotencyKey),
		zap.Int("payload_keys", len(payload)),
	)
	return nil
}

// Deps aggregates every collaborator the runtime Service depends on.
// Constructing the Service through Deps (rather than positional args)
// keeps the wiring patches small as new dependencies are added.
type Deps struct {
	Store *Store
	Cache *Cache
	EngineVersions *EngineVersionService

	Docker dockerclient.Client
	Engine *engineclient.Client
	Lobby LobbyConsumer
	Notification NotificationPublisher

	// DockerNetwork is the user-defined Docker network name engine
	// containers attach to. Wired from `cfg.Docker.Network`.
	DockerNetwork string

	// HostStateRoot is the host-side directory that holds per-game
	// state subdirectories. Wired from `cfg.Game.StateRoot`.
	HostStateRoot string

	Config config.RuntimeConfig
	Logger *zap.Logger
	Now func() time.Time
}
