package metadata

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	domainSchema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// ---------------------------------------------------------------------------
// CreateField rollupsummary — single-fact contract tests
// ---------------------------------------------------------------------------

// rollupCreateTestSchemaRepo tracks AddColumn and SaveFieldMetadataWithIDs calls.
type rollupCreateTestSchemaRepo struct {
	ports.SchemaRepository
	saved        *models.FieldMetadata
	addedCols    []domainSchema.ColumnDefinition
	modifiedCols []domainSchema.ColumnDefinition
}

func (r *rollupCreateTestSchemaRepo) SaveFieldMetadataWithIDs(ctx context.Context, field *models.FieldMetadata, objectID string, objectAPIName string, fieldID string, tx interface{}) error {
	cloned := *field
	r.saved = &cloned
	return nil
}

func (r *rollupCreateTestSchemaRepo) AddColumn(tableName string, col domainSchema.ColumnDefinition) error {
	r.addedCols = append(r.addedCols, col)
	return nil
}

func (r *rollupCreateTestSchemaRepo) ModifyColumn(tableName, colName string, col domainSchema.ColumnDefinition) error {
	r.modifiedCols = append(r.modifiedCols, col)
	return nil
}

func (r *rollupCreateTestSchemaRepo) MapFieldTypeToSQL(fieldType string) string {
	switch strings.ToUpper(fieldType) {
	case "CURRENCY":
		return "DECIMAL(18,2)"
	case "STRING", "TEXT":
		return "TEXT"
	case "DOUBLE":
		return "DOUBLE"
	case "INT", "INTEGER":
		return "BIGINT"
	default:
		return "INVALID_TYPE_ERROR"
	}
}

func (r *rollupCreateTestSchemaRepo) GetStandardSystemColumns() []domainSchema.ColumnDefinition {
	return nil
}

func (r *rollupCreateTestSchemaRepo) GetStandardFieldMetadata() []models.FieldMetadata {
	return nil
}

func (r *rollupCreateTestSchemaRepo) IsSystemColumn(name string) bool {
	return false
}

// rollupCreateTestMetaRepo is a MetadataRepository stub for CreateField tests.
type rollupCreateTestMetaRepo struct {
	ports.MetadataRepository
	all []*models.ObjectMetadata
}

func (r *rollupCreateTestMetaRepo) GetSchemaByAPIName(ctx context.Context, apiName string) (*models.ObjectMetadata, error) {
	for _, obj := range r.all {
		if strings.EqualFold(obj.APIName, apiName) {
			return obj, nil
		}
	}
	return nil, nil
}

func (r *rollupCreateTestMetaRepo) GetAllSchemas(ctx context.Context) ([]*models.ObjectMetadata, error) {
	return r.all, nil
}

func (r *rollupCreateTestMetaRepo) GetLayouts(ctx context.Context, objectAPIName string) ([]*models.PageLayout, error) {
	// Return empty layouts — the test doesn't exercise layout logic.
	return nil, nil
}

// TestCreateFieldRollupSummary_PersistsCanonicalConfig verifies that CreateField
// with a rollupsummary field persists the canonicalized RollupConfig (uppercase
// CalcType, schema-lookup-resolved API names) and uses the resolver's SQL type
// for DDL — not MapFieldTypeToSQL which returns INVALID_TYPE_ERROR.
func TestCreateFieldRollupSummary_PersistsCanonicalConfig(t *testing.T) {
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "Amount__c", Type: constants.FieldTypeCurrency},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	field := &models.FieldMetadata{
		APIName: "Total__c",
		Label:   "Total",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "  sum  ", // lowercase + whitespace → canonicalized to SUM
			SummaryObject:     "  child__c  ",
			SummaryField:      "  amount__c  ",
			RelationshipField: "  parentid  ",
		},
	}

	err := ms.CreateField(context.Background(), "Parent__c", field)
	if err != nil {
		t.Fatalf("CreateField (rollupsummary) returned error: %v", err)
	}

	// Verify the field was canonicalized in-place.
	cfg := field.RollupConfig
	if cfg == nil {
		t.Fatal("field RollupConfig is nil after CreateField")
	}
	if cfg.CalcType != "SUM" {
		t.Errorf("CalcType = %q, want SUM (canonicalized)", cfg.CalcType)
	}
	if cfg.SummaryObject != "Child__c" {
		t.Errorf("SummaryObject = %q, want Child__c (canonicalized from schema lookup)", cfg.SummaryObject)
	}
	if cfg.SummaryField != "Amount__c" {
		t.Errorf("SummaryField = %q, want Amount__c (canonicalized from schema lookup)", cfg.SummaryField)
	}

	// Verify the DDL used the resolver's SQL type, not MapFieldTypeToSQL.
	if len(schemaRepo.addedCols) != 1 {
		t.Fatalf("AddColumn calls = %d, want 1", len(schemaRepo.addedCols))
	}
	if schemaRepo.addedCols[0].Type != "DECIMAL(65,30)" {
		t.Errorf("AddColumn SQL type = %q, want DECIMAL(65,30) (resolver output for SUM)", schemaRepo.addedCols[0].Type)
	}
}

func TestCreateFieldRequiredBusinessFieldKeepsPhysicalColumnNullable(t *testing.T) {
	obj := &models.ObjectMetadata{
		ID:      "obj_case",
		APIName: "Case",
		Fields:  []models.FieldMetadata{},
	}
	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{obj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	field := &models.FieldMetadata{
		APIName:  "Resolution__c",
		Label:    "Resolution",
		Type:     constants.FieldTypeString,
		Required: true,
	}

	if err := ms.CreateField(context.Background(), "Case", field); err != nil {
		t.Fatalf("CreateField returned error: %v", err)
	}
	if len(schemaRepo.addedCols) != 1 {
		t.Fatalf("AddColumn calls = %d, want 1", len(schemaRepo.addedCols))
	}
	if !schemaRepo.addedCols[0].Nullable {
		t.Fatalf("AddColumn Nullable = false, want true; required is save-time validation, not physical NOT NULL")
	}
	if schemaRepo.saved == nil {
		t.Fatal("SaveFieldMetadataWithIDs was not called to preserve Required metadata")
	}
	if !schemaRepo.saved.Required {
		t.Fatalf("saved Required = false, want true")
	}
}

// TestCreateFieldRollupSummary_CycleRejected verifies that CreateField rejects
// a rollupsummary field that would create a cycle in the rollup graph.
func TestCreateFieldRollupSummary_CycleRejected(t *testing.T) {
	// ObjectA already has a rollup that summarizes ObjectB.
	existingCfg := models.RollupConfig{
		CalcType:          "SUM",
		SummaryObject:     "ObjectB",
		SummaryField:      "Amount",
		RelationshipField: "ObjectAId",
	}
	objA := &models.ObjectMetadata{
		ID:      "obj_a",
		APIName: "ObjectA",
		Fields: []models.FieldMetadata{
			{APIName: "Total", Type: constants.FieldTypeRollupSummary, RollupConfig: &existingCfg},
			{APIName: "ObjectBId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ObjectB"}},
		},
	}
	objB := &models.ObjectMetadata{
		ID:      "obj_b",
		APIName: "ObjectB",
		Fields: []models.FieldMetadata{
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
			{APIName: "ObjectAId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ObjectA"}},
		},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{objA, objB}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	// Try to create a rollup on ObjectB that summarizes ObjectA → cycle.
	err := ms.CreateField(context.Background(), "ObjectB", &models.FieldMetadata{
		APIName: "BackRef",
		Label:   "Back",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "ObjectA",
			SummaryField:      "Total",
			RelationshipField: "ObjectBId",
		},
	})
	if err == nil {
		t.Fatal("CreateField (creating cycle) returned nil, want error")
	}
	if !strings.Contains(strings.ToLower(err.Error()), "cycle") {
		t.Errorf("error must mention 'cycle', got: %v", err)
	}
}

// TestCreateFieldRollupSummary_SelfLoopRejected verifies that CreateField rejects
// a self-referencing rollupsummary field.
func TestCreateFieldRollupSummary_SelfLoopRejected(t *testing.T) {
	objA := &models.ObjectMetadata{
		ID:      "obj_a",
		APIName: "ObjectA",
		Fields: []models.FieldMetadata{
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ObjectA"}},
		},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{objA}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	err := ms.CreateField(context.Background(), "ObjectA", &models.FieldMetadata{
		APIName: "SelfTotal",
		Label:   "Self Total",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "ObjectA",
			SummaryField:      "Amount",
			RelationshipField: "ParentId",
		},
	})
	if err == nil {
		t.Fatal("CreateField (self-loop) returned nil, want error")
	}
	if !strings.Contains(strings.ToLower(err.Error()), "cycle") {
		t.Errorf("error must mention 'cycle', got: %v", err)
	}
}

// TestCreateFieldRollupSummary_MIN_PassthroughSourceType verifies that CreateField
// with MIN/MAX uses the source field's SQL type (passthrough), not DECIMAL(65,30).
func TestCreateFieldRollupSummary_MIN_PassthroughSourceType(t *testing.T) {
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "Amount__c", Type: constants.FieldTypeCurrency},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	err := ms.CreateField(context.Background(), "Parent__c", &models.FieldMetadata{
		APIName: "MinAmount__c",
		Label:   "Min Amount",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "MIN",
			SummaryObject:     "Child__c",
			SummaryField:      "Amount__c",
			RelationshipField: "ParentId",
		},
	})
	if err != nil {
		t.Fatalf("CreateField (MIN rollupsummary) returned error: %v", err)
	}

	if len(schemaRepo.addedCols) != 1 {
		t.Fatalf("AddColumn calls = %d, want 1", len(schemaRepo.addedCols))
	}
	// MIN of currency → passthrough DECIMAL(18,2), not DECIMAL(65,30).
	if schemaRepo.addedCols[0].Type != "DECIMAL(18,2)" {
		t.Errorf("AddColumn SQL type = %q, want DECIMAL(18,2) (MIN passthrough of currency)", schemaRepo.addedCols[0].Type)
	}
}

// TestCreateFieldRollupSummary_COUNT_ReturnsDecimal verifies that CreateField
// with COUNT resolves to DECIMAL(65,30) and canonicalizes SummaryField to "*".
func TestCreateFieldRollupSummary_COUNT_ReturnsDecimal(t *testing.T) {
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	field := &models.FieldMetadata{
		APIName: "ChildCount__c",
		Label:   "Child Count",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "COUNT",
			SummaryObject:     "Child__c",
			SummaryField:      "Id",
			RelationshipField: "ParentId",
		},
	}

	err := ms.CreateField(context.Background(), "Parent__c", field)
	if err != nil {
		t.Fatalf("CreateField (COUNT rollupsummary) returned error: %v", err)
	}

	if len(schemaRepo.addedCols) != 1 {
		t.Fatalf("AddColumn calls = %d, want 1", len(schemaRepo.addedCols))
	}
	if schemaRepo.addedCols[0].Type != "DECIMAL(65,30)" {
		t.Errorf("AddColumn SQL type = %q, want DECIMAL(65,30) (COUNT)", schemaRepo.addedCols[0].Type)
	}
	// COUNT must set SummaryField to "*" in the canonical config.
	if field.RollupConfig == nil {
		t.Fatal("field RollupConfig is nil after CreateField")
	}
	if field.RollupConfig.SummaryField != "*" {
		t.Errorf("SummaryField = %q, want * (COUNT always uses *)", field.RollupConfig.SummaryField)
	}
}

func TestCreateFieldRejectsRollupConfigOnNonRollupField(t *testing.T) {
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)
	ms.SetValidationService(NewValidationService(nil))

	err := ms.CreateField(context.Background(), "Parent__c", &models.FieldMetadata{
		APIName: "Name__c",
		Label:   "Name",
		Type:    constants.FieldTypeString,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "Child__c",
			SummaryField:      "Amount__c",
			RelationshipField: "ParentId",
		},
	})
	if err == nil {
		t.Fatal("CreateField accepted rollupConfig on non-rollup field, want rejection")
	}
	if !strings.Contains(err.Error(), "rollupsummary") {
		t.Fatalf("error = %v, want rollupsummary diagnostic", err)
	}
	if len(schemaRepo.addedCols) != 0 {
		t.Fatalf("AddColumn calls = %d, want 0", len(schemaRepo.addedCols))
	}
}

// TestResolveRollupSourceFieldSQLType_CaseInsensitiveMatch verifies that
// resolveRollupSourceFieldSQLType matches the source field name
// case-insensitively, matching the canonicalization behavior.
func TestResolveRollupSourceFieldSQLType_CaseInsensitiveMatch(t *testing.T) {
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "Amount__c", Type: constants.FieldTypeCurrency},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	// Pass lowercase+whitespace SummaryField — must still resolve.
	cfg := &models.RollupConfig{
		CalcType:      "SUM",
		SummaryObject: "Child__c",
		SummaryField:  "  amount__c  ",
	}
	sqlType, err := ms.resolveRollupSourceFieldSQLType(context.Background(), cfg)
	if err != nil {
		t.Fatalf("resolveRollupSourceFieldSQLType returned error: %v", err)
	}
	if sqlType != "DECIMAL(18,2)" {
		t.Errorf("sqlType = %q, want DECIMAL(18,2) (currency field)", sqlType)
	}
}

func TestResolveRollupSourceFieldSQLType_MissingSummaryObjectRejected(t *testing.T) {
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	cfg := &models.RollupConfig{
		CalcType:      "SUM",
		SummaryObject: "  Missing__c  ",
		SummaryField:  "Amount__c",
	}
	if _, err := ms.resolveRollupSourceFieldSQLType(context.Background(), cfg); err == nil {
		t.Fatal("resolveRollupSourceFieldSQLType returned nil error for missing summary object")
	}
}

func TestResolveRollupSourceFieldSQLType_FormulaBackedScalarUsesReturnType(t *testing.T) {
	formula := "Amount__c * 2"
	returnType := constants.FieldTypeDouble
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{
				APIName:    "ComputedAmount__c",
				Type:       constants.FieldTypeString,
				Formula:    &formula,
				ReturnType: &returnType,
			},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
		Fields:  []models.FieldMetadata{},
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	cfg := &models.RollupConfig{
		CalcType:      "MAX",
		SummaryObject: "Child__c",
		SummaryField:  "ComputedAmount__c",
	}
	sqlType, err := ms.resolveRollupSourceFieldSQLType(context.Background(), cfg)
	if err != nil {
		t.Fatalf("resolveRollupSourceFieldSQLType returned error: %v", err)
	}
	if sqlType != "DOUBLE" {
		t.Fatalf("sqlType = %q, want DOUBLE from formula ReturnType", sqlType)
	}
}

func TestCreateFieldRollupSummary_RelationshipFieldWrongTargetRejected(t *testing.T) {
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "Amount__c", Type: constants.FieldTypeCurrency},
			{APIName: "ProjectId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Project__c"}},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	err := ms.CreateField(context.Background(), "Parent__c", &models.FieldMetadata{
		APIName: "Total__c",
		Label:   "Total",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "Child__c",
			SummaryField:      "Amount__c",
			RelationshipField: "ProjectId",
		},
	})
	if err == nil {
		t.Fatal("CreateField with wrong relationship target returned nil, want rejection")
	}
	if !strings.Contains(err.Error(), "relationshipField") {
		t.Fatalf("error = %v, want relationshipField diagnostic", err)
	}
}

func TestCreateFieldRollupSummary_SUM_NonNumericSourceRejected(t *testing.T) {
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "Name__c", Type: constants.FieldTypeString},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	err := ms.CreateField(context.Background(), "Parent__c", &models.FieldMetadata{
		APIName: "Total__c",
		Label:   "Total",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "Child__c",
			SummaryField:      "Name__c",
			RelationshipField: "ParentId",
		},
	})
	if err == nil {
		t.Fatal("CreateField SUM(Text) returned nil, want rejection")
	}
	if !strings.Contains(strings.ToLower(err.Error()), "numeric") {
		t.Fatalf("error = %v, want numeric diagnostic", err)
	}
}

func TestCreateFieldRollupSummary_SUM_FormulaBackedScalarUsesReturnType(t *testing.T) {
	formula := "Amount__c * 2"
	returnType := constants.FieldTypeDouble
	childObj := &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{
				APIName:    "ComputedAmount__c",
				Type:       constants.FieldTypeString,
				Formula:    &formula,
				ReturnType: &returnType,
			},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}},
		},
	}
	parentObj := &models.ObjectMetadata{
		ID:      "obj_parent",
		APIName: "Parent__c",
	}

	metaRepo := &rollupCreateTestMetaRepo{all: []*models.ObjectMetadata{parentObj, childObj}}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	field := &models.FieldMetadata{
		APIName: "Total__c",
		Label:   "Total",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "Child__c",
			SummaryField:      "ComputedAmount__c",
			RelationshipField: "ParentId",
		},
	}
	if err := ms.CreateField(context.Background(), "Parent__c", field); err != nil {
		t.Fatalf("CreateField SUM(formula-backed scalar) returned error: %v", err)
	}
	if len(schemaRepo.addedCols) != 1 {
		t.Fatalf("AddColumn calls = %d, want 1", len(schemaRepo.addedCols))
	}
	if schemaRepo.addedCols[0].Type != "DECIMAL(65,30)" {
		t.Fatalf("AddColumn SQL type = %q, want DECIMAL(65,30)", schemaRepo.addedCols[0].Type)
	}
	if field.ReturnType == nil || *field.ReturnType != constants.FieldTypeDouble || field.Precision == nil || *field.Precision != 18 || field.Scale == nil || *field.Scale != 0 {
		t.Fatalf("created rollup output = %+v, want double(18,0)", field)
	}
	if field.RollupConfig == nil || field.RollupConfig.ResultType == nil || *field.RollupConfig.ResultType != constants.FieldTypeDouble || field.RollupConfig.ResultPrecision == nil || *field.RollupConfig.ResultPrecision != 18 || field.RollupConfig.ResultScale == nil || *field.RollupConfig.ResultScale != 0 {
		t.Fatalf("created rollup result facets = %+v, want double(18,0)", field.RollupConfig)
	}
}

// TestCreateFieldRollupSummary_DepthRejected verifies that CreateField rejects
// a rollupsummary field that would exceed the maximum allowed chain depth.
// This ensures metadata CRUD and pack/apply enforce the same depth cap.
func TestCreateFieldRollupSummary_DepthRejected(t *testing.T) {
	// Build a linear chain: Depth0→Depth1→...→Depth6 (7 objects, 7 existing edges).
	// Each Depth_i has a rollup field summarizing Depth_{i+1}.
	// MaxRollupDepth = 8, so depth 7 is at cap and passes.
	// We then try to create a rollup on Depth7 that summarizes Depth8,
	// which would push depth to 8 (exceeds MaxRollupDepth).
	schemas := make([]*models.ObjectMetadata, 9)
	for i := 0; i < 9; i++ {
		name := fmt.Sprintf("Depth%d", i)
		obj := &models.ObjectMetadata{
			ID:      fmt.Sprintf("obj_depth%d", i),
			APIName: name,
		}
		if i > 0 {
			obj.Fields = append(obj.Fields, models.FieldMetadata{
				APIName:     fmt.Sprintf("Depth%dId", i-1),
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{fmt.Sprintf("Depth%d", i-1)},
			})
		}
		if i < 7 {
			// Existing rollup: Depth_i rolls up over Depth_{i+1}
			childName := fmt.Sprintf("Depth%d", i+1)
			obj.Fields = append(obj.Fields, models.FieldMetadata{
				APIName: fmt.Sprintf("F%d", i),
				Type:    constants.FieldTypeRollupSummary,
				RollupConfig: &models.RollupConfig{
					CalcType:          "SUM",
					SummaryObject:     childName,
					SummaryField:      fmt.Sprintf("F%d", i+1),
					RelationshipField: fmt.Sprintf("Depth%dId", i),
				},
			})
		}
		if i == 8 {
			obj.Fields = append(obj.Fields, models.FieldMetadata{
				APIName: "Amount",
				Type:    constants.FieldTypeCurrency,
			})
		}
		schemas[i] = obj
	}

	metaRepo := &rollupCreateTestMetaRepo{all: schemas}
	schemaRepo := &rollupCreateTestSchemaRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)

	// Try to create a rollup on Depth7 that summarizes Depth8.
	// This extends the chain to depth 8, exceeding MaxRollupDepth.
	err := ms.CreateField(context.Background(), "Depth7", &models.FieldMetadata{
		APIName: "F7",
		Label:   "Rollup",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			CalcType:          "SUM",
			SummaryObject:     "Depth8",
			SummaryField:      "Amount",
			RelationshipField: "Depth7Id",
		},
	})
	if err == nil {
		t.Fatal("CreateField (depth exceed) returned nil, want error")
	}
	if !strings.Contains(strings.ToLower(err.Error()), "depth") {
		t.Errorf("error must mention 'depth', got: %v", err)
	}
}
