package automation

import (
	"context"

	"github.com/nexuscrm/shared/pkg/models"
)

// PersistenceProvider defines the subset of PersistenceService needed by Automation
type PersistenceProvider interface {
	Insert(ctx context.Context, tableName string, record models.SObject, user *models.UserSession) (models.SObject, error)
	PreviewBeforeInsert(ctx context.Context, tableName string, record models.SObject, user *models.UserSession) (models.SObject, error)
	PrelockUpdateParents(ctx context.Context, tableName string, id string, updates models.SObject, user *models.UserSession) error
	Update(ctx context.Context, tableName string, id string, updates models.SObject, user *models.UserSession) error
	PreviewBeforeUpdate(ctx context.Context, tableName string, id string, updates models.SObject, internalStamps models.SObject, user *models.UserSession) (*models.PreparedRecordUpdate, error)
	CommitPreparedUpdate(ctx context.Context, tableName string, id string, prepared *models.PreparedRecordUpdate, extraUpdates models.SObject, user *models.UserSession) error
	Delete(ctx context.Context, tableName string, id string, user *models.UserSession) error
	GetRaw(ctx context.Context, objectName string, id string) (models.SObject, error)
	// GetLock is the checked row-lock escape hatch. New production callers must
	// be added to the records parent-lock inventory; business parent-row locks
	// should use records admission paths instead of this public port.
	GetLock(ctx context.Context, tableName string, id string) (models.SObject, error) // SELECT ... FOR UPDATE
	GetRecordType(ctx context.Context, id string) (string, error)
	// RunInTransaction MUST reuse the outer transaction when `ctx` already
	// carries one. Nested atomic flows (a RunAtomically subflow invoked
	// from another RunAtomically flow) depend on this reentrancy: if the
	// implementation opens a fresh savepoint / independent tx on every
	// call, a subflow's rollback would not propagate to the parent,
	// silently breaking atomic semantics for parent actions that create
	// multiple related records. Production code
	// delegates to TransactionManager.IsTransactionActive → direct call
	// path; test doubles must do the same, or a capability probe must
	// be added here if a future use case needs fresh-savepoint semantics.
	RunInTransaction(ctx context.Context, fn func(context.Context) error) error
	// IsTransactionActive reports whether ctx already carries an open DB
	// transaction. FlowExecutor uses this to fail closed when an atomic
	// flow is invoked under a foreign rollback boundary; only callers that
	// are already inside an atomic flow boundary or a before-save root may
	// safely reuse the active transaction.
	IsTransactionActive(ctx context.Context) bool
	// RunInPessimisticTransaction is required by approval lifecycle paths
	// whose in-transaction ProcessAction FieldUpdates can reach generic record
	// CRUD and rollup locking.
	RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error
	// DetachTx returns a derived context with the active DB transaction removed
	// so that subsequent persistence calls start a fresh independent transaction.
	// Used by AfterSave recordUpdate elements to avoid self-deadlock (TiDB 1205)
	// when updating the triggering record while running inside the outbox transaction.
	DetachTx(ctx context.Context) context.Context
	// RecomputeActiveFormulaProjection re-fetches active-formula values for the
	// given (object, recordID) through the formula view and returns them as a
	// new SObject snapshot. Used by the flow runtime to refresh $Record after
	// an in-flow RecordUpdate so chained Decision/Assignment/FlowFormula reads
	// of dependent active fields see post-update values (issue #1362 HIGH 3).
	//
	// fields restricts the projection to the given active-field API names
	// (nil = all active fields). Field-scoped callers — approval criteria
	// evaluation, whose read authority is capped at the fields the criteria
	// author wrote (#3945) — pass the referenced subset so the strict
	// projection selects and fail-close-validates only those fields;
	// unrelated active-field view drift must not fail a scoped read.
	//
	// Returns (nil, nil) when the projector is not wired or the schema has no
	// (matching) active fields. Returns (nil, error) on view query failure —
	// callers MUST NOT treat error as "no values to overlay" (fail-closed
	// contract).
	RecomputeActiveFormulaProjection(ctx context.Context, user *models.UserSession, objectAPIName, recordID string, fields []string) (models.SObject, error)
}

// externalIDUpsertPersistence is an opt-in capability for Flow RecordCreate
// elements authored with upsertByExternalIdField. Keeping it separate from
// PersistenceProvider preserves existing test doubles and makes unsupported
// runtimes fail closed instead of silently degrading to lookup-then-insert.
type externalIDUpsertPersistence interface {
	UpsertByUniqueExternalID(
		ctx context.Context,
		objectName string,
		fieldName string,
		// fieldValue may be blank when schema defaults or before-save mutation
		// own final identity resolution inside persistence.
		fieldValue string,
		data models.SObject,
		user *models.UserSession,
	) (models.SObject, bool, error)
}

// externalIDGuardedUpsertPersistence is the stronger opt-in capability used
// when a Flow declares ownership guards for external-ID adoption. Keeping the
// capability distinct prevents an older runtime from silently dropping the
// guard while still satisfying plain upsert.
type externalIDGuardedUpsertPersistence interface {
	UpsertByExternalIDWithAdoptionGuards(
		ctx context.Context,
		objectName string,
		fieldName string,
		// fieldValue may be blank when schema defaults or before-save mutation
		// own final identity resolution inside persistence.
		fieldValue string,
		data models.SObject,
		guardFields []string,
		user *models.UserSession,
	) (models.SObject, bool, error)
}

// guardedRecordUpdatePersistence is an opt-in capability for Flow RecordUpdate
// elements that must re-evaluate their authored filters against the row image
// held by the persistence layer's write lock. Keeping this separate from
// PersistenceProvider preserves every existing test double and non-guarded
// RecordUpdate path.
type guardedRecordUpdatePersistence interface {
	UpdateGuarded(
		ctx context.Context,
		tableName string,
		id string,
		updates models.SObject,
		user *models.UserSession,
		guard func(context.Context, models.SObject) (bool, error),
	) (bool, error)
}

// SchemaLookup is the narrow view of MetadataService that Automation needs
// to resolve lookup-field target objects via referenceTo. Allows tests to
// stub out schema without depending on the full MetadataService.
type SchemaLookup interface {
	GetSchema(ctx context.Context, apiName string) *models.ObjectMetadata
}

// FieldMappingApplier resolves _System_ObjectFieldMapping rows for a
// (source, target, actionType) triple and copies fields from source into
// target. The interface hides the underlying table and mapping-row shape
// so the Flow executor does not depend on bootstrap types.
type FieldMappingApplier interface {
	ApplyMappings(ctx context.Context, sourceObject, targetObject, actionType string, source, target map[string]interface{}) error
}
