package records

import (
	"context"
	"errors"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordmutationv1"
)

// runtimeRecordMutationPersistenceEngine is the only production adapter from
// the coarse Runtime mutation use case to the existing persistence engine. It
// preserves the engine's single CRUD/RLS/FLS path while owning the Runtime
// contract's disclosure policy. PersistenceService itself intentionally does
// not implement RuntimeRecordMutationEngine. The constructor returns this as a
// value so production configuration cannot contain a typed-nil engine.
type runtimeRecordMutationPersistenceEngine struct {
	persistence *PersistenceService
}

func NewRuntimeRecordMutationPersistenceEngine(persistence *PersistenceService) (RuntimeRecordMutationEngine, error) {
	if persistence == nil {
		return nil, errors.New("runtime record mutation persistence engine is required")
	}
	return runtimeRecordMutationPersistenceEngine{persistence: persistence}, nil
}

func (runtimeRecordMutationPersistenceEngine) runtimeRecordMutationEngine() {}

func (e runtimeRecordMutationPersistenceEngine) Insert(ctx context.Context, objectName string, record models.Record, user *models.UserSession) (models.Record, error) {
	return e.persistence.Insert(ctx, objectName, record, user)
}

func (e runtimeRecordMutationPersistenceEngine) UpdateAuthorized(
	ctx context.Context,
	objectName, recordID string,
	record models.Record,
	user *models.UserSession,
	authorize func(context.Context, models.Record) error,
) error {
	if authorize == nil {
		return errors.New("runtime update authorization is required")
	}
	_, err := e.persistence.UpdateGuarded(ctx, objectName, recordID, record, user, func(guardCtx context.Context, locked models.Record) (bool, error) {
		if err := authorize(guardCtx, locked); err != nil {
			return false, err
		}
		return true, nil
	})
	return err
}

func (e runtimeRecordMutationPersistenceEngine) DeleteAuthorized(
	ctx context.Context,
	objectName, recordID string,
	user *models.UserSession,
	authorize func(context.Context, models.Record) error,
) error {
	if authorize == nil {
		return errors.New("runtime delete authorization is required")
	}
	missingOrDenied := func() error { return appErrors.NewNotFoundError("Record", "") }
	objectDenied := func() error { return appErrors.NewPermissionError("delete", objectName) }
	return e.persistence.deleteWithOutcomesAuthorized(ctx, objectName, recordID, user, missingOrDenied, objectDenied, missingOrDenied, func(guardCtx context.Context, locked models.Record) error {
		return authorize(guardCtx, locked)
	})
}

// ResolveOpaqueRecordObject is the compatibility-only reverse lookup for IDs
// that carry no Catalog KeyPrefix identity. Runtime mutation must resolve
// normal prefix-addressed IDs through its request-bound metadata view first.
func (e runtimeRecordMutationPersistenceEngine) ResolveOpaqueRecordObject(ctx context.Context, recordID string) (string, error) {
	return e.persistence.GetRecordType(ctx, recordID)
}

// ResolveOpaqueRecordObjectCandidates batches the compatibility-only physical
// discovery required by objectless opaque collection deletes. Returning every
// current owner lets Runtime evaluate Catalog policy without a first-match
// collision bypass or one complete catalog walk per input ID.
func (e runtimeRecordMutationPersistenceEngine) ResolveOpaqueRecordObjectCandidates(ctx context.Context, recordIDs []string) (map[string][]string, error) {
	return e.persistence.ResolveObjectTypeCandidates(ctx, recordIDs)
}

// ReadPolicyTarget performs the unlocked command-preflight read for authored
// mutation policy. It mirrors the established DML disclosure split while the
// locked UpdateAuthorized/DeleteAuthorized call remains final authority:
// object-level CRUD denial is explicit, a missing or RLS-hidden row is masked,
// and a known object is read from exactly one table.
func (e runtimeRecordMutationPersistenceEngine) ReadPolicyTarget(
	ctx context.Context,
	objectName, recordID string,
	kind semantic.OperationKind,
	user *models.UserSession,
) (models.Record, error) {
	operation := ""
	switch kind {
	case semantic.OperationUpdate:
		operation = constants.PermEdit
	case semantic.OperationDelete:
		operation = constants.PermDelete
	default:
		return nil, appErrors.NewValidationError("operation", "policy target read requires update or delete")
	}
	schema, err := e.persistence.prepareOperation(ctx, objectName, operation, user)
	if err != nil {
		return nil, err
	}
	record, err := e.persistence.repo.FindOne(ctx, objectName, recordID)
	if err != nil {
		return nil, appErrors.NewInternalError("failed to read Runtime mutation policy target", err)
	}
	if record == nil {
		return nil, appErrors.NewNotFoundError("Record", "")
	}
	if schema == nil || strings.TrimSpace(schema.APIName) == "" {
		return nil, appErrors.NewNotFoundError("Object", "")
	}
	denied := func() error { return appErrors.NewNotFoundError("Record", "") }
	if kind == semantic.OperationDelete {
		if constants.GetSecurityContext(ctx) < constants.SystemContext &&
			!constants.IsScopedInternalAccess(ctx, objectName, operation) {
			if !e.persistence.permissions.CheckObjectPermissionOnRecord(ctx, schema, record, operation, user) {
				return nil, appErrors.NewPermissionError(strings.ToLower(operation), objectName)
			}
			if !e.persistence.permissions.CheckRecordAccess(ctx, schema, record, operation, user) {
				return nil, denied()
			}
		}
	} else if err := e.persistence.checkRecordMutationAccessForOperation(ctx, objectName, recordID, schema, record, operation, user, denied); err != nil {
		return nil, err
	}
	return models.Record(record), nil
}
