package records

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/automation/autonumber"
	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

// autoNumberMetadataProvider is the records-package-private interface for
// autonumber metadata lookups.  It matches the only method
// assignAutoNumbersBatch / assignAutoNumbers / engineOwnedFieldSet consult
// via ps.autoNumberMeta (LookupByObject).  Defined here so unit tests can
// stub it without touching the autonumber package.
type autoNumberMetadataProvider interface {
	LookupByObjectContext(context.Context, string) ([]autonumber.FieldMeta, error)
}

func (ps *PersistenceService) autoNumberFields(ctx context.Context, objectName string) ([]autonumber.FieldMeta, error) {
	if ps.autoNumberMeta == nil {
		return nil, nil
	}
	fields, err := ps.autoNumberMeta.LookupByObjectContext(ctx, objectName)
	if err != nil {
		return nil, fmt.Errorf("resolve autonumber metadata for %s: %w", objectName, err)
	}
	return fields, nil
}

// engineOwnedFieldSet returns the set of field API names (lower-cased) on
// objectName that are managed by a platform engine and must not be
// overwritten by insert-time defaulters, inheriters, or child-clone copiers.
// Today the only contributing engine is the autonumber renderer — a field
// with a non-empty auto_number_format participates.
//
// The inheritance applier and child-clone executor both consult this set to
// preserve behavior parity with the deleted mergeQuoteDefaultsFromOpportunity
// / buildOrderProductClonePayload code paths, which hand-rolled the same
// skip. Hoisting it into one function keeps future engines (if we add
// formula-engine-managed fields or rollup-engine-managed fields) in one
// place.
//
// Returns nil when no engine owns any field on objectName — callers should
// cheaply treat a nil map as "skip set empty".
func (ps *PersistenceService) engineOwnedFieldSet(ctx context.Context, objectName string) (map[string]bool, error) {
	fields, err := ps.autoNumberFields(ctx, objectName)
	if err != nil {
		return nil, err
	}
	if len(fields) == 0 {
		return nil, nil
	}
	out := make(map[string]bool, len(fields))
	for _, fm := range fields {
		out[strings.ToLower(fm.FieldAPIName)] = true
	}
	return out, nil
}

// rejectExplicitAutoNumberWrites blocks caller-supplied writes to
// metadata-declared autonumber fields. Assignment still happens through the
// generic autonumber engine later in the write pipeline; this helper only
// rejects direct user payload writes.
func (ps *PersistenceService) rejectExplicitAutoNumberWrites(ctx context.Context, objectName string, data models.SObject) error {
	if len(data) == 0 {
		return nil
	}
	autoNumberFields, err := ps.engineOwnedFieldSet(ctx, objectName)
	if err != nil {
		return appErrors.NewInternalError("failed to resolve engine-owned fields", err)
	}
	if len(autoNumberFields) == 0 {
		return nil
	}
	var denied []string
	for key := range data {
		if autoNumberFields[strings.ToLower(key)] {
			denied = append(denied, key)
		}
	}
	if len(denied) == 0 {
		return nil
	}
	return appErrors.NewFLSDeniedErrorForFields(denied)
}
