// Package owner contains process-local workspace execution ownership.
package owner

import (
	"context"
	"errors"
	"fmt"
	"strings"
)

// ErrOwnerMismatch marks a request whose workspace is not owned by this process.
var ErrOwnerMismatch = errors.New("workspace owner mismatch")

// OwnerMismatchError carries the workspace that failed the execution-owner check.
type OwnerMismatchError struct {
	WorkspaceID string
}

func (err OwnerMismatchError) Error() string {
	workspaceID := strings.TrimSpace(err.WorkspaceID)
	if workspaceID == "" {
		return ErrOwnerMismatch.Error()
	}
	return fmt.Sprintf("%s: workspace_id=%s", ErrOwnerMismatch, workspaceID)
}

func (err OwnerMismatchError) Unwrap() error {
	return ErrOwnerMismatch
}

// ManagedWorkspaceSet answers whether this process owns execution for a workspace.
type ManagedWorkspaceSet interface {
	Has(workspaceID string) bool
}

// Loader loads the workspace IDs assigned to a stable workspace group.
type Loader interface {
	ListActiveWorkspaceIDsForGroup(ctx context.Context, workspaceGroupID string) ([]string, error)
}

// NewAllSet returns a set that owns every workspace.
func NewAllSet() ManagedWorkspaceSet {
	return allSet{}
}

// NewEmptySet returns a set that owns no workspaces.
func NewEmptySet() ManagedWorkspaceSet {
	return staticSet{workspaceIDs: map[string]struct{}{}}
}

// NewStaticSet returns an immutable set from a snapshot of workspace IDs.
func NewStaticSet(workspaceIDs []string) ManagedWorkspaceSet {
	index := make(map[string]struct{}, len(workspaceIDs))
	for _, workspaceID := range workspaceIDs {
		trimmed := strings.TrimSpace(workspaceID)
		if trimmed == "" {
			continue
		}
		index[trimmed] = struct{}{}
	}
	return staticSet{workspaceIDs: index}
}

// LoadStaticSet loads one startup snapshot for a workspace group.
func LoadStaticSet(ctx context.Context, loader Loader, workspaceGroupID string) (ManagedWorkspaceSet, error) {
	trimmedGroupID := strings.TrimSpace(workspaceGroupID)
	if trimmedGroupID == "" {
		return nil, fmt.Errorf("workspace group id is required")
	}
	if loader == nil {
		return nil, fmt.Errorf("workspace placement loader is not configured")
	}
	workspaceIDs, err := loader.ListActiveWorkspaceIDsForGroup(ctx, trimmedGroupID)
	if err != nil {
		return nil, fmt.Errorf("load workspace placements for group %q: %w", trimmedGroupID, err)
	}
	return NewStaticSet(workspaceIDs), nil
}

// IsManaged treats nil as unrestricted so legacy tests and monolith call sites
// only opt into owner checks when a process profile explicitly provides a set.
func IsManaged(set ManagedWorkspaceSet, workspaceID string) bool {
	if set == nil {
		return true
	}
	return set.Has(workspaceID)
}

// Require returns ErrOwnerMismatch when the workspace is outside the managed set.
func Require(set ManagedWorkspaceSet, workspaceID string) error {
	trimmed := strings.TrimSpace(workspaceID)
	if trimmed == "" {
		return OwnerMismatchError{WorkspaceID: workspaceID}
	}
	if IsManaged(set, trimmed) {
		return nil
	}
	return OwnerMismatchError{WorkspaceID: trimmed}
}

type allSet struct{}

func (allSet) Has(workspaceID string) bool {
	return strings.TrimSpace(workspaceID) != ""
}

type staticSet struct {
	workspaceIDs map[string]struct{}
}

func (set staticSet) Has(workspaceID string) bool {
	_, ok := set.workspaceIDs[strings.TrimSpace(workspaceID)]
	return ok
}
