package records

// persistence_immutable_fields_test.go
//
// Regression coverage for issue #503 / PR #564 P1:
// RequiredReadPermission must not be writable via generic CRUD on _System_Object.
// The runtime permission gate reads this field directly from the DB-loaded schema;
// a generic CRUD write can silently downgrade the audit-payload gate without any
// startup assertion catching it.
//
// Option B.2 (broadest): RequiredReadPermission is added to the global
// immutableObjectFields set, blocking ALL non-ElevatedContext writes to
// _System_Object.RequiredReadPermission regardless of which row. Only
// SystemContext (setup apply / bootstrap) can bypass via BypassesCRUDandFLS().

import (
	"context"
	"strings"
	"testing"

	eventFeature "github.com/nexuscrm/events"
	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/validation"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// TestRejectImmutableFieldUpdates_RequiredReadPermission_Blocked verifies that a
// generic CRUD update carrying RequiredReadPermission on _System_Object is
// rejected with an FLSDeniedError (INVALID_FIELD_FOR_INSERT_UPDATE).
// This pins the B.2 guard: the field is now in immutableObjectFields.
func TestRejectImmutableMetadataFieldWrites_RequiredReadPermission_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysObject_RequiredReadPermission: "ViewSetup",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableObject, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for RequiredReadPermission write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

// TestRejectImmutableFieldUpdates_RequiredReadPermission_OtherTable verifies
// that the guard is scoped to _System_Object and does not reject
// RequiredReadPermission writes on unrelated tables (no false positives).
func TestRejectImmutableMetadataFieldWrites_RequiredReadPermission_OtherTable(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysObject_RequiredReadPermission: "ViewSetup",
	}
	// Should be nil — only _System_Object is guarded.
	err := rejectImmutableMetadataFieldWrites("SomeOtherTable", updates)
	if err != nil {
		t.Fatalf("expected nil for non-_System_Object table, got: %v", err)
	}
}

// TestRejectImmutableFieldUpdates_UnrelatedField_NotBlocked verifies that
// unrelated writable fields on _System_Object are not blocked.
func TestRejectImmutableMetadataFieldWrites_UnrelatedField_NotBlocked(t *testing.T) {
	updates := models.SObject{
		"Label": "Some Label",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableObject, updates)
	if err != nil {
		t.Fatalf("expected nil for non-immutable field Label, got: %v", err)
	}
}

// TestRejectImmutableFieldUpdates_RequiredWritePermission_Blocked verifies that a
// generic CRUD update carrying RequiredWritePermission on _System_Object is
// rejected with an FLSDeniedError.
func TestRejectImmutableMetadataFieldWrites_RequiredWritePermission_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysObject_RequiredWritePermission: "ViewAllData",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableObject, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for RequiredWritePermission write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectImmutableMetadataFieldWrites_ReadSource_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysObject_ReadSource: `{"kind":"physical_table","physicalTable":"_System_RecordType"}`,
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableObject, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for ReadSource write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectImmutableMetadataFieldWrites_DisplayNameField_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysObject_DisplayNameField: "SomeField__c",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableObject, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for DisplayNameField write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectImmutableMetadataFieldWrites_ExclusiveSiblingFields_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysObject_ExclusiveSiblingFields:                  `[{"fieldApiName":"IsDefault__c","partitionFieldApiName":"Parent__c"}]`,
		constants.FieldSysObject_ExclusiveSiblingFieldsCustomized:        true,
		constants.FieldSysObject_ExclusiveSiblingFieldsPackName:          "crm",
		constants.FieldSysObject_ExclusiveSiblingFieldsPackBusinessKey:   "crm-objects",
		constants.FieldSysObject_ExclusiveSiblingFieldsPackManageability: "defaults_only",
		constants.FieldSysObject_ExclusiveSiblingFieldsPackValue:         "[]",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableObject, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for ExclusiveSiblingFields metadata writes, got nil")
	}
	fls, ok := appErrors.AsFLSDenied(err)
	if !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	for field := range updates {
		if !containsString(fls.Fields, field) {
			t.Fatalf("FLSDenied fields = %#v, want %s", fls.Fields, field)
		}
	}
}

func TestRejectImmutableMetadataFieldWrites_FieldOwnerPackAndOwnerKind_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysField_OwnerKind: constants.FieldOwnerKindPackRemoving,
		constants.FieldSysField_OwnerPack: "jira-like-sample",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableField, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for _System_Field ownership write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectImmutableMetadataFieldWrites_SiblingMaxParentField_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysField_SiblingMaxParentField: "Parent__c",
	}
	err := rejectImmutableMetadataFieldWrites(constants.TableField, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for sibling_max_parent_field write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectUnbypassableMetadataFieldWrites_FieldCascadeGraphFields_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysField_ReferenceTo:    `["Parent__c"]`,
		constants.FieldSysField_DeleteRule:     string(constants.DeleteRuleCascade),
		constants.FieldSysField_IsMasterDetail: true,
		constants.FieldSysField_IsPolymorphic:  false,
	}
	err := rejectUnbypassableMetadataFieldWrites(constants.TableField, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for _System_Field cascade graph writes, got nil")
	}
	fls, ok := appErrors.AsFLSDenied(err)
	if !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	for field := range updates {
		if !containsString(fls.Fields, field) {
			t.Fatalf("FLSDenied fields = %#v, want %s", fls.Fields, field)
		}
	}
}

func TestRejectImmutableMetadataFieldWrites_InstalledPackStatus_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldStatus: "removing",
	}
	err := rejectImmutableMetadataFieldWrites("_System_InstalledPack", updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for _System_InstalledPack.Status write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectUnbypassableMetadataFieldWrites_EmailTemplateOwnerPack_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysEmailTemplate_OwnerPack: "crm",
	}
	err := rejectUnbypassableMetadataFieldWrites(constants.TableEmailTemplate, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for _System_EmailTemplate.OwnerPack write, got nil")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
}

func TestRejectUnbypassableMetadataFieldWrites_ListViewOwnershipFields_Blocked(t *testing.T) {
	updates := models.SObject{
		constants.FieldSysListView_OwnerID:       "005-owner",
		constants.FieldSysListView_Visibility:    "Private",
		constants.FieldSysListView_DeveloperName: "Account_All",
	}
	err := rejectUnbypassableMetadataFieldWrites(constants.TableListView, updates)
	if err == nil {
		t.Fatal("expected FLSDeniedError for _System_ListView ownership field write, got nil")
	}
	fls, ok := appErrors.AsFLSDenied(err)
	if !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	for field := range updates {
		if !containsString(fls.Fields, field) {
			t.Fatalf("FLSDenied fields = %#v, want %s", fls.Fields, field)
		}
	}
}

func TestInsertRejectsListViewOwnershipFieldsIntroducedByBeforeCreate(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(listViewOwnershipGuardSchema(), repo)
	ps.eventBus = lifecycleSpikeEventBus{publish: func(_ context.Context, eventType sharedEvents.EventType, payload interface{}) error {
		if eventType != sharedEvents.RecordBeforeCreate {
			return nil
		}
		recordPayload, ok := payload.(eventFeature.RecordEventPayload)
		if !ok {
			t.Fatalf("RecordBeforeCreate payload type = %T, want RecordEventPayload", payload)
		}
		recordPayload.Record[constants.FieldSysListView_DeveloperName] = "Account_Private"
		return nil
	}}

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "list view before-create guard test")
	_, err := ps.Insert(ctx, constants.TableListView, models.SObject{
		constants.FieldSysListView_ObjectAPIName: "Account",
		constants.FieldSysListView_Label:         "All Accounts",
		constants.FieldSysListView_FilterExpr:    "{}",
		constants.FieldSysListView_Fields:        `["Name"]`,
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Insert = nil, want final list-view ownership field rejection")
	}
	fls, ok := appErrors.AsFLSDenied(err)
	if !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if !containsString(fls.Fields, constants.FieldSysListView_DeveloperName) {
		t.Fatalf("FLSDenied fields = %#v, want %s", fls.Fields, constants.FieldSysListView_DeveloperName)
	}
	if repo.inserted != nil {
		t.Fatalf("Insert reached repo.Insert with %#v, want fail-closed before SQL write", repo.inserted)
	}
}

func TestUpdateRejectsListViewOwnershipFieldsIntroducedByBeforeUpdate(t *testing.T) {
	repo := &captureRecordRepo{old: models.SObject{
		constants.FieldID:                           "lv-1",
		constants.FieldSysListView_ObjectAPIName:    "Account",
		constants.FieldSysListView_Label:            "All Accounts",
		constants.FieldSysListView_FilterExpr:       "{}",
		constants.FieldSysListView_Fields:           `["Name"]`,
		constants.FieldSysListView_OwnerID:          nil,
		constants.FieldSysListView_Visibility:       nil,
		constants.FieldSysListView_DeveloperName:    nil,
		constants.FieldCreatedDate:                  "2026-01-01T00:00:00Z",
		constants.FieldSysListView_LastModifiedDate: "2026-01-01T00:00:00Z",
	}}
	ps := newRecordEntrypointTestService(listViewOwnershipGuardSchema(), repo)
	ps.eventBus = lifecycleSpikeEventBus{publish: func(_ context.Context, eventType sharedEvents.EventType, payload interface{}) error {
		if eventType != sharedEvents.RecordBeforeUpdate {
			return nil
		}
		recordPayload, ok := payload.(eventFeature.RecordEventPayload)
		if !ok {
			t.Fatalf("RecordBeforeUpdate payload type = %T, want RecordEventPayload", payload)
		}
		recordPayload.Record[constants.FieldSysListView_Visibility] = "Private"
		return nil
	}}

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "list view before-update guard test")
	err := ps.Update(ctx, constants.TableListView, "lv-1", models.SObject{
		constants.FieldSysListView_Label: "Private Accounts",
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Update = nil, want final list-view ownership field rejection")
	}
	fls, ok := appErrors.AsFLSDenied(err)
	if !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if !containsString(fls.Fields, constants.FieldSysListView_Visibility) {
		t.Fatalf("FLSDenied fields = %#v, want %s", fls.Fields, constants.FieldSysListView_Visibility)
	}
	if repo.updated != nil {
		t.Fatalf("Update reached repo.Update with %#v, want fail-closed before SQL write", repo.updated)
	}
}

func TestDeleteRecordsRejectsListViewResolvedGenericMutation(t *testing.T) {
	repo := &captureRecordRepo{
		recordTypes: map[string]string{"lv-1": constants.TableListView},
		old: models.SObject{
			constants.FieldID:                        "lv-1",
			constants.FieldSysListView_ObjectAPIName: "Account",
			constants.FieldSysListView_Label:         "My Accounts",
		},
	}
	ps := newRecordEntrypointTestService(listViewOwnershipGuardSchema(), repo)

	results, err := ps.DeleteRecords(context.Background(), []string{"lv-1"}, false, recordEntrypointUser())
	if err != nil {
		t.Fatalf("DeleteRecords(_System_ListView) top-level error = %v, want per-record invalid operation", err)
	}
	if len(results) != 1 || results[0].Success || len(results[0].Errors) != 1 {
		t.Fatalf("DeleteRecords(_System_ListView) results = %#v, want one failed result", results)
	}
	if results[0].Errors[0].Kind != contract.RecordMutationErrorInvalidOperation {
		t.Fatalf("DeleteRecords(_System_ListView) error kind = %s, want %s", results[0].Errors[0].Kind, contract.RecordMutationErrorInvalidOperation)
	}
	if repo.deletedID != "" {
		t.Fatalf("DeleteRecords(_System_ListView) reached repo.Delete(%q), want fail before SQL write", repo.deletedID)
	}
}

func TestInsertRejectsSystemFieldOwnerKindPublicPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableField,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_OwnerKind, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_OwnerPack, Type: constants.FieldTypeString},
		},
	})

	_, err := ps.Insert(context.Background(), constants.TableField, models.SObject{
		constants.FieldSysField_OwnerKind: constants.FieldOwnerKindPackRemoving,
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Insert = nil, want immutable ownership rejection")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.insertCalled {
		t.Fatal("Insert reached repo.Insert, want early rejection")
	}
}

func TestInsertRejectsEmailTemplateOwnerPackSystemContext(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableEmailTemplate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysEmailTemplate_Name, Type: constants.FieldTypeString, Createable: true},
			{APIName: constants.FieldSysEmailTemplate_OwnerPack, Type: constants.FieldTypeString, Createable: true},
		},
	})

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "record-triggered flow execution")
	_, err := ps.Insert(ctx, constants.TableEmailTemplate, models.SObject{
		constants.FieldSysEmailTemplate_Name:      "Managed welcome",
		constants.FieldSysEmailTemplate_OwnerPack: "crm",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileSystemAdmin})
	if err == nil {
		t.Fatal("Insert = nil, want email template OwnerPack rejection under SystemContext")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.insertCalled {
		t.Fatal("Insert reached repo.Insert, want early rejection")
	}
}

func TestBulkInsertRejectsSystemFieldOwnerPackPublicPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableField,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_OwnerKind, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_OwnerPack, Type: constants.FieldTypeString},
		},
	})

	_, err := ps.BulkInsert(context.Background(), constants.TableField, []models.SObject{{
		constants.FieldSysField_OwnerPack: "jira-like-sample",
	}}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser}, BulkInsertOptions{})
	if err == nil {
		t.Fatal("BulkInsert = nil, want immutable ownership rejection")
	}
	if repo.bulkInsertCalled {
		t.Fatal("BulkInsert reached repo.BulkInsert, want early rejection")
	}
}

func TestGenericWritesRejectBulkControlTables(t *testing.T) {
	tests := []string{
		constants.TableBulkJob,
		constants.TableBulkBatch,
		strings.ToLower(constants.TableBulkJob),
	}

	for _, objectName := range tests {
		t.Run(objectName, func(t *testing.T) {
			err := rejectToolingOnlyTableWrites(objectName)
			if err == nil {
				t.Fatal("rejectToolingOnlyTableWrites = nil, want bulk control table generic write rejection")
			}
			if _, ok := appErrors.AsFLSDenied(err); !ok {
				t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
			}
		})
	}
}

func TestUpdateRejectsPackOwnedEmailTemplateGenericPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableEmailTemplate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysEmailTemplate_Subject, Type: constants.FieldTypeString, Updateable: true},
			{APIName: constants.FieldSysEmailTemplate_OwnerPack, Type: constants.FieldTypeString, Updateable: true},
		},
	})
	repo.findOneFn = func(ctx context.Context, t string, id string) (models.SObject, error) {
		return models.SObject{
			constants.FieldID:                             id,
			constants.FieldSysEmailTemplate_OwnerPack:     "crm",
			constants.FieldSysEmailTemplate_DeveloperName: "Welcome",
		}, nil
	}

	err := ps.Update(context.Background(), constants.TableEmailTemplate, "email_template_1", models.SObject{
		constants.FieldSysEmailTemplate_Subject: "Changed",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Update = nil, want pack-owned email template rejection")
	}
	if !strings.Contains(err.Error(), "owned by pack") {
		t.Fatalf("Update error = %v, want pack ownership rejection", err)
	}
	if repo.updateCalled {
		t.Fatal("Update reached repo.Update, want early rejection")
	}
}

func TestDeleteRejectsPackOwnedEmailTemplateGenericPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableEmailTemplate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysEmailTemplate_OwnerPack, Type: constants.FieldTypeString},
		},
	})
	repo.findOneFn = func(ctx context.Context, t string, id string) (models.SObject, error) {
		return models.SObject{
			constants.FieldID:                         id,
			constants.FieldSysEmailTemplate_OwnerPack: "crm",
		}, nil
	}

	err := ps.Delete(context.Background(), constants.TableEmailTemplate, "email_template_1", &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Delete = nil, want pack-owned email template rejection")
	}
	if !strings.Contains(err.Error(), "owned by pack") {
		t.Fatalf("Delete error = %v, want pack ownership rejection", err)
	}
	if repo.deleteCalled {
		t.Fatal("Delete reached repo.Delete, want early rejection")
	}
}

func TestUpdateRejectsSystemFieldOwnerKindPublicPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableField,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_OwnerKind, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_OwnerPack, Type: constants.FieldTypeString},
		},
	})

	err := ps.Update(context.Background(), constants.TableField, "fld-1", models.SObject{
		constants.FieldSysField_OwnerKind: constants.FieldOwnerKindPackRemoving,
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Update = nil, want immutable ownership rejection")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.updateCalled {
		t.Fatal("Update reached repo.Update, want early rejection")
	}
}

func TestUpdateRejectsSystemFieldCascadeGraphFieldsSystemContext(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableField,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysField_ReferenceTo, Type: constants.FieldTypeString, Updateable: true},
			{APIName: constants.FieldSysField_DeleteRule, Type: constants.FieldTypeString, Updateable: true},
			{APIName: constants.FieldSysField_IsMasterDetail, Type: constants.FieldTypeBoolean, Updateable: true},
			{APIName: constants.FieldSysField_IsPolymorphic, Type: constants.FieldTypeBoolean, Updateable: true},
		},
	})

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "record-triggered flow execution")
	err := ps.Update(ctx, constants.TableField, "fld-1", models.SObject{
		constants.FieldSysField_ReferenceTo:    `["Other__c"]`,
		constants.FieldSysField_DeleteRule:     string(constants.DeleteRuleSetNull),
		constants.FieldSysField_IsMasterDetail: false,
		constants.FieldSysField_IsPolymorphic:  true,
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileSystemAdmin})
	if err == nil {
		t.Fatal("Update = nil, want cascade graph field rejection under SystemContext")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.updateCalled {
		t.Fatal("Update reached repo.Update, want early rejection")
	}
}

func TestUpdateRejectsSystemObjectExclusiveSiblingFieldsPublicPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableObject,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFields, Type: constants.FieldTypeString, Updateable: true},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFieldsCustomized, Type: constants.FieldTypeBoolean, Updateable: true},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFieldsPackValue, Type: constants.FieldTypeString, Updateable: true},
		},
	})

	err := ps.Update(context.Background(), constants.TableObject, "obj-1", models.SObject{
		constants.FieldSysObject_ExclusiveSiblingFields:           `[{"fieldApiName":"IsDefault__c","partitionFieldApiName":"Parent__c"}]`,
		constants.FieldSysObject_ExclusiveSiblingFieldsCustomized: true,
		constants.FieldSysObject_ExclusiveSiblingFieldsPackValue:  "[]",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Update = nil, want immutable ExclusiveSiblingFields rejection")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.updateCalled {
		t.Fatal("Update reached repo.Update, want early rejection")
	}
}

func TestUpdateRejectsSystemObjectExclusiveSiblingFieldsSystemContext(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableObject,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFields, Type: constants.FieldTypeString, Updateable: true},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFieldsCustomized, Type: constants.FieldTypeBoolean, Updateable: true},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFieldsPackValue, Type: constants.FieldTypeString, Updateable: true},
		},
	})

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "record-triggered flow execution")
	err := ps.Update(ctx, constants.TableObject, "obj-1", models.SObject{
		constants.FieldSysObject_ExclusiveSiblingFields:           `[{"fieldApiName":"IsDefault__c","partitionFieldApiName":"Parent__c"}]`,
		constants.FieldSysObject_ExclusiveSiblingFieldsCustomized: true,
		constants.FieldSysObject_ExclusiveSiblingFieldsPackValue:  "[]",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileSystemAdmin})
	if err == nil {
		t.Fatal("Update = nil, want immutable ExclusiveSiblingFields rejection under SystemContext")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.updateCalled {
		t.Fatal("Update reached repo.Update, want early rejection")
	}
}

func TestInsertRejectsSystemObjectExclusiveSiblingFieldsElevatedContext(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: constants.TableObject,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFieldsPackName, Type: constants.FieldTypeString, Createable: true},
			{APIName: constants.FieldSysObject_ExclusiveSiblingFieldsPackBusinessKey, Type: constants.FieldTypeString, Createable: true},
		},
	})

	ctx := constants.WithSecurityContext(context.Background(), constants.ElevatedContext, "trusted generic record writer")
	_, err := ps.Insert(ctx, constants.TableObject, models.SObject{
		constants.FieldSysObject_ExclusiveSiblingFieldsPackName:        "crm",
		constants.FieldSysObject_ExclusiveSiblingFieldsPackBusinessKey: "crm-objects",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileSystemAdmin})
	if err == nil {
		t.Fatal("Insert = nil, want immutable ExclusiveSiblingFields sidecar rejection under ElevatedContext")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.insertCalled {
		t.Fatal("Insert reached repo.Insert, want early rejection")
	}
}

func TestInsertRejectsInstalledPackStatusPublicPath(t *testing.T) {
	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: "_System_InstalledPack",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldStatus, Type: constants.FieldTypeString},
		},
	})

	_, err := ps.Insert(context.Background(), "_System_InstalledPack", models.SObject{
		constants.FieldStatus: "removing",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Insert = nil, want installed-pack status rejection")
	}
	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("expected *appErrors.FLSDeniedError, got %T: %v", err, err)
	}
	if repo.insertCalled {
		t.Fatal("Insert reached repo.Insert, want early rejection")
	}
}

type immutableWritePathRecordRepo struct {
	stubUpsertRecordRepo
	insertCalled     bool
	bulkInsertCalled bool
	updateCalled     bool
	deleteCalled     bool
	findOneFn        func(ctx context.Context, t string, id string) (models.SObject, error)
}

func (r *immutableWritePathRecordRepo) Insert(ctx context.Context, t string, rec models.SObject) error {
	r.insertCalled = true
	return nil
}

func (r *immutableWritePathRecordRepo) BulkInsert(ctx context.Context, t string, recs []models.SObject, cols []string, opts interface{}) error {
	r.bulkInsertCalled = true
	return nil
}

func (r *immutableWritePathRecordRepo) Update(ctx context.Context, t string, id string, u models.SObject) error {
	r.updateCalled = true
	return nil
}

func (r *immutableWritePathRecordRepo) Delete(ctx context.Context, t string, id string) error {
	r.deleteCalled = true
	return nil
}

func (r *immutableWritePathRecordRepo) FindOne(ctx context.Context, t string, id string) (models.SObject, error) {
	if r.findOneFn != nil {
		return r.findOneFn(ctx, t, id)
	}
	return nil, nil
}

func newImmutableWritePathService(schema *models.ObjectMetadata) (*PersistenceService, *immutableWritePathRecordRepo) {
	recordRepo := &immutableWritePathRecordRepo{}
	metaSvc := metadata.NewMetadataService(&stubUpsertMetaRepo{schema: schema}, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantEdit: true, grantCreate: true, grantDelete: true}, nil, nil)
	return &PersistenceService{
		repo:            recordRepo,
		metadata:        metaSvc,
		permissions:     permSvc,
		schemaValidator: validation.NewSchemaValidator(),
	}, recordRepo
}

func listViewOwnershipGuardSchema() *models.ObjectMetadata {
	schema := &models.ObjectMetadata{
		ID:           "obj_list_view",
		APIName:      constants.TableListView,
		Label:        "List View",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, Createable: false, Updateable: false},
			{APIName: constants.FieldSysListView_ObjectAPIName, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSysListView_Label, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSysListView_FilterExpr, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSysListView_Fields, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSysListView_OwnerID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSysListView_Visibility, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSysListView_DeveloperName, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldCreatedDate, Type: constants.FieldTypeDatetime, Createable: false, Updateable: false},
			{APIName: constants.FieldSysListView_LastModifiedDate, Type: constants.FieldTypeDatetime, Createable: false, Updateable: false},
		},
	}
	makeFieldsMap(schema)
	return schema
}

func containsString(values []string, want string) bool {
	for _, value := range values {
		if value == want {
			return true
		}
	}
	return false
}
