package connectors

import (
	"context"

	"github.com/pingcap-inc/nexuscrm-connectors/features/connector/inbound"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/constants"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/models"
)

// FlowExecutor is the narrow slice of the backend flow executor used by
// flowAdapter. Keeping it as an interface lets tests inject a stub without
// pulling in the full automation dependency graph.
type FlowExecutor interface {
	ExecuteFlow(ctx context.Context, flowApiName string, inputs map[string]interface{}, user *models.UserSession) (*models.FlowExecutionResult, error)
	ExecuteFlowVersionById(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]interface{}, user *models.UserSession) (*models.FlowExecutionResult, error)
}

// flowOutcomeExecutor is the source-compatible extension implemented by a
// backend that returns direct-Go execution identity, typed causes, and
// dependency requests. Older backends continue through FlowExecutor and
// produce an outcome without this connector-only evidence.
type flowOutcomeExecutor interface {
	ExecuteFlowOutcome(ctx context.Context, flowAPIName string, inputs map[string]interface{}, user *models.UserSession) (*models.FlowExecutionTypedOutcome, error)
	ExecuteFlowVersionByIdOutcome(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]interface{}, user *models.UserSession) (*models.FlowExecutionTypedOutcome, error)
}

// SystemUserLoader loads the synthetic system user session used by background
// connector workers.
type SystemUserLoader interface {
	LoadSystemUserSession(ctx context.Context) (*models.UserSession, error)
}

type flowAdapter struct {
	executor    FlowExecutor
	systemUsers SystemUserLoader
}

func newFlowAdapter(executor FlowExecutor, systemUsers SystemUserLoader) *flowAdapter {
	return &flowAdapter{executor: executor, systemUsers: systemUsers}
}

func (a *flowAdapter) ExecuteFlow(ctx context.Context, flowDeveloperName string, inputs map[string]any, user *models.UserSession) (*connector_inbound.FlowResult, error) {
	// Connector-inbound flows are driven by the background worker, not an end
	// user. Two stamps are required:
	//
	// 1. SystemContext: skips RLS so pack-installed FlowDefinition rows
	//    (OwnerId=NULL) are visible to the system user. Mirrors scheduler_service.go.
	//
	// 2. ConnectorInboundFlowExecution: pack-installed standard flows are
	//    protected — authorizeProtectedStandardFlow in flow_executor.go gates
	//    them. This narrow marker authorizes the worker as a trusted platform
	//    caller WITHOUT forging backend-owned standard-action execution markers,
	//    so standard-action-only actions (previewRecordUpdate,
	//    stampConvertedAnchors) stay locked.
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "connector inbound flow execution")
	// Read the connector namespace stamped by the worker before calling
	// ExecuteFlow. The worker sets ContextKeyConnectorSyncOrigin to
	// w.cfg.Namespace so that outbound echo suppression works; we reuse the
	// same value to bind the same-pack authorization in
	// ConnectorInboundExecutionAuthorizesFlow. If the namespace is absent (e.g.
	// in unit tests that call ExecuteFlow directly), WithConnectorInboundFlowExecution
	// returns ctx unchanged (fail-safe) and the protected flow will be rejected.
	connectorNamespace, _ := ctx.Value(constants.ContextKeyConnectorSyncOrigin).(string)
	ctx = constants.WithConnectorInboundFlowExecution(ctx, flowDeveloperName, connectorNamespace)
	result, err := a.executor.ExecuteFlow(ctx, flowDeveloperName, inputs, user)
	return connector_inbound.FlowResultFromModel(result), err
}

// ExecuteFlowOutcome preserves connector-only typed evidence when the backend
// supports the optional outcome extension. The authorization context is
// identical to ExecuteFlow; legacy backends retain the prior result path.
func (a *flowAdapter) ExecuteFlowOutcome(ctx context.Context, flowDeveloperName string, inputs map[string]any, user *models.UserSession) (*connector_inbound.FlowOutcome, error) {
	outcome, err := a.ExecuteFlowDependencyOutcome(ctx, flowDeveloperName, inputs, user)
	return outcome.FlowOutcome, err
}

// ExecuteFlowDependencyOutcome is the additive dependency extension. Keeping
// it separate preserves the exported FlowOutcome layout for legacy callers.
func (a *flowAdapter) ExecuteFlowDependencyOutcome(ctx context.Context, flowDeveloperName string, inputs map[string]any, user *models.UserSession) (*connector_inbound.FlowDependencyOutcome, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "connector inbound flow execution")
	connectorNamespace, _ := ctx.Value(constants.ContextKeyConnectorSyncOrigin).(string)
	ctx = constants.WithConnectorInboundFlowExecution(ctx, flowDeveloperName, connectorNamespace)
	if executor, ok := a.executor.(flowOutcomeExecutor); ok {
		outcome, err := executor.ExecuteFlowOutcome(ctx, flowDeveloperName, inputs, user)
		return connector_inbound.FlowDependencyOutcomeFromModel(outcome), err
	}
	result, err := a.executor.ExecuteFlow(ctx, flowDeveloperName, inputs, user)
	return &connector_inbound.FlowDependencyOutcome{FlowOutcome: &connector_inbound.FlowOutcome{Result: connector_inbound.FlowResultFromModel(result)}}, err
}

// ExecuteFlowVersion implements FlowInvoker.ExecuteFlowVersion for cross-pack
// connector-inbound events. It:
//  1. Stamps SystemContext and ConnectorInboundFlowExecution (for the flow name)
//     as in ExecuteFlow.
//  2. Delegates to executor.ExecuteFlowVersionById which loads the exact version
//     by id, hydrates definition fields, and calls executeFlowVersion.
//
// The worker stamps WithConnectorInboundCompanionFlowExecution onto ctx BEFORE
// calling this method — this adapter does NOT re-stamp it.
// The worker also performs the §5.2 incarnation re-check BEFORE calling this
// method — this adapter does NOT re-verify the companion incarnation.
func (a *flowAdapter) ExecuteFlowVersion(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]any, user *models.UserSession) (*connector_inbound.FlowResult, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "connector inbound companion flow execution")
	connectorNamespace, _ := ctx.Value(constants.ContextKeyConnectorSyncOrigin).(string)
	ctx = constants.WithConnectorInboundFlowExecution(ctx, flowDeveloperName, connectorNamespace)
	result, err := a.executor.ExecuteFlowVersionById(ctx, flowDeveloperName, companionFlowVersionID, inputs, user)
	return connector_inbound.FlowResultFromModel(result), err
}

// ExecuteFlowVersionOutcome is the companion-version form of
// ExecuteFlowOutcome. The worker remains responsible for stamping and checking
// the exact companion version before this boundary.
func (a *flowAdapter) ExecuteFlowVersionOutcome(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]any, user *models.UserSession) (*connector_inbound.FlowOutcome, error) {
	outcome, err := a.ExecuteFlowVersionDependencyOutcome(ctx, flowDeveloperName, companionFlowVersionID, inputs, user)
	return outcome.FlowOutcome, err
}

func (a *flowAdapter) ExecuteFlowVersionDependencyOutcome(ctx context.Context, flowDeveloperName, companionFlowVersionID string, inputs map[string]any, user *models.UserSession) (*connector_inbound.FlowDependencyOutcome, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "connector inbound companion flow execution")
	connectorNamespace, _ := ctx.Value(constants.ContextKeyConnectorSyncOrigin).(string)
	ctx = constants.WithConnectorInboundFlowExecution(ctx, flowDeveloperName, connectorNamespace)
	if executor, ok := a.executor.(flowOutcomeExecutor); ok {
		outcome, err := executor.ExecuteFlowVersionByIdOutcome(ctx, flowDeveloperName, companionFlowVersionID, inputs, user)
		return connector_inbound.FlowDependencyOutcomeFromModel(outcome), err
	}
	result, err := a.executor.ExecuteFlowVersionById(ctx, flowDeveloperName, companionFlowVersionID, inputs, user)
	return &connector_inbound.FlowDependencyOutcome{FlowOutcome: &connector_inbound.FlowOutcome{Result: connector_inbound.FlowResultFromModel(result)}}, err
}

func (a *flowAdapter) SystemUser(ctx context.Context) *models.UserSession {
	if a.systemUsers != nil {
		if user, err := a.systemUsers.LoadSystemUserSession(ctx); err == nil && user != nil {
			return user
		}
	}
	email := constants.SystemUserEmail
	return &models.UserSession{
		ID:    constants.SystemUserID,
		Name:  constants.SystemUserName,
		Email: &email,
	}
}
