package service

import (
	"errors"
	"fmt"
	"sort"

	edgenode "iop/apps/edge/internal/node"
	"iop/packages/go/config"
)

var (
	ErrSingleRequestWorkspaceUnavailable = errors.New("single-request workspace: approved workspace is unavailable")
	ErrSingleRequestWorkspaceStale       = errors.New("single-request workspace: ready connection changed before executor handoff")
)

// bindSingleRequestWorkspace compiles the caller's already-authorized opaque
// workspace ref into a request-stable capability projection. It deliberately
// resolves only the configured catalog owner and then only that exact node id's
// ready owner; aliases, caller Node/path input, and implicit registry selection
// are not admission paths.
func bindSingleRequestWorkspace(binding *SingleRequestBinding, store *edgenode.NodeStore, registry *edgenode.Registry) (*SingleRequestBinding, error) {
	base, err := cloneValidatedSingleRequestBinding(binding)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrSingleRequestInvalidBinding, err)
	}
	if store == nil || registry == nil {
		return nil, ErrSingleRequestWorkspaceUnavailable
	}

	owner, workspace, err := store.ResolveWorkspace(base.WorkspaceRef)
	if err != nil || owner == nil || owner.ID == "" || workspace.Ref != base.WorkspaceRef {
		return nil, fmt.Errorf("%w: workspace ref %q", ErrSingleRequestWorkspaceUnavailable, base.WorkspaceRef)
	}
	ready, ok := registry.ReadyOwnerSnapshot(owner.ID)
	if !ok || ready == nil || ready.NodeID != owner.ID || ready.ConnectionGeneration == 0 {
		return nil, fmt.Errorf("%w: node %q is not ready", ErrSingleRequestWorkspaceUnavailable, owner.ID)
	}

	workspaceBinding, err := compileSingleRequestWorkspaceBinding(workspace, ready)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrSingleRequestWorkspaceUnavailable, err)
	}
	base.Workspace = workspaceBinding
	if err := applySingleRequestWorkspaceEffectiveLimits(base); err != nil {
		return nil, fmt.Errorf("%w: %v", ErrSingleRequestWorkspaceUnavailable, err)
	}
	return base, nil
}

func compileSingleRequestWorkspaceBinding(workspace config.WorkspaceDefinition, ready *edgenode.NodeEntry) (*SingleRequestWorkspaceBinding, error) {
	if ready == nil || workspace.Ref == "" || ready.NodeID == "" || ready.ConnectionGeneration == 0 {
		return nil, errSingleRequestWorkspaceMalformed
	}
	operations := make([]string, 0, len(workspace.Operations))
	commandEnabled := false
	for _, operation := range workspace.Operations {
		switch operation {
		case config.WorkspaceOpRead, config.WorkspaceOpList, config.WorkspaceOpWrite, config.WorkspaceOpDelete, config.WorkspaceOpCommand:
			operations = append(operations, string(operation))
			commandEnabled = commandEnabled || operation == config.WorkspaceOpCommand
		default:
			return nil, errSingleRequestWorkspaceMalformed
		}
	}
	if len(operations) == 0 || (len(workspace.Commands) > 0 && !commandEnabled) {
		return nil, errSingleRequestWorkspaceMalformed
	}
	sort.Strings(operations)
	if !sortedUniqueNonEmpty(operations) {
		return nil, errSingleRequestWorkspaceMalformed
	}

	commandIDs := make([]string, 0, len(workspace.Commands))
	for _, command := range workspace.Commands {
		if command.ID == "" {
			return nil, errSingleRequestWorkspaceMalformed
		}
		commandIDs = append(commandIDs, command.ID)
	}
	sort.Strings(commandIDs)
	if !sortedUniqueNonEmpty(commandIDs) {
		return nil, errSingleRequestWorkspaceMalformed
	}
	environmentNames := append([]string(nil), workspace.EnvironmentAllowlist...)
	sort.Strings(environmentNames)
	if !sortedUniqueNonEmpty(environmentNames) {
		return nil, errSingleRequestWorkspaceMalformed
	}
	for _, name := range environmentNames {
		if !validEnvironmentName(name) {
			return nil, errSingleRequestWorkspaceMalformed
		}
	}
	limits := SingleRequestWorkspaceLimits{
		MaxReadBytes:        workspace.MaxReadBytes,
		MaxWriteBytes:       workspace.MaxWriteBytes,
		MaxOutputBytes:      workspace.MaxOutputBytes,
		MaxCommandTimeoutMS: workspace.MaxCommandTimeoutMS,
	}
	if err := validateSingleRequestWorkspaceCapabilities(operations, commandIDs, limits); err != nil {
		return nil, errSingleRequestWorkspaceMalformed
	}
	return &SingleRequestWorkspaceBinding{
		Ref:                  workspace.Ref,
		NodeID:               ready.NodeID,
		ConnectionGeneration: ready.ConnectionGeneration,
		OperationIDs:         operations,
		CommandIDs:           commandIDs,
		EnvironmentNames:     environmentNames,
		Limits:               limits,
	}, nil
}

func applySingleRequestWorkspaceEffectiveLimits(binding *SingleRequestBinding) error {
	if binding == nil || binding.Workspace == nil {
		return errSingleRequestWorkspaceMalformed
	}
	if binding.Limits.MaxOutputBytes < binding.Workspace.Limits.MaxOutputBytes {
		binding.Workspace.Limits.MaxOutputBytes = binding.Limits.MaxOutputBytes
	}
	if binding.Limits.StageTimeoutMS < binding.Workspace.Limits.MaxCommandTimeoutMS {
		binding.Workspace.Limits.MaxCommandTimeoutMS = binding.Limits.StageTimeoutMS
	}
	_, err := validateAndCloneWorkspaceBinding(binding.Workspace)
	return err
}
