package daemon

import (
	"context"
	"log/slog"
	"time"

	ws "banger/internal/daemon/workspace"
	"banger/internal/model"
	"banger/internal/paths"
	"banger/internal/store"
	"banger/internal/system"
)

// WorkspaceService owns workspace.prepare / workspace.export plus the
// ssh-key + git-identity sync that runs as part of VM start's
// prepare_work_disk capability hook. The workspaceLocks set lives here
// so its scope (serialise concurrent tar imports on the same VM) is
// obvious at the field definition.
//
// The inspect/import test seams are per-service fields so tests inject
// fakes without mutating package-level state.
type WorkspaceService struct {
	runner system.CommandRunner
	logger *slog.Logger
	config model.DaemonConfig
	layout paths.Layout
	store  *store.Store

	// workspaceLocks serialises concurrent workspace.prepare /
	// workspace.export on the same VM. Separate from vmLocks so slow
	// guest I/O doesn't block lifecycle ops.
	workspaceLocks vmLockSet

	// Peer-service access via narrow function-typed dependencies.
	// WorkspaceService doesn't hold pointers to the full VMService or
	// HostNetwork; it only sees the exact operations it needs.
	vmResolver      func(ctx context.Context, idOrName string) (model.VMRecord, error)
	aliveChecker    func(vm model.VMRecord) bool
	waitGuestSSH    func(ctx context.Context, address string, interval time.Duration) error
	dialGuest       func(ctx context.Context, address string) (guestSSHClient, error)
	imageResolver   func(ctx context.Context, idOrName string) (model.Image, error)
	imageWorkSeed   func(ctx context.Context, image model.Image, fingerprint string) error
	withVMLockByRef func(ctx context.Context, idOrName string, fn func(model.VMRecord) (model.VMRecord, error)) (model.VMRecord, error)

	beginOperation func(ctx context.Context, name string, attrs ...any) *operationLog

	// repoInspector is the Inspector used by the real InspectRepo /
	// ImportRepoToGuest fallbacks when the test seams below aren't
	// set. wireServices installs the production one; tests that want
	// to intercept only the host-command surface (not the whole
	// inspect/import hook) can assign a stub-runner Inspector here.
	repoInspector *ws.Inspector

	// Test seams.
	workspaceInspectRepo func(ctx context.Context, sourcePath, branchName, fromRef string, includeUntracked bool) (ws.RepoSpec, error)
	workspaceImport      func(ctx context.Context, client ws.GuestClient, spec ws.RepoSpec, guestPath string, mode model.WorkspacePrepareMode) error
}

type workspaceServiceDeps struct {
	runner          system.CommandRunner
	logger          *slog.Logger
	config          model.DaemonConfig
	layout          paths.Layout
	store           *store.Store
	repoInspector   *ws.Inspector
	vmResolver      func(ctx context.Context, idOrName string) (model.VMRecord, error)
	aliveChecker    func(vm model.VMRecord) bool
	waitGuestSSH    func(ctx context.Context, address string, interval time.Duration) error
	dialGuest       func(ctx context.Context, address string) (guestSSHClient, error)
	imageResolver   func(ctx context.Context, idOrName string) (model.Image, error)
	imageWorkSeed   func(ctx context.Context, image model.Image, fingerprint string) error
	withVMLockByRef func(ctx context.Context, idOrName string, fn func(model.VMRecord) (model.VMRecord, error)) (model.VMRecord, error)
	beginOperation  func(ctx context.Context, name string, attrs ...any) *operationLog
}

func newWorkspaceService(deps workspaceServiceDeps) *WorkspaceService {
	return &WorkspaceService{
		runner:          deps.runner,
		logger:          deps.logger,
		config:          deps.config,
		layout:          deps.layout,
		store:           deps.store,
		repoInspector:   deps.repoInspector,
		vmResolver:      deps.vmResolver,
		aliveChecker:    deps.aliveChecker,
		waitGuestSSH:    deps.waitGuestSSH,
		dialGuest:       deps.dialGuest,
		imageResolver:   deps.imageResolver,
		imageWorkSeed:   deps.imageWorkSeed,
		withVMLockByRef: deps.withVMLockByRef,
		beginOperation:  deps.beginOperation,
	}
}
