package devsandbox

import (
	"context"
	"errors"
	"testing"

	"galaxy/backend/internal/config"

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

// TestBootstrapSkippedWhenEmailEmpty exercises the no-op branch: with
// the production posture (Email == "") Bootstrap must return without
// touching any dependency. The fact that Users/Lobby/EngineVersions
// are nil here doubles as a check that the early-return runs first.
func TestBootstrapSkippedWhenEmailEmpty(t *testing.T) {
	err := Bootstrap(
		context.Background(),
		Deps{},
		config.DevSandboxConfig{},
		zap.NewNop(),
	)
	if err != nil {
		t.Fatalf("expected nil error on empty email, got: %v", err)
	}
}

// TestBootstrapRejectsZeroPlayerCount confirms the validation
// short-circuits the flow before any DB call when PlayerCount is
// non-positive but Email is set. The error path is fast and never
// dereferences the (still-nil) Users/Lobby deps.
func TestBootstrapRejectsZeroPlayerCount(t *testing.T) {
	err := Bootstrap(
		context.Background(),
		Deps{Users: stubEnsurer{}, Lobby: nil, EngineVersions: nil},
		config.DevSandboxConfig{
			Email:         "dev@local.test",
			EngineImage:   "galaxy-engine:local-dev",
			EngineVersion: "0.0.0-local-dev",
			PlayerCount:   0,
		},
		zap.NewNop(),
	)
	if err == nil {
		t.Fatal("expected error on zero PlayerCount, got nil")
	}
}

// TestBootstrapRejectsMissingDeps checks that a misconfigured wiring
// (Email set but one of the required services nil) fails fast rather
// than panicking when the bootstrap reaches its first service call.
func TestBootstrapRejectsMissingDeps(t *testing.T) {
	err := Bootstrap(
		context.Background(),
		Deps{Users: stubEnsurer{}, Lobby: nil, EngineVersions: nil},
		config.DevSandboxConfig{
			Email:         "dev@local.test",
			EngineImage:   "galaxy-engine:local-dev",
			EngineVersion: "0.0.0-local-dev",
			PlayerCount:   20,
		},
		zap.NewNop(),
	)
	if err == nil {
		t.Fatal("expected error on missing deps, got nil")
	}
	if !errors.Is(err, errMissingDepsSentinel) && err.Error() == "" {
		// The exact wording is not part of the contract; this branch
		// only asserts the error is non-nil and human-readable.
		t.Fatalf("error has empty message: %v", err)
	}
}

// errMissingDepsSentinel exists so the assertion above can compile;
// the real error is constructed via errors.New inside Bootstrap and
// is intentionally not exported. The test only needs to confirm the
// returned error has a message.
var errMissingDepsSentinel = errors.New("sentinel")

// TestTerminalSandboxStatus pins the contract that decides whether a
// previously created sandbox game gets purged on the next boot.
// Terminal states are deleted (cascade-style) so the developer's
// lobby never piles up dead tiles between `make rebuild` cycles.
func TestTerminalSandboxStatus(t *testing.T) {
	terminal := []string{"cancelled", "finished", "start_failed"}
	live := []string{"draft", "enrollment_open", "ready_to_start", "starting", "running", "paused"}

	for _, status := range terminal {
		if !terminalSandboxStatus(status) {
			t.Errorf("expected %q to be terminal", status)
		}
	}
	for _, status := range live {
		if terminalSandboxStatus(status) {
			t.Errorf("expected %q to be non-terminal", status)
		}
	}
}

type stubEnsurer struct{}

func (stubEnsurer) EnsureByEmail(_ context.Context, _, _, _, _ string) (uuid.UUID, error) {
	return uuid.UUID{}, nil
}
