package connector_inbound

import (
	"context"
	"errors"
	"strings"

	connector_inbound_routing "github.com/pingcap-inc/nexuscrm-connectors/features/connector/inboundrouting"
	flowcommandv2 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv2"
)

// RuntimeCommandHandoffSupportsInvocationMode is the single composition-level
// capability contract shared by startup admission and the claimed WorkItem
// guard. Row-specific proof that a settled operation is a source-projection
// WorkItem remains the worker's responsibility.
func RuntimeCommandHandoffSupportsInvocationMode(invocationMode string) bool {
	switch invocationMode {
	case connector_inbound_routing.InvocationModePerEvent,
		connector_inbound_routing.InvocationModeLatestBySourceFullRecordPreservePayloadSettled:
		return true
	default:
		return false
	}
}

// RuntimeCommandHandoffScope binds one exact WorkItem lease and immutable
// Connector Event route to a private Runtime command handoff. LeaseToken is
// Integration-owned execution authority: implementations must use it only to
// freeze the handoff request and must never place it on the neutral wire or in
// logs.
type RuntimeCommandHandoffScope struct {
	WorkItemID      string
	EventID         string
	LeaseToken      string
	Namespace       string
	InstalledPackID string
	EventKind       string
	SourcePartition string
	RouteVersion    uint64
}

func (s RuntimeCommandHandoffScope) normalized() RuntimeCommandHandoffScope {
	s.WorkItemID = strings.TrimSpace(s.WorkItemID)
	s.EventID = strings.TrimSpace(s.EventID)
	s.LeaseToken = strings.TrimSpace(s.LeaseToken)
	s.Namespace = strings.TrimSpace(s.Namespace)
	s.InstalledPackID = strings.TrimSpace(s.InstalledPackID)
	s.EventKind = strings.ToLower(strings.TrimSpace(s.EventKind))
	s.SourcePartition = strings.TrimSpace(s.SourcePartition)
	return s
}

// CanonicalRuntimeCommandHandoffScope normalizes and validates the exact
// Integration-owned execution scope shared by the Worker and receipt store.
func CanonicalRuntimeCommandHandoffScope(
	s RuntimeCommandHandoffScope,
) (RuntimeCommandHandoffScope, error) {
	s = s.normalized()
	if s.WorkItemID == "" || s.EventID == "" || s.LeaseToken == "" ||
		s.Namespace == "" || s.InstalledPackID == "" || s.EventKind == "" ||
		s.RouteVersion == 0 {
		return RuntimeCommandHandoffScope{}, errors.New(
			"connector Runtime command handoff scope is incomplete",
		)
	}
	return s, nil
}

func (s RuntimeCommandHandoffScope) validate() error {
	_, err := CanonicalRuntimeCommandHandoffScope(s)
	return err
}

// RuntimeCommandHandoffRequest is the explicit Integration-to-Runtime owner
// transfer. DefinitionRef and DefinitionVersion are cross-checked against the
// same authorized IntegrationBindingView as the route generation; Inputs are
// canonicalized and frozen before the ownership-changing AcceptCommand call.
type RuntimeCommandHandoffRequest struct {
	Scope             RuntimeCommandHandoffScope
	DefinitionRef     string
	DefinitionVersion string
	Inputs            map[string]any
	DependencySource  flowcommandv2.DependencySourceScope
}

// RuntimeCommandHandoff accepts one command into Runtime's durable inbox. A
// nil error means ownership has transferred; it does not claim synchronous
// Flow completion.
type RuntimeCommandHandoff interface {
	// VerifyReady proves the Integration receipt store is published before the
	// Worker can claim any durable work.
	VerifyReady(context.Context) error
	// ResumePreparedRuntimeCommand retries the exact stored bytes, when one
	// exists, before the Worker performs another provider read. found=false is
	// the only signal that first-time preparation may continue.
	ResumePreparedRuntimeCommand(
		context.Context,
		RuntimeCommandHandoffScope,
	) (found bool, err error)
	AcceptRuntimeCommand(context.Context, RuntimeCommandHandoffRequest) error
}

// RuntimeCommandHandoffFailure classifies whether the Integration WorkItem may
// retry without consuming its genuine-failure budget.
type RuntimeCommandHandoffFailure struct {
	Cause     error
	Retriable bool
}

func (e *RuntimeCommandHandoffFailure) Error() string {
	if e == nil || e.Cause == nil {
		return "connector Runtime command handoff failed"
	}
	return e.Cause.Error()
}

func (e *RuntimeCommandHandoffFailure) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

func runtimeCommandHandoffIsRetriable(err error) bool {
	var failure *RuntimeCommandHandoffFailure
	return errors.As(err, &failure) && failure.Retriable
}
