package service

import (
	"errors"
	"reflect"
	"sort"

	"iop/packages/go/config"
	"iop/packages/go/singlerequesttemplate"
)

var (
	errSingleRequestMissingPublicModel               = errors.New("single-request binding: public model is required")
	errSingleRequestMissingWorkspaceRef              = errors.New("single-request binding: workspace ref is required")
	errSingleRequestMissingPlan                      = errors.New("single-request binding: plan stage model is required")
	errSingleRequestMissingWork                      = errors.New("single-request binding: work stage model is required")
	errSingleRequestMissingReview                    = errors.New("single-request binding: review stage model is required")
	errSingleRequestLimitTooLow                      = errors.New("single-request binding: limit field must be >= 1")
	errSingleRequestLimitTooHigh                     = errors.New("single-request binding: limit field exceeds maximum")
	errSingleRequestStageTimeoutExceedsWallClock     = errors.New("single-request binding: stage timeout must not exceed wall clock")
	errSingleRequestWorkspaceMalformed               = errors.New("single-request workspace binding: malformed")
	errSingleRequestDispatchMissingManaged           = errors.New("single-request dispatch binding: managed flag is required")
	errSingleRequestDispatchMissingPrincipalRef      = errors.New("single-request dispatch binding: principal ref is required")
	errSingleRequestDispatchMissingModelGroupKey     = errors.New("single-request dispatch binding: model group key is required")
	errSingleRequestDispatchMissingRouteID           = errors.New("single-request dispatch binding: route id is required")
	errSingleRequestDispatchMissingProfileID         = errors.New("single-request dispatch binding: profile id is required")
	errSingleRequestDispatchMissingCredentialRef     = errors.New("single-request dispatch binding: credential slot ref is required")
	errSingleRequestDispatchCredentialRevisionTooLow = errors.New("single-request dispatch binding: credential revision must be positive")
	errSingleRequestDispatchNotCloned                = errors.New("single-request dispatch binding: not a cloned binding")
)

// SingleRequestBinding is the surface-neutral, endpoint-agnostic immutable
// admission value compiled at request start. It freezes the public identity,
// the canonical plan/work/review stage bindings, the opaque workspace
// capability reference, and the absolute resource caps so that later runtime
// mutation or refresh cannot alter an admitted request's authorized shape.
//
// The service package owns this type. The OpenAI/Anthropic surface reads it
// only to echo the public identity and to enforce the frozen limits; the
// private route/provider/credential/endpoint details never cross this boundary.
type SingleRequestBinding struct {
	// PublicModel is the caller-requested public model identity. It is echoed
	// in responses and used for metric labels only; it is never a credential,
	// slot, or provider resource identity.
	PublicModel string

	// WorkspaceRef is an opaque workspace capability reference. It is never
	// exposed as a raw path, credential, Node id, or endpoint.
	WorkspaceRef string

	// Plan is the plan-stage binding. It must reference a canonical model
	// authorized for the authenticated principal.
	Plan SingleRequestStageBinding

	// Work is the work-stage binding. It must reference a canonical model
	// authorized for the authenticated principal.
	Work SingleRequestStageBinding

	// Review is the review-stage binding. It must reference a canonical model
	// authorized for the authenticated principal.
	Review SingleRequestStageBinding

	// Limits declares absolute resource caps for the fixed single-request
	// execution. Every field is in [1, cap] and stage_timeout_ms must not
	// exceed wall_clock_ms.
	Limits SingleRequestLimits

	// Templates contains the frozen effective Plan and Review Markdown templates.
	Templates SingleRequestTemplateBinding

	// Workspace is populated only by Service workspace admission. It contains
	// the request-stable, coordinator-safe capability projection; in
	// particular it intentionally excludes roots, command templates, and
	// environment values.
	Workspace *SingleRequestWorkspaceBinding
}

// SingleRequestTemplateBinding carries the content-only frozen effective Plan
// and Review templates snapshot compiled at admission time.
type SingleRequestTemplateBinding struct {
	Plan   string
	Review string
}

// SingleRequestWorkspaceBinding is the coordinator-safe result of one exact
// workspace capability admission. It freezes a configured Node id and its
// dispatch-ready connection generation together with the closed operation and
// command identifiers and effective maxima. It contains no filesystem root,
// executable, fixed arguments, or environment value.
type SingleRequestWorkspaceBinding struct {
	Ref                  string
	NodeID               string
	ConnectionGeneration uint64
	OperationIDs         []string
	CommandIDs           []string
	EnvironmentNames     []string
	Limits               SingleRequestWorkspaceLimits
}

// SingleRequestWorkspaceLimits are the maxima applicable to the admitted
// workspace. Output and command timeout values are the lower workspace/preset
// bound; read and write are workspace-only bounds.
type SingleRequestWorkspaceLimits struct {
	MaxReadBytes        int
	MaxWriteBytes       int
	MaxOutputBytes      int
	MaxCommandTimeoutMS int
}

// SingleRequestStageDispatchBinding is the service-owned, secret-free managed
// route snapshot copied at stage admission. It freezes the identity, revision,
// and capability facts required to reconstruct a request-local candidate
// predicate and CredentialBinding without retaining projection maps, closures
// over refreshable state, secrets, endpoints, or Node selection.
//
// Every field is validated at admission: PrincipalRef, ModelGroupKey, RouteID,
// ProfileID, CredentialSlotRef, CredentialRevision, and RouteRevision must be
// present. CredentialRevision must be positive. RouteRevision may be zero,
// which is the Control Plane's valid initial revision for a newly created
// route. The
// ManagedPredicate is deep-copied into a stable closure so a later catalog or
// policy refresh cannot alter an admitted binding's selection policy.
type SingleRequestStageDispatchBinding struct {
	// Managed is true when the stage resolved through a managed principal
	// resolution. False values cannot back a single-request admission.
	Managed bool
	// ModelGroupKey is the canonical model group reference for this stage.
	ModelGroupKey string
	// RouteID is the authenticated principal route identifier.
	RouteID string
	// ProfileID is the protocol profile identifier attached to the route.
	ProfileID string
	// CredentialSlotRef is the authenticated credential slot reference.
	CredentialSlotRef string
	// CredentialRevision is the credential slot revision at admission time.
	CredentialRevision uint64
	// RouteRevision is the route revision at admission time.
	RouteRevision uint64
	// PrincipalRef is the authenticated principal reference.
	PrincipalRef string
	// ProjectionGeneration is the auth projection generation at admission time.
	ProjectionGeneration uint64
	// ProviderID is the resolved provider identifier.
	ProviderID string
	// UpstreamModel is the resolved upstream model identifier.
	UpstreamModel string
	// TimeoutSec, MaxQueue, and QueueTimeoutMS are the stage's frozen dispatch
	// budget values copied from the resolved route. Zero preserves the default.
	TimeoutSec     int
	MaxQueue       int
	QueueTimeoutMS int
	// CandidatePredicate is a deep-copied, request-local admission predicate
	// reconstructed from the frozen facts. It is nil when no request-local
	// predicate was attached to the resolved route.
	CandidatePredicate ProviderPoolCandidatePredicate
}

// CredentialBindingSnapshot returns a secret-free CredentialBinding derived
// from the frozen dispatch facts. The returned value is independent of the
// source: mutating the snapshot never affects the binding.
func (d *SingleRequestStageDispatchBinding) CredentialBindingSnapshot() *CredentialBinding {
	if d == nil || !d.Managed {
		return nil
	}
	return &CredentialBinding{
		PrincipalRef:         d.PrincipalRef,
		CredentialSlotRef:    d.CredentialSlotRef,
		RouteID:              d.RouteID,
		ProfileID:            d.ProfileID,
		CredentialRevision:   d.CredentialRevision,
		RouteRevision:        d.RouteRevision,
		ProjectionGeneration: d.ProjectionGeneration,
	}
}

// Clone returns an independent snapshot of the dispatch binding. A nil receiver
// returns nil.
func (d *SingleRequestStageDispatchBinding) Clone() *SingleRequestStageDispatchBinding {
	if d == nil {
		return nil
	}
	clone := *d
	if d.CandidatePredicate != nil {
		predicate := d.CandidatePredicate
		clone.CandidatePredicate = func(c ProviderPoolCandidate) bool { return predicate(c) }
	}
	return &clone
}

// SingleRequestStageBinding is one frozen stage binding: a canonical model
// reference, an optional stage-level option snapshot, and the secret-free
// managed route snapshot required for provider-pool dispatch. Options and
// Dispatch are stored as deep copies so caller mutation cannot alter an
// admitted binding.
type SingleRequestStageBinding struct {
	// Model is the canonical model reference for this stage.
	Model string
	// Options is a deep copy of the stage-level model options. nil means no
	// options; a non-nil empty map means options were declared but empty.
	Options map[string]any
	// Dispatch is the frozen managed route snapshot for this stage. nil means
	// the stage resolved without managed-route facts (e.g. unmanaged preset).
	Dispatch *SingleRequestStageDispatchBinding
}

// SingleRequestLimits carries server-owned absolute resource caps.
// Every field must be in [1, cap] and timeout_ms must not exceed wall_clock_ms.
type SingleRequestLimits struct {
	// WallClockMS is the total wall-clock budget for the request in milliseconds.
	WallClockMS int
	// StageTimeoutMS is the per-stage timeout in milliseconds.
	StageTimeoutMS int
	// MaxToolIterations is the maximum tool iterations allowed per stage.
	MaxToolIterations int
	// MaxOutputBytes is the maximum output bytes allowed per stage.
	MaxOutputBytes int
}

// NewSingleRequestBinding constructs a validated, defensive-copy admission
// value. It rejects incomplete stage sets (any of plan/work/review is empty)
// and validates that every limit is in [1, cap] with stage_timeout_ms <=
// wall_clock_ms. On any violation it returns an error and a zero binding so
// callers cannot retain a partially-constructed value.
//
// Dispatch bindings, when non-nil, are validated for required identity and
// revision fields and deep-cloned so the caller cannot alter an admitted
// binding through the original reference.
func NewSingleRequestBinding(publicModel, workspaceRef string, plan, work, review SingleRequestStageBinding, limits SingleRequestLimits) (*SingleRequestBinding, error) {
	return NewSingleRequestBindingWithTemplates(publicModel, workspaceRef, plan, work, review, limits, SingleRequestTemplateBinding{
		Plan:   singlerequesttemplate.DefaultPlanTemplate,
		Review: singlerequesttemplate.DefaultReviewTemplate,
	})
}

// NewSingleRequestBindingWithTemplates constructs a validated, defensive-copy admission
// value including explicit effective Plan and Review templates.
func NewSingleRequestBindingWithTemplates(publicModel, workspaceRef string, plan, work, review SingleRequestStageBinding, limits SingleRequestLimits, templates SingleRequestTemplateBinding) (*SingleRequestBinding, error) {
	if publicModel == "" {
		return nil, errSingleRequestMissingPublicModel
	}
	if workspaceRef == "" {
		return nil, errSingleRequestMissingWorkspaceRef
	}
	if plan.Model == "" {
		return nil, errSingleRequestMissingPlan
	}
	if work.Model == "" {
		return nil, errSingleRequestMissingWork
	}
	if review.Model == "" {
		return nil, errSingleRequestMissingReview
	}

	if err := singlerequesttemplate.ValidatePlanTemplate(templates.Plan); err != nil {
		return nil, err
	}
	if err := singlerequesttemplate.ValidateReviewTemplate(templates.Review); err != nil {
		return nil, err
	}

	limits, err := validateSingleRequestLimits(limits)
	if err != nil {
		return nil, err
	}

	planDispatch, err := validateAndCloneDispatchBinding(plan.Dispatch)
	if err != nil {
		return nil, err
	}
	workDispatch, err := validateAndCloneDispatchBinding(work.Dispatch)
	if err != nil {
		return nil, err
	}
	reviewDispatch, err := validateAndCloneDispatchBinding(review.Dispatch)
	if err != nil {
		return nil, err
	}

	planCopy := SingleRequestStageBinding{Model: plan.Model, Options: cloneMapStringAny(plan.Options), Dispatch: planDispatch}
	workCopy := SingleRequestStageBinding{Model: work.Model, Options: cloneMapStringAny(work.Options), Dispatch: workDispatch}
	reviewCopy := SingleRequestStageBinding{Model: review.Model, Options: cloneMapStringAny(review.Options), Dispatch: reviewDispatch}

	return &SingleRequestBinding{
		PublicModel:  publicModel,
		WorkspaceRef: workspaceRef,
		Plan:         planCopy,
		Work:         workCopy,
		Review:       reviewCopy,
		Limits:       limits,
		Templates: SingleRequestTemplateBinding{
			Plan:   templates.Plan,
			Review: templates.Review,
		},
	}, nil
}

// Clone returns a deep copy of a SingleRequestBinding. The returned value is
// independent of the source: mutating the copy's options maps or the source's
// options maps never affects the other. A nil receiver returns nil.
func (b *SingleRequestBinding) Clone() *SingleRequestBinding {
	if b == nil {
		return nil
	}
	return &SingleRequestBinding{
		PublicModel:  b.PublicModel,
		WorkspaceRef: b.WorkspaceRef,
		Plan: SingleRequestStageBinding{
			Model:    b.Plan.Model,
			Options:  cloneMapStringAny(b.Plan.Options),
			Dispatch: b.Plan.Dispatch.Clone(),
		},
		Work: SingleRequestStageBinding{
			Model:    b.Work.Model,
			Options:  cloneMapStringAny(b.Work.Options),
			Dispatch: b.Work.Dispatch.Clone(),
		},
		Review: SingleRequestStageBinding{
			Model:    b.Review.Model,
			Options:  cloneMapStringAny(b.Review.Options),
			Dispatch: b.Review.Dispatch.Clone(),
		},
		Limits:    b.Limits,
		Templates: b.Templates,
		Workspace: b.Workspace.Clone(),
	}
}

// Clone returns an independent workspace admission snapshot. A nil receiver
// returns nil.
func (b *SingleRequestWorkspaceBinding) Clone() *SingleRequestWorkspaceBinding {
	if b == nil {
		return nil
	}
	return &SingleRequestWorkspaceBinding{
		Ref:                  b.Ref,
		NodeID:               b.NodeID,
		ConnectionGeneration: b.ConnectionGeneration,
		OperationIDs:         append([]string(nil), b.OperationIDs...),
		CommandIDs:           append([]string(nil), b.CommandIDs...),
		EnvironmentNames:     append([]string(nil), b.EnvironmentNames...),
		Limits:               b.Limits,
	}
}

func cloneValidatedSingleRequestBinding(binding *SingleRequestBinding) (*SingleRequestBinding, error) {
	if binding == nil {
		return nil, errSingleRequestWorkspaceMalformed
	}
	base, err := NewSingleRequestBindingWithTemplates(
		binding.PublicModel,
		binding.WorkspaceRef,
		binding.Plan,
		binding.Work,
		binding.Review,
		binding.Limits,
		binding.Templates,
	)
	if err != nil {
		return nil, err
	}
	if binding.Workspace == nil {
		return base, nil
	}
	workspace, err := validateAndCloneWorkspaceBinding(binding.Workspace)
	if err != nil {
		return nil, err
	}
	if workspace.Ref != base.WorkspaceRef {
		return nil, errSingleRequestWorkspaceMalformed
	}
	base.Workspace = workspace
	return base, nil
}

func validateAndCloneWorkspaceBinding(binding *SingleRequestWorkspaceBinding) (*SingleRequestWorkspaceBinding, error) {
	if binding == nil || binding.Ref == "" || binding.NodeID == "" || binding.ConnectionGeneration == 0 {
		return nil, errSingleRequestWorkspaceMalformed
	}
	if len(binding.OperationIDs) == 0 {
		return nil, errSingleRequestWorkspaceMalformed
	}
	if !sortedUniqueNonEmpty(binding.OperationIDs) || !sortedUniqueNonEmpty(binding.CommandIDs) ||
		!sortedUniqueNonEmpty(binding.EnvironmentNames) {
		return nil, errSingleRequestWorkspaceMalformed
	}
	for _, name := range binding.EnvironmentNames {
		if !validEnvironmentName(name) {
			return nil, errSingleRequestWorkspaceMalformed
		}
	}
	if err := validateSingleRequestWorkspaceCapabilities(binding.OperationIDs, binding.CommandIDs, binding.Limits); err != nil {
		return nil, err
	}
	return binding.Clone(), nil
}

// validateSingleRequestWorkspaceCapabilities mirrors the catalog's
// operation-aware bounds. Disabled operations retain a zero limit rather than
// being rejected by the coordinator-safe projection.
func validateSingleRequestWorkspaceCapabilities(operationIDs, commandIDs []string, limits SingleRequestWorkspaceLimits) error {
	operations := make(map[string]bool, len(operationIDs))
	for _, operation := range operationIDs {
		switch operation {
		case string(config.WorkspaceOpRead), string(config.WorkspaceOpList), string(config.WorkspaceOpWrite), string(config.WorkspaceOpDelete), string(config.WorkspaceOpCommand):
			operations[operation] = true
		default:
			return errSingleRequestWorkspaceMalformed
		}
	}
	if operations[string(config.WorkspaceOpRead)] && limits.MaxReadBytes < 1 {
		return errSingleRequestWorkspaceMalformed
	}
	if operations[string(config.WorkspaceOpWrite)] && limits.MaxWriteBytes < 1 {
		return errSingleRequestWorkspaceMalformed
	}
	if (operations[string(config.WorkspaceOpList)] || operations[string(config.WorkspaceOpCommand)]) && limits.MaxOutputBytes < 1 {
		return errSingleRequestWorkspaceMalformed
	}
	if operations[string(config.WorkspaceOpCommand)] {
		if limits.MaxCommandTimeoutMS < 1 || len(commandIDs) == 0 {
			return errSingleRequestWorkspaceMalformed
		}
	} else if len(commandIDs) != 0 {
		return errSingleRequestWorkspaceMalformed
	}
	return nil
}

func sortedUniqueNonEmpty(values []string) bool {
	if len(values) == 0 {
		return true
	}
	if !sort.StringsAreSorted(values) {
		return false
	}
	for i, value := range values {
		if value == "" || (i > 0 && values[i-1] == value) {
			return false
		}
	}
	return true
}

func validateSingleRequestLimits(l SingleRequestLimits) (SingleRequestLimits, error) {
	if l.WallClockMS < 1 {
		return l, errSingleRequestLimitTooLow
	}
	if l.WallClockMS > config.MaxSingleRequestWallClockMS {
		return l, errSingleRequestLimitTooHigh
	}
	if l.StageTimeoutMS < 1 {
		return l, errSingleRequestLimitTooLow
	}
	if l.StageTimeoutMS > config.MaxSingleRequestStageTimeoutMS {
		return l, errSingleRequestLimitTooHigh
	}
	if l.StageTimeoutMS > l.WallClockMS {
		return l, errSingleRequestStageTimeoutExceedsWallClock
	}
	if l.MaxToolIterations < 1 {
		return l, errSingleRequestLimitTooLow
	}
	if l.MaxToolIterations > config.MaxSingleRequestToolIterations {
		return l, errSingleRequestLimitTooHigh
	}
	if l.MaxOutputBytes < 1 {
		return l, errSingleRequestLimitTooLow
	}
	if l.MaxOutputBytes > config.MaxSingleRequestOutputBytes {
		return l, errSingleRequestLimitTooHigh
	}
	return l, nil
}

// cloneMapStringAny deep-copies a map[string]any so the caller cannot mutate
// the original through the returned reference. Nested maps, slices, arrays, and
// pointers are copied recursively; nil input returns nil.
func cloneMapStringAny(m map[string]any) map[string]any {
	if m == nil {
		return nil
	}
	out := make(map[string]any, len(m))
	for k, v := range m {
		out[k] = cloneValueAny(v)
	}
	return out
}

// cloneValueAny returns a deep copy of an arbitrary option value so nested
// reference types cannot be mutated through the returned value. Scalar values
// are returned unchanged.
func cloneValueAny(v any) any {
	if v == nil {
		return nil
	}
	return cloneReflectValue(reflect.ValueOf(v)).Interface()
}

// cloneReflectValue recursively copies pointer, interface, map, slice, and array
// values, leaving scalars unchanged. It preserves the concrete collection types
// so callers receive the same option shape without retaining mutable references.
func cloneReflectValue(rv reflect.Value) reflect.Value {
	if !rv.IsValid() {
		return rv
	}
	switch rv.Kind() {
	case reflect.Pointer:
		if rv.IsNil() {
			return reflect.Zero(rv.Type())
		}
		elemCopy := cloneReflectValue(rv.Elem())
		ptr := reflect.New(rv.Type().Elem())
		ptr.Elem().Set(elemCopy)
		return ptr
	case reflect.Interface:
		if rv.IsNil() {
			return reflect.Zero(rv.Type())
		}
		return cloneReflectValue(rv.Elem())
	case reflect.Map:
		if rv.IsNil() {
			return reflect.Zero(rv.Type())
		}
		outMap := reflect.MakeMapWithSize(rv.Type(), rv.Len())
		iter := rv.MapRange()
		for iter.Next() {
			kCopy := cloneReflectValue(iter.Key())
			vCopy := cloneReflectValue(iter.Value())
			outMap.SetMapIndex(kCopy, vCopy)
		}
		return outMap
	case reflect.Slice:
		if rv.IsNil() {
			return reflect.Zero(rv.Type())
		}
		outSlice := reflect.MakeSlice(rv.Type(), rv.Len(), rv.Cap())
		for i := 0; i < rv.Len(); i++ {
			elemCopy := cloneReflectValue(rv.Index(i))
			outSlice.Index(i).Set(elemCopy)
		}
		return outSlice
	case reflect.Array:
		outArray := reflect.New(rv.Type()).Elem()
		for i := 0; i < rv.Len(); i++ {
			elemCopy := cloneReflectValue(rv.Index(i))
			outArray.Index(i).Set(elemCopy)
		}
		return outArray
	default:
		return rv
	}
}

// validateAndCloneDispatchBinding validates the required identity and revision
// fields of a dispatch binding, deep-clones it, and reconstructs the
// request-local candidate predicate. It returns errSingleRequestDispatchNotCloned
// when the binding was already cloned (defensive: prevents double-clone).
func validateAndCloneDispatchBinding(d *SingleRequestStageDispatchBinding) (*SingleRequestStageDispatchBinding, error) {
	if d == nil {
		return nil, nil
	}
	if d.Managed == false {
		return nil, errSingleRequestDispatchMissingManaged
	}
	if d.PrincipalRef == "" {
		return nil, errSingleRequestDispatchMissingPrincipalRef
	}
	if d.ModelGroupKey == "" {
		return nil, errSingleRequestDispatchMissingModelGroupKey
	}
	if d.RouteID == "" {
		return nil, errSingleRequestDispatchMissingRouteID
	}
	if d.ProfileID == "" {
		return nil, errSingleRequestDispatchMissingProfileID
	}
	if d.CredentialSlotRef == "" {
		return nil, errSingleRequestDispatchMissingCredentialRef
	}
	if d.CredentialRevision < 1 {
		return nil, errSingleRequestDispatchCredentialRevisionTooLow
	}
	return d.Clone(), nil
}
