package records

import (
	"context"
	"fmt"
	"time"

	"github.com/nexuscrm/kernel/pkg/apierr"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// prepareOperation checks permissions and retrieves schema.
func (ps *PersistenceService) prepareOperation(ctx context.Context, objectName string, operation string, user *models.UserSession) (*models.ObjectMetadata, error) {
	stageStart := time.Now()
	schema, err := ps.metadata.GetSchemaOrError(ctx, objectName)
	if err != nil {
		logRecordStageTiming(ctx, "PrepareOperation", "metadata.GetSchemaOrError", objectName, "", stageStart, err, 0)
		return nil, err
	}
	logRecordStageTiming(ctx, "PrepareOperation", "metadata.GetSchemaOrError", objectName, "", stageStart, nil, len(schema.Fields))
	stageStart = time.Now()
	if err := ps.checkOperationPermission(ctx, schema, objectName, operation, user); err != nil {
		logRecordStageTiming(ctx, "PrepareOperation", "checkOperationPermission", objectName, "", stageStart, err, 0)
		return nil, err
	}
	logRecordStageTiming(ctx, "PrepareOperation", "checkOperationPermission", objectName, "", stageStart, nil, 0)
	return schema, nil
}

func (ps *PersistenceService) checkOperationPermission(ctx context.Context, schema *models.ObjectMetadata, objectName string, operation string, user *models.UserSession) error {
	// SystemContext bypasses business-level permission checks (CRUD/FLS/RLS)
	// but still enforces hard security boundaries (blocked tables).
	if constants.GetSecurityContext(ctx) >= constants.SystemContext || constants.IsScopedInternalAccess(ctx, objectName, operation) {
		if ps.permissions.IsBlockedObject(ctx, objectName, operation) {
			return apierr.Forbidden(fmt.Sprintf("blocked: %s is not accessible via generic %s, even in system context", objectName, operation))
		}
	} else {
		// Defer the producer-side object-permission check when the
		// downstream record-aware consumer gate (CheckObjectPermissionOnRecord
		// in Update / Delete / Upsert) is the correct place to enforce CRUD -
		// either because the schema itself has a record-context AccessPolicy
		// for this operation, OR because the schema is parent-controlled and
		// the parent (or any ancestor) needs record context. The unified
		// predicate folds Edit/Delete-only semantics inside the helper, so
		// PermCreate and PermRead never defer (they have no record-aware
		// consumer gate downstream).
		requiresRecordContext := ps.permissions.RequiresRecordAwareCRUDDeferral(ctx, schema, operation)
		if !requiresRecordContext {
			if err := ps.permissions.CheckPermissionOrErrorWithUser(ctx, objectName, operation, user); err != nil {
				return err
			}
		}
	}
	return nil
}

// GetObjectSchema returns the schema for an object by name, bypassing permission checks.
// Use only in contexts where the caller has already performed access validation
// and delegates writes to paths that enforce permissions themselves. Do NOT use
// as a general-purpose alternative to prepareOperation/Retrieve.
func (ps *PersistenceService) GetObjectSchema(ctx context.Context, objectName string) (*models.ObjectMetadata, error) {
	return ps.metadata.GetSchemaOrError(ctx, objectName)
}
