// C10.5 wiring: bridge PersistenceService UPDATE → audit.DBEmitter.
//
// This file is intentionally tiny — it owns exactly one translation between
// PersistenceService's `effectiveUpdates / oldRecord` map pair and the audit
// package's `[]FieldDiff` shape, plus the call into the gated emitter.
//
// The emitter is graph-local composition state. It must never be activated by
// a package global tied to a worker goroutine in the same process.
package records

import (
	"context"
	"fmt"

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

// emitUpdateAuditV2 uses the same recordAuditFieldChanges helper as the legacy
// writer, so the readiness-gated bridge has one field-filter owner while the v2
// writer replaces the legacy update writer. Errors propagate so the caller's
// business tx rolls back on emission failure (§3.3 CH-3 atomicity).
func (ps *PersistenceService) emitUpdateAuditV2(
	txCtx context.Context,
	schema *models.ObjectMetadata,
	objectName, id string,
	effectiveUpdates, oldRecord models.SObject,
	currentUser *models.UserSession,
) error {
	if ps.auditV2Emitter == nil {
		return nil
	}
	changes := ps.recordAuditFieldChanges(txCtx, schema, objectName, effectiveUpdates, oldRecord)
	if err := ps.reserveRecordAuditSequences(txCtx, changes); err != nil {
		return fmt.Errorf("audit v2 reserve history sequence: %w", err)
	}
	diffs := make([]audit.FieldDiff, 0, len(changes))
	for _, change := range changes {
		diffs = append(diffs, audit.FieldDiff{
			FieldName:            change.fieldName,
			OldValue:             change.oldValue,
			NewValue:             change.newValue,
			FieldHistoryEligible: change.fieldHistoryEligible,
			HistorySequence:      change.historySequence,
		})
	}
	if len(diffs) == 0 {
		return nil
	}
	changedByID := recordAuditChangedByID(currentUser)
	if err := audit.MaybeEmitUpdateV2(
		txCtx, ps.txManager, ps.auditV2Emitter,
		objectName, id, changedByID, diffs,
	); err != nil {
		return fmt.Errorf("audit v2 emit: %w", err)
	}
	return nil
}

// emitCreateAuditV2 is the CREATE counterpart of emitUpdateAuditV2. It
// writes a single CREATE head row into _System_AuditLog + a matching
// outbox row so the C6 worker can fan out the `__created__` sentinel row
// that `<Object>History` surfaces as the CREATE entry (§3.6).
//
// Same gating rules as emitUpdateAuditV2: skip system tables other than
// _System_User, no-op when V2 is disabled, propagate errors so the
// caller's tx rolls back on emission failure (§3.3 CH-3 atomicity).
func (ps *PersistenceService) emitCreateAuditV2(
	txCtx context.Context,
	objectName, id string,
	currentUser *models.UserSession,
) error {
	if ps.auditV2Emitter == nil {
		return nil
	}
	if id == "" {
		// The Insert path assigns Id before calling us; if it didn't, the
		// upstream write is about to fail — don't double-report.
		return nil
	}
	if constants.IsSystemTable(objectName) && objectName != constants.TableUser {
		return nil
	}
	changedByID := constants.SystemUserID
	if currentUser != nil {
		changedByID = currentUser.ID
	}
	if err := audit.MaybeEmitCreateV2(
		txCtx, ps.txManager, ps.auditV2Emitter,
		objectName, id, changedByID,
	); err != nil {
		return fmt.Errorf("audit v2 emit create: %w", err)
	}
	return nil
}

// emitDeleteAuditV2 is the DELETE counterpart of emitCreateAuditV2 — writes
// a single DELETE head row + outbox payload into _System_AuditLog so the
// C6 worker can fan out the `__deleted__` sentinel row that
// `<Object>History` surfaces as the DELETE entry (§3.6).
//
// Without this hook, PersistenceService.Delete left the audit trail silent
// on destructive ops. Request capture still recorded the HTTP surface, but
// audit had no correlated row to join — a gap in forensic coverage.
//
// Same gating rules as emitCreateAuditV2: skip system tables other than
// _System_User, no-op when V2 is disabled, propagate errors so the
// caller's tx rolls back on emission failure (§3.3 CH-3 atomicity).
func (ps *PersistenceService) emitDeleteAuditV2(
	txCtx context.Context,
	objectName, id string,
	currentUser *models.UserSession,
) error {
	if ps.auditV2Emitter == nil {
		return nil
	}
	if id == "" {
		return nil
	}
	if constants.IsSystemTable(objectName) && objectName != constants.TableUser {
		return nil
	}
	changedByID := constants.SystemUserID
	if currentUser != nil {
		changedByID = currentUser.ID
	}
	if err := audit.MaybeEmitDeleteV2(
		txCtx, ps.txManager, ps.auditV2Emitter,
		objectName, id, changedByID,
	); err != nil {
		return fmt.Errorf("audit v2 emit delete: %w", err)
	}
	return nil
}

// emitUndeleteAuditV2 writes a single UNDELETE head row + outbox payload into
// _System_AuditLog so `<Object>History` can surface the `__undeleted__`
// sentinel row. Same gating rules as create/delete.
func (ps *PersistenceService) emitUndeleteAuditV2(
	txCtx context.Context,
	objectName, id string,
	currentUser *models.UserSession,
) error {
	if ps.auditV2Emitter == nil {
		return nil
	}
	if id == "" {
		return nil
	}
	if constants.IsSystemTable(objectName) && objectName != constants.TableUser {
		return nil
	}
	changedByID := constants.SystemUserID
	if currentUser != nil {
		changedByID = currentUser.ID
	}
	if err := audit.MaybeEmitUndeleteV2(
		txCtx, ps.txManager, ps.auditV2Emitter,
		objectName, id, changedByID,
	); err != nil {
		return fmt.Errorf("audit v2 emit undelete: %w", err)
	}
	return nil
}
