// Package automation — audit_fault_ctx.go
//
// Audit-fault carrier on ctx: lets a deferred audit emit detect when an
// element's body returned (faultTarget, nil) — which is the contract Flow
// fault routing uses to keep control flow on the fault connector — but the
// element actually FAILED. Without this carrier, the deferred status
// derivation `if execErr != nil { status = "error" } else { status = "success" }`
// would record fault-handled failures as successful executions, breaking the
// audit log's `{elementId, status, errorMsg}` contract.
//
// Usage in element executors with audit defers:
//
//	func (e *FlowExecutor) executeRecordCreate(...) (nextElem string, execErr error) {
//	    ctx, faultBox := withAuditFaultBox(ctx)
//	    start := time.Now()
//	    defer func() {
//	        status := "success"
//	        errPayload := ""
//	        if execErr != nil {
//	            status, errPayload = "error", execErr.Error()
//	        } else if be := faultBox.Err(); be != nil {
//	            status, errPayload = "error", be.Error()
//	        }
//	        ...MaybeEmit(...)
//	    }()
//	    ...
//	    if create.FaultConnector != nil {
//	        recordAuditFault(ctx, err)
//	        interview.ContextData["$Flow.FaultMessage"] = err.Error()
//	        return create.FaultConnector.TargetReference, nil
//	    }
//	}
//
// The box is stored as a pointer so callees that the executor delegates to
// (e.g., executeFlowAction called from executeActionCall) can record their
// own fault routing on the same shared box without needing a defer of their
// own.
package automation

import (
	"context"
	"errors"
	"sync"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/shared/pkg/models"
)

// auditFaultBoxKey is the context key for *auditFaultBox.
type auditFaultBoxKey struct{}

type flowCauseElementTypeKey struct{}

func withFlowCauseElementType(ctx context.Context, elementType string) context.Context {
	return context.WithValue(ctx, flowCauseElementTypeKey{}, elementType)
}

func flowCauseElementType(ctx context.Context) string {
	elementType, _ := ctx.Value(flowCauseElementTypeKey{}).(string)
	return elementType
}

// auditFaultBox carries a fault-routed error for the deferred audit emit.
// Mutex-guarded so concurrent fault recordings (rare but possible across
// goroutines reusing the same ctx) cannot race.
type auditFaultBox struct {
	mu  sync.Mutex
	err error
}

// withAuditFaultBox attaches a fresh fault box to ctx and returns the new
// ctx plus a handle to the box. Element executors call this once at the top
// of the function so their deferred audit emit can read fault-routed errors.
func withAuditFaultBox(ctx context.Context) (context.Context, *auditFaultBox) {
	box := &auditFaultBox{}
	return context.WithValue(ctx, auditFaultBoxKey{}, box), box
}

// recordAuditFault records err on the nearest auditFaultBox in ctx (if any).
// Safe to call when no box is in scope (no-op) and when err is nil (no-op).
// First non-nil error wins; subsequent calls are ignored so a deeper helper
// that re-routes via the same fault connector cannot overwrite the original
// cause.
func recordAuditFault(ctx context.Context, err error) {
	if err == nil {
		return
	}
	box, ok := ctx.Value(auditFaultBoxKey{}).(*auditFaultBox)
	if !ok || box == nil {
		return
	}
	box.mu.Lock()
	defer box.mu.Unlock()
	if box.err == nil {
		box.err = err
	}
}

// Err returns the recorded fault error, or nil.
func (b *auditFaultBox) Err() error {
	if b == nil {
		return nil
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.err
}

// routeFault is the canonical fault-routing helper for element executors.
// It performs the three pieces every fault path needs:
//
//  1. stamp $Flow.FaultMessage on the interview's context data so the fault
//     connector branch can render it,
//  2. record err on the ctx audit-fault box so the deferred audit emit can
//     stamp Status="error" + ErrorPayload on what otherwise would have been
//     a (target, nil) "success" row,
//  3. return (target, nil) so the FlowExecutor main loop follows the fault
//     connector instead of propagating the error up the call stack.
//
// History-sequence and parent-row prelock replay signals are transaction-control
// flow, not routable business faults. They must reach the root retry owner
// unchanged so the current attempt rolls back and restarts with the complete
// prelock set. Unique-field upsert races use the same propagation contract.
//
// Without (2), Lane C round-3 Pass B Finding 2 holds: every fault-handled
// failure is logged as a successful element execution.
func (e *FlowExecutor) routeFault(ctx context.Context, interview *models.FlowInterview, target string, err error) (string, error) {
	if err == nil {
		err = errors.New("flow fault routed without specific error")
	}
	if _, retryRequired := ports.HistorySequencePrelockFields(err); retryRequired {
		return "", err
	}
	var parentPrelockReplay recordlocks.RetryablePrelockError
	if errors.As(err, &parentPrelockReplay) &&
		len(parentPrelockReplay.ParentPrelockKeys()) > 0 {
		return "", err
	}
	if ports.IsUniqueFieldUpsertRace(err) {
		return "", err
	}
	publishFlowAttemptFaultCause(ctx, classifyFlowExecutionCause(err, flowCauseElementType(ctx), interviewElementName(interview)), target)
	if interview != nil && interview.ContextData != nil {
		interview.ContextData["$Flow.FaultMessage"] = err.Error()
	}
	recordAuditFault(ctx, err)
	return target, nil
}

func interviewElementName(interview *models.FlowInterview) string {
	if interview == nil {
		return ""
	}
	return interview.CurrentElement
}
