package connector_inbound

import (
	"context"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/models"
)

// FlowInvoker is the worker's view of the AutoLaunched Flow runtime.
// It is a narrow interface so the worker package avoids a direct import of the
// automation package (which would create a dependency cycle). Production
// wiring lives outside this package — a thin adapter struct in the application
// services layer delegates to automation.FlowExecutor.
type FlowInvoker interface {
	// ExecuteFlow invokes an AutoLaunched Flow by its DeveloperName with the
	// given inputs and user session. Returns a non-nil result on any
	// completion — callers must check FlowResult.IsSuccess.
	ExecuteFlow(ctx context.Context, flowDeveloperName string, inputs map[string]any, user *models.UserSession) (*FlowResult, error)

	// ExecuteFlowVersion executes a cross-pack companion flow by its exact
	// (frozen) Flow.Id version. flowDeveloperName is used only for logging and
	// audit; the actual execution is keyed on companionFlowVersionID. The worker
	// stamps the companion auth marker onto ctx and performs the §5.2 incarnation
	// re-check BEFORE calling this method — the implementation must NOT re-stamp
	// the marker and must NOT re-verify the companion incarnation.
	ExecuteFlowVersion(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]any, user *models.UserSession) (*FlowResult, error)

	// SystemUser returns a synthetic UserSession used to drive background-
	// worker Flow invocations. The returned session must have enough
	// permissions for the flows being invoked.
	SystemUser(ctx context.Context) *models.UserSession
}

// FlowOutcomeInvoker is the optional direct-Go extension used by connector
// workers that preserve execution identity, typed failure causes, and
// persistent dependency requests. Keeping it separate preserves source
// compatibility for legacy adapters.
type FlowOutcomeInvoker interface {
	ExecuteFlowOutcome(ctx context.Context, flowDeveloperName string, inputs map[string]any, user *models.UserSession) (*FlowOutcome, error)
	ExecuteFlowVersionOutcome(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]any, user *models.UserSession) (*FlowOutcome, error)
}

// FlowDependencyOutcomeInvoker is an additive extension for a Flow dependency
// request. It intentionally does not change FlowOutcome: that exported type is
// consumed with unkeyed struct literals by pinned downstream packages.
type FlowDependencyOutcomeInvoker interface {
	ExecuteFlowDependencyOutcome(ctx context.Context, flowDeveloperName string, inputs map[string]any, user *models.UserSession) (*FlowDependencyOutcome, error)
	ExecuteFlowVersionDependencyOutcome(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]any, user *models.UserSession) (*FlowDependencyOutcome, error)
}

// FlowResult is the worker-visible outcome of a Flow execution. It is a
// narrow projection of [models.FlowExecutionResult] so the worker does not
// import the full models surface for flow-outcome handling.
type FlowResult struct {
	// IsSuccess is true when the flow completed without fault.
	IsSuccess bool

	// IsRetriable signals that the caller should treat the outcome as a
	// retriable failure and re-attempt the event later rather than finalize it.
	// The worker honors it on both result shapes:
	//   - IsSuccess=true, IsRetriable=true: the worker calls MarkFailed instead
	//     of MarkProcessed, leaving the event in the queue for retry after
	//     prerequisites land (the original #2128 out-of-order-dependency case).
	//   - IsSuccess=false, IsRetriable=true: the shape a FlowRetriableFault
	//     produces. The worker re-drives the event via ReleaseClaimForRetry (a
	//     backed-off release that does NOT increment RetryCount) instead of
	//     MarkFailed, so a transient concurrent-loser self-heals without burning
	//     the dead-letter budget before the in-flight winner finalizes (OUT-1).
	IsRetriable bool

	// FirstError is the first error from the flow's Errors list, or nil.
	// Used to build failure log messages.
	FirstError *FlowError

	// OutputValues contains Flow output variables keyed by API name. The
	// single-event worker path ignores this; batch-collection routes use a
	// configured output collection to finalize each event independently.
	OutputValues map[string]any
}

// FlowOutcome is the connector-aware direct-Go envelope. Preserve its field
// shape for source compatibility with downstream unkeyed struct literals.
type FlowOutcome struct {
	Result      *FlowResult
	ExecutionID string
	Cause       *models.FlowExecutionCause
}

// FlowDependencyOutcome carries the additive, non-persistable request until
// the worker binds immutable Event scope.
type FlowDependencyOutcome struct {
	*FlowOutcome
	Dependency *models.FlowExecutionDependencyRequest
}

// FlowError is a narrow projection of [models.FlowExecutionError].
type FlowError struct {
	StatusCode string
	Message    string
}

// FlowResultFromModel converts a [models.FlowExecutionResult] to [FlowResult].
// The production adapter in the application services layer calls this helper
// so the worker itself never imports models directly for flow-result handling.
func FlowResultFromModel(r *models.FlowExecutionResult) *FlowResult {
	if r == nil {
		return &FlowResult{IsSuccess: false}
	}
	res := &FlowResult{IsSuccess: r.IsSuccess, IsRetriable: r.IsRetriable, OutputValues: r.OutputValues}
	if len(r.Errors) > 0 {
		res.FirstError = &FlowError{
			StatusCode: r.Errors[0].StatusCode,
			Message:    r.Errors[0].Message,
		}
	}
	return res
}

// FlowOutcomeFromModel converts the typed contract envelope without placing
// connector-only evidence back onto the source-compatible FlowResult struct.
func FlowOutcomeFromModel(outcome *models.FlowExecutionTypedOutcome) *FlowOutcome {
	if outcome == nil {
		return &FlowOutcome{Result: FlowResultFromModel(nil)}
	}
	return &FlowOutcome{
		Result:      FlowResultFromModel(outcome.Result),
		ExecutionID: outcome.ExecutionID,
		Cause:       outcome.Cause,
	}
}

func FlowDependencyOutcomeFromModel(outcome *models.FlowExecutionTypedOutcome) *FlowDependencyOutcome {
	if outcome == nil {
		return &FlowDependencyOutcome{FlowOutcome: FlowOutcomeFromModel(nil)}
	}
	return &FlowDependencyOutcome{FlowOutcome: FlowOutcomeFromModel(outcome), Dependency: outcome.Dependency}
}

// failureMessage returns a human-readable failure string from result, suitable
// for use in MarkFailed error messages.
func (r *FlowResult) failureMessage() string {
	if r.FirstError != nil {
		return r.FirstError.StatusCode + ": " + r.FirstError.Message
	}
	return "flow returned failure with no error detail"
}
