package permissions

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldpermissions"
	"github.com/nexuscrm/shared/pkg/models"
)

type flsFallbackRepo struct {
	saveTestRepo
	objectPerm           *models.ObjectPermissions
	effectiveObjectPerms []models.ObjectPermissions
	fieldPerms           map[string]*models.FieldPermissions
	systemPerms          map[string]bool
}

func (r *flsFallbackRepo) LoadEffectiveObjectPermission(_ context.Context, _ *models.UserSession, _ string) (*models.ObjectPermissions, error) {
	return r.objectPerm, nil
}

func (r *flsFallbackRepo) ListEffectiveObjectPermissionsForUser(context.Context, string, string) ([]models.ObjectPermissions, error) {
	return append([]models.ObjectPermissions(nil), r.effectiveObjectPerms...), nil
}

func (r *flsFallbackRepo) LoadEffectiveFieldPermission(_ context.Context, _ *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	for raw, perm := range r.fieldPerms {
		if fieldpermissions.MatchesFieldReference(objectAPIName, raw, fieldAPIName) {
			cp := *perm
			return &cp, nil
		}
	}
	return nil, nil
}

func (r *flsFallbackRepo) ListEffectiveFieldPermissionsForUserObject(_ context.Context, _, _, objectAPIName string) ([]models.FieldPermissions, error) {
	perms := make([]models.FieldPermissions, 0, len(r.fieldPerms))
	for raw, perm := range r.fieldPerms {
		rowObject, rowField := fieldpermissions.SplitFieldReference(objectAPIName, raw)
		if !strings.EqualFold(strings.TrimSpace(rowObject), strings.TrimSpace(objectAPIName)) {
			continue
		}
		cp := *perm
		if strings.TrimSpace(cp.SobjectType) == "" {
			cp.SobjectType = objectAPIName
		}
		if strings.TrimSpace(cp.Field) == "" {
			cp.Field = rowField
		}
		perms = append(perms, cp)
	}
	return perms, nil
}

func (r *flsFallbackRepo) GetEffectiveSystemPermissions(context.Context, string, string) (map[string]bool, error) {
	if r.systemPerms == nil {
		return map[string]bool{}, nil
	}
	return r.systemPerms, nil
}

type flsMetadataStub struct {
	schema          *models.ObjectMetadata
	schemas         []*models.ObjectMetadata
	getSchemaCalls  []string
	getSchemasCalls int
}

type metadataReadBlockedField struct {
	objectAPIName string
	fieldAPIName  string
}

type bootstrapColumn struct {
	Name                 string `json:"name"`
	ReadProtectionPolicy string `json:"readProtectionPolicy"`
}

type bootstrapTable struct {
	TableName              string            `json:"tableName"`
	RequiredReadPermission string            `json:"requiredReadPermission"`
	Columns                []bootstrapColumn `json:"columns"`
}

var metadataReadBlockedFields = []metadataReadBlockedField{
	{objectAPIName: "_System_ConnectedApp", fieldAPIName: "ConsumerSecret"},
	{objectAPIName: "_System_ConnectedApp", fieldAPIName: "ConsumerSecretHash"},
	{objectAPIName: "_System_Session", fieldAPIName: "Token"},
	{objectAPIName: "_System_Session", fieldAPIName: "CurrentBearerToken"},
	{objectAPIName: "_System_OAuthCode", fieldAPIName: "Code"},
	{objectAPIName: "_System_OAuthCode", fieldAPIName: "CodeHash"},
	{objectAPIName: "_System_Webhook", fieldAPIName: "AuthConfig"},
	{objectAPIName: "_System_WebhookSecretValue", fieldAPIName: "SecretValue"},
	{objectAPIName: "_System_Config", fieldAPIName: "Value"},
	{objectAPIName: "User", fieldAPIName: "Password"},
}

func sensitiveFallbackFieldKey(objectAPIName, fieldAPIName string) string {
	return objectAPIName + "." + fieldAPIName
}

func bootstrapCredentialFieldRequiresReadProtection(table bootstrapTable, column bootstrapColumn) bool {
	if strings.EqualFold(strings.TrimSpace(table.RequiredReadPermission), "BLOCKED") {
		return false
	}
	return bootstrapCredentialFieldName(table.TableName, column.Name)
}

func bootstrapCredentialFieldName(tableName, fieldName string) bool {
	name := normalizeBootstrapCredentialName(fieldName)
	if name == "" || bootstrapCredentialNameException(tableName, fieldName) {
		return false
	}
	table := normalizeBootstrapCredentialName(tableName)

	if strings.Contains(name, "password") || strings.Contains(name, "secret") {
		return true
	}
	if bootstrapCredentialTokenName(name) {
		return true
	}
	if strings.Contains(name, "authorizationcode") || strings.Contains(name, "authcode") || strings.Contains(name, "oauthcode") {
		return true
	}
	if (name == "code" || name == "codehash") && strings.Contains(table, "oauthcode") {
		return true
	}
	if strings.Contains(name, "hash") {
		return strings.Contains(name, "password") || strings.Contains(name, "secret") ||
			strings.Contains(name, "token") || strings.Contains(name, "authcode") ||
			strings.Contains(name, "authorizationcode")
	}
	for _, suffix := range []string{"apikey", "privatekey", "secretkey", "authkey", "accesskey", "signingkey", "encryptionkey"} {
		if strings.HasSuffix(name, suffix) {
			return true
		}
	}
	return false
}

func normalizeBootstrapCredentialName(name string) string {
	replacer := strings.NewReplacer("_", "", "-", "", " ", "")
	return replacer.Replace(strings.ToLower(strings.TrimSpace(name)))
}

func bootstrapCredentialTokenName(name string) bool {
	return name == "token" || strings.HasSuffix(name, "token")
}

func bootstrapCredentialNameException(tableName, fieldName string) bool {
	switch sensitiveFallbackFieldKey(tableName, fieldName) {
	case "_System_ConnectedApp.ConsumerKey":
		return true
	}

	switch normalizeBootstrapCredentialName(fieldName) {
	case "issecret", "keyprefix", "tokenendpointauthmethod", "issuerefreshtokens", "autonumbertokenrefs",
		"tokencount", "tokentype", "publiccsrftoken":
		return true
	}
	if strings.HasSuffix(normalizeBootstrapCredentialName(fieldName), "id") {
		return true
	}
	return false
}

func bootstrapCredentialReadProtectionViolations(tables []bootstrapTable) []string {
	var violations []string
	for _, table := range tables {
		for _, column := range table.Columns {
			if !bootstrapCredentialFieldRequiresReadProtection(table, column) {
				continue
			}
			if strings.EqualFold(strings.TrimSpace(column.ReadProtectionPolicy), constants.FieldReadProtectionBlocked) {
				continue
			}
			violations = append(violations, sensitiveFallbackFieldKey(table.TableName, column.Name))
		}
	}
	return violations
}

func (m *flsMetadataStub) GetSchema(_ context.Context, apiName string) *models.ObjectMetadata {
	if m != nil {
		m.getSchemaCalls = append(m.getSchemaCalls, apiName)
	}
	if m != nil && m.schema != nil && m.schema.APIName == apiName {
		return m.schema
	}
	if m != nil {
		for _, schema := range m.schemas {
			if schema != nil && schema.APIName == apiName {
				return schema
			}
		}
	}
	return nil
}

func (m *flsMetadataStub) GetSchemas(context.Context) []*models.ObjectMetadata {
	if m == nil {
		return nil
	}
	m.getSchemasCalls++
	return append([]*models.ObjectMetadata(nil), m.schemas...)
}
func (m *flsMetadataStub) GetRelationships(context.Context, string) []*models.Relationship {
	return nil
}
func (m *flsMetadataStub) GetSharingParentRelationship(context.Context, string) *models.Relationship {
	return nil
}
func (m *flsMetadataStub) GetParentControlledObject(context.Context, *models.ObjectMetadata) string {
	return ""
}
func (m *flsMetadataStub) GetSharingRules(context.Context, string) []*models.SharingRule {
	return nil
}

func (m *flsMetadataStub) GetRecordTypes(context.Context, string) ([]*models.RecordType, error) {
	return nil, nil
}

func TestFieldChecksFallbackToObjectCRUDWhenEffectiveFieldPermissionMissing(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsRead: true, PermissionsCreate: true, PermissionsEdit: true},
	}
	svc := NewPermissionService(repo, nil, nil)

	if !mustCheckFieldVisibilityWithUser(t, svc, ctx, "Account", "Secret__c", user) {
		t.Fatal("CheckFieldVisibilityWithUser = false for missing FieldPermissions row with object read; want true")
	}
	if !mustCheckFieldEditabilityWithUser(t, svc, ctx, "Account", "Secret__c", user) {
		t.Fatal("CheckFieldEditabilityWithUser = false for missing FieldPermissions row with object edit; want true")
	}
}

func TestSchemaBoundFieldVisibilityReusesOneImmutableObject(t *testing.T) {
	ctx := WithAccessInputCache(context.Background())
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Visible__c"},
			{APIName: "Secret__c", ReadProtectionPolicy: constants.FieldReadProtectionBlocked},
		},
	}
	metadata := &flsMetadataStub{schema: schema}
	svc := NewPermissionService(&flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsRead: true},
		fieldPerms: map[string]*models.FieldPermissions{
			"Account.Visible__c": {SobjectType: "Account", Field: "Visible__c", PermissionsRead: true},
		},
	}, metadata, nil)

	visible, err := svc.CheckFieldVisibilityWithSchema(ctx, schema, models.FindField(schema, "Visible__c"), user)
	if err != nil || !visible {
		t.Fatalf("schema-bound visible decision = %v, %v; want true, nil", visible, err)
	}
	blocked, err := svc.CheckFieldVisibilityWithSchema(ctx, schema, models.FindField(schema, "Secret__c"), user)
	if err != nil || blocked {
		t.Fatalf("schema-bound blocked decision = %v, %v; want false, nil", blocked, err)
	}
	if got := len(metadata.getSchemaCalls); got != 0 {
		t.Fatalf("schema-bound decisions reloaded metadata %d times; want 0", got)
	}

	legacyVisible, err := svc.CheckFieldVisibilityWithUser(ctx, "Account", "Visible__c", user)
	if err != nil || legacyVisible != visible {
		t.Fatalf("legacy visible decision = %v, %v; want parity with schema-bound %v", legacyVisible, err, visible)
	}
	if got := len(metadata.getSchemaCalls); got != 1 {
		t.Fatalf("single-field compatibility path loaded metadata %d times; want exactly 1", got)
	}
}

func TestFieldChecksRespectExplicitEffectiveDeny(t *testing.T) {
	ctx := WithAccessInputCache(context.Background())
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsRead: true, PermissionsCreate: true, PermissionsEdit: true},
		fieldPerms: map[string]*models.FieldPermissions{
			"Account.Secret__c": {PermissionsRead: false, PermissionsEdit: false},
		},
	}
	svc := NewPermissionService(repo, nil, nil)

	if mustCheckFieldVisibilityWithUser(t, svc, ctx, "Account", "secret__c", user) {
		t.Fatal("CheckFieldVisibilityWithUser = true for explicit deny; want false")
	}
	if mustCheckFieldEditabilityWithUser(t, svc, ctx, "Account", "secret__c", user) {
		t.Fatal("CheckFieldEditabilityWithUser = true for explicit deny; want false")
	}
}

func TestFieldChecksBoundedProjectionUsesRepositoryDeny(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsRead: true, PermissionsCreate: true, PermissionsEdit: true},
		fieldPerms: map[string]*models.FieldPermissions{
			"Account.Secret__c": {PermissionsRead: true, PermissionsEdit: false},
		},
	}
	svc := NewPermissionService(repo, nil, nil)

	if !mustCheckFieldVisibilityWithUser(t, svc, ctx, "Account", "Secret__c", user) {
		t.Fatal("CheckFieldVisibilityWithUser = false with repo explicit read grant; want true")
	}
	if mustCheckFieldEditabilityWithUser(t, svc, ctx, "Account", "Secret__c", user) {
		t.Fatal("CheckFieldEditabilityWithUser = true from loaded cache miss; want repo explicit edit deny")
	}
}

func TestFieldChecksBoundedProjectionPreservesAbsentRowFallback(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsRead: true, PermissionsCreate: true, PermissionsEdit: true},
	}
	svc := NewPermissionService(repo, nil, nil)

	if !mustCheckFieldVisibilityWithUser(t, svc, ctx, "Account", "Missing__c", user) {
		t.Fatal("CheckFieldVisibilityWithUser = false for absent FLS row with object read; want #656 fallback")
	}
	if !mustCheckFieldEditabilityWithUser(t, svc, ctx, "Account", "Missing__c", user) {
		t.Fatal("CheckFieldEditabilityWithUser = false for absent FLS row with object edit; want #656 fallback")
	}
}

func TestFieldChecksSuperUserBypassesFLS(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "admin-1", ProfileID: "jira_admin"}
	svc := NewPermissionService(&flsFallbackRepo{systemPerms: superUserCapabilityGrant()}, nil, nil)

	if !mustCheckFieldVisibilityWithUser(t, svc, ctx, "Account", "Secret__c", user) {
		t.Fatal("CheckFieldVisibilityWithUser = false for super-user capability holder; want true")
	}
	if !mustCheckFieldEditabilityWithUser(t, svc, ctx, "Account", "Secret__c", user) {
		t.Fatal("CheckFieldEditabilityWithUser = false for super-user capability holder; want true")
	}
}

func TestFieldVisibilityHardBlocksConnectedAppSecretsEvenForSuperUserCapability(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "admin-1", ProfileID: "jira_admin"}
	metadata := &flsMetadataStub{
		schema: &models.ObjectMetadata{
			APIName: "_System_ConnectedApp",
			Fields: []models.FieldMetadata{
				{APIName: "ConsumerSecret", ReadProtectionPolicy: constants.FieldReadProtectionBlocked},
				{APIName: "ConsumerSecretHash", ReadProtectionPolicy: constants.FieldReadProtectionBlocked},
			},
		},
	}
	svc := NewPermissionService(&flsFallbackRepo{systemPerms: superUserCapabilityGrant()}, metadata, nil)

	for _, field := range []string{"ConsumerSecret", "ConsumerSecretHash"} {
		if mustCheckFieldVisibilityWithUser(t, svc, ctx, "_System_ConnectedApp", field, user) {
			t.Fatalf("%s visible through generic FLS, want hard block", field)
		}
	}
}

func TestFieldVisibilityBlocksReadProtectionPolicyEvenForSuperUserCapability(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "admin-1", ProfileID: "jira_admin"}
	metadata := &flsMetadataStub{
		schema: &models.ObjectMetadata{
			APIName: "Issue",
			Fields: []models.FieldMetadata{
				{APIName: "SecretText", ReadProtectionPolicy: constants.FieldReadProtectionBlocked},
			},
		},
	}
	svc := NewPermissionService(&flsFallbackRepo{systemPerms: superUserCapabilityGrant()}, metadata, nil)

	if !svc.IsFieldReadBlocked(ctx, "Issue", "SecretText") {
		t.Fatal("IsFieldReadBlocked = false for metadata read-protected field; want true")
	}
	if mustCheckFieldVisibilityWithUser(t, svc, ctx, "Issue", "SecretText", user) {
		t.Fatal("CheckFieldVisibilityWithUser = true for metadata read-protected field; want hard block")
	}
}

func TestFieldVisibilityBlocksBackfilledLegacyWebhookSecretKey(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "admin-1", ProfileID: "jira_admin"}
	metadata := &flsMetadataStub{
		schema: &models.ObjectMetadata{
			APIName: "_System_Webhook",
			Fields: []models.FieldMetadata{
				{APIName: "SecretKey", ReadProtectionPolicy: constants.FieldReadProtectionBlocked},
			},
		},
	}
	svc := NewPermissionService(&flsFallbackRepo{systemPerms: superUserCapabilityGrant()}, metadata, nil)

	if !svc.IsFieldReadBlocked(ctx, "_System_Webhook", "SecretKey") {
		t.Fatal("IsFieldReadBlocked = false for backfilled _System_Webhook.SecretKey readProtectionPolicy")
	}
	if mustCheckFieldVisibilityWithUser(t, svc, ctx, "_System_Webhook", "SecretKey", user) {
		t.Fatal("CheckFieldVisibilityWithUser allowed backfilled _System_Webhook.SecretKey")
	}
}

func TestFieldVisibilityDoesNotUseHardcodedCredentialNameFallback(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "admin-1", ProfileID: "jira_admin"}
	metadata := &flsMetadataStub{
		schema: &models.ObjectMetadata{
			APIName: "_System_ConnectedApp",
			Fields: []models.FieldMetadata{
				{APIName: "ConsumerSecret"},
			},
		},
	}
	svc := NewPermissionService(&flsFallbackRepo{systemPerms: superUserCapabilityGrant()}, metadata, nil)

	if svc.IsFieldReadBlocked(ctx, "_System_ConnectedApp", "ConsumerSecret") {
		t.Fatal("IsFieldReadBlocked = true without ReadProtectionPolicy; concrete field membership must come from metadata")
	}
	if !mustCheckFieldVisibilityWithUser(t, svc, ctx, "_System_ConnectedApp", "ConsumerSecret", user) {
		t.Fatal("CheckFieldVisibilityWithUser = false without ReadProtectionPolicy; hardcoded sensitive fallback is still active")
	}
}

func TestBootstrapCredentialNameGuardCoversFutureReadableSecretField(t *testing.T) {
	tables := []bootstrapTable{{
		TableName: "_System_ConnectedApp",
		Columns: []bootstrapColumn{
			{Name: "ClientSecret"},
			{Name: "ApiToken"},
		},
	}}

	violations := bootstrapCredentialReadProtectionViolations(tables)
	wantViolations := []string{"_System_ConnectedApp.ClientSecret", "_System_ConnectedApp.ApiToken"}
	if strings.Join(violations, ",") != strings.Join(wantViolations, ",") {
		t.Fatalf("credential-name guard violations = %#v, want %#v", violations, wantViolations)
	}
}

func TestBootstrapCredentialNameGuardDocumentsIdentifierExceptions(t *testing.T) {
	cases := []struct {
		name   string
		table  bootstrapTable
		column bootstrapColumn
	}{
		{
			name:   "public connected app consumer key",
			table:  bootstrapTable{TableName: "_System_ConnectedApp"},
			column: bootstrapColumn{Name: "ConsumerKey"},
		},
		{
			name:   "token issuance boolean",
			table:  bootstrapTable{TableName: "_System_ConnectedApp"},
			column: bootstrapColumn{Name: "IssueRefreshTokens"},
		},
		{
			name:   "token endpoint auth method enum",
			table:  bootstrapTable{TableName: "_System_ConnectedApp"},
			column: bootstrapColumn{Name: "TokenEndpointAuthMethod"},
		},
		{
			name:   "metadata key prefix",
			table:  bootstrapTable{TableName: "_System_Object"},
			column: bootstrapColumn{Name: "KeyPrefix"},
		},
		{
			name:   "payload integrity hash",
			table:  bootstrapTable{TableName: "_System_InstalledPack"},
			column: bootstrapColumn{Name: "DesiredPayloadHash"},
		},
		{
			name:   "auto number token reference metadata",
			table:  bootstrapTable{TableName: "_System_Field"},
			column: bootstrapColumn{Name: "auto_number_token_refs"},
		},
		{
			name:   "token count metadata",
			table:  bootstrapTable{TableName: "_System_ConnectedApp"},
			column: bootstrapColumn{Name: "TokenCount"},
		},
		{
			name:   "token type metadata",
			table:  bootstrapTable{TableName: "_System_ConnectedApp"},
			column: bootstrapColumn{Name: "TokenType"},
		},
		{
			name:   "public csrf token marker",
			table:  bootstrapTable{TableName: "_System_ConnectedApp"},
			column: bootstrapColumn{Name: "PublicCsrfToken"},
		},
		{
			name:   "blocked table credential value",
			table:  bootstrapTable{TableName: "_System_NamedCredentialSecret", RequiredReadPermission: "BLOCKED"},
			column: bootstrapColumn{Name: "ClientSecret"},
		},
	}

	for _, tc := range cases {
		if bootstrapCredentialFieldRequiresReadProtection(tc.table, tc.column) {
			t.Fatalf("%s required read protection; want documented identifier/blocked-table exception", tc.name)
		}
	}
}

func TestBootstrapCredentialFieldsDeclareReadProtectionPolicy(t *testing.T) {
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	path := filepath.Join(filepath.Dir(file), "..", "nexuscrm-kernel", "pkg", "schemacatalog", "system_tables.json")
	payload, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("ReadFile(%s): %v", path, err)
	}

	var tables []bootstrapTable
	if err := json.Unmarshal(payload, &tables); err != nil {
		t.Fatalf("Unmarshal(%s): %v", path, err)
	}

	columnsByField := map[string]bootstrapColumn{}
	for _, table := range tables {
		for _, column := range table.Columns {
			columnsByField[sensitiveFallbackFieldKey(table.TableName, column.Name)] = column
		}
	}

	for _, field := range metadataReadBlockedFields {
		column, ok := columnsByField[sensitiveFallbackFieldKey(field.objectAPIName, field.fieldAPIName)]
		if !ok {
			t.Fatalf("%s.%s is expected to be read-protected but is missing from bootstrap metadata",
				field.objectAPIName, field.fieldAPIName)
		}
		if !strings.EqualFold(strings.TrimSpace(column.ReadProtectionPolicy), constants.FieldReadProtectionBlocked) {
			t.Fatalf("%s.%s readProtectionPolicy = %q, want %q",
				field.objectAPIName, field.fieldAPIName, column.ReadProtectionPolicy, constants.FieldReadProtectionBlocked)
		}
	}

	for _, violation := range bootstrapCredentialReadProtectionViolations(tables) {
		t.Fatalf("%s has a credential-looking name on a readable bootstrap object but readProtectionPolicy is not %q",
			violation, constants.FieldReadProtectionBlocked)
	}
}

func TestFieldAccessResolverFallsBackToObjectCreateWhenFLSMissing(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	field := &models.FieldMetadata{APIName: "Secret__c", Createable: true, Updateable: true}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsCreate: true},
	}
	svc := NewPermissionService(repo, nil, nil)
	resolver := NewFieldAccessResolver(svc)

	if !mustEffectiveCreateable(t, resolver, ctx, field, "Account", user) {
		t.Fatal("EffectiveCreateable = false without explicit field edit grant and with object create; want true")
	}

	repo.fieldPerms = map[string]*models.FieldPermissions{
		"Secret__c": {PermissionsEdit: false},
	}
	if mustEffectiveCreateable(t, resolver, ctx, field, "Account", user) {
		t.Fatal("EffectiveCreateable = true with explicit field edit deny; want false")
	}
}

func TestFieldAccessResolverBoundedProjectionUsesRepositoryDeny(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	field := &models.FieldMetadata{APIName: "Secret__c", Createable: true, Updateable: true}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{PermissionsCreate: true},
		fieldPerms: map[string]*models.FieldPermissions{
			"Account.Secret__c": {PermissionsRead: true, PermissionsEdit: false},
		},
	}
	svc := NewPermissionService(repo, nil, nil)
	resolver := NewFieldAccessResolver(svc)

	if mustEffectiveCreateable(t, resolver, ctx, field, "Account", user) {
		t.Fatal("EffectiveCreateable = true from loaded cache miss; want repo explicit edit deny")
	}
}

func TestFieldAccessResolverPreservesDerivedAccessPolicyException(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	field := &models.FieldMetadata{APIName: "DerivedField", Createable: true}
	repo := &flsFallbackRepo{
		systemPerms: map[string]bool{"DerivedCreate": true},
	}
	metadata := &flsMetadataStub{
		schema: &models.ObjectMetadata{
			APIName: "DerivedObject",
			AccessPolicy: &models.ObjectAccessPolicy{
				Mode:         "derived",
				ObjectCreate: &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "DerivedCreate"},
			},
		},
	}
	svc := NewPermissionService(repo, metadata, nil)
	resolver := NewFieldAccessResolver(svc)

	if !mustEffectiveCreateable(t, resolver, ctx, field, "DerivedObject", user) {
		t.Fatal("EffectiveCreateable = false for derived access-policy object; want true")
	}
}

func TestEffectiveAccessAppliesVirtualFieldOverrides(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	repo := &flsFallbackRepo{
		objectPerm: &models.ObjectPermissions{
			PermissionsRead:   true,
			PermissionsCreate: true,
			PermissionsEdit:   false,
			PermissionsDelete: false,
		},
		fieldPerms: map[string]*models.FieldPermissions{
			"Widget.Physical__c":             {PermissionsRead: true, PermissionsEdit: true},
			"Widget.PermissionsViewSetup":    {PermissionsRead: true, PermissionsEdit: true},
			"Widget.BillingAddress":          {PermissionsRead: true, PermissionsEdit: true},
			"Widget.ServerDeniedPhysical__c": {PermissionsRead: false, PermissionsEdit: false},
		},
	}
	metadata := &flsMetadataStub{
		schema: &models.ObjectMetadata{
			APIName: "Widget",
			Fields: []models.FieldMetadata{
				{APIName: "Physical__c", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: "ServerDeniedPhysical__c", Type: constants.FieldTypeString, Createable: true, Updateable: true},
				{APIName: "PermissionsViewSetup", Type: constants.FieldTypeBoolean, Createable: true, Updateable: true, IsVirtual: true, VirtualKind: constants.VirtualFieldSystemPermission},
				{APIName: "BillingAddress", Type: constants.FieldTypeString, Createable: true, Updateable: true, IsVirtual: true, VirtualKind: constants.VirtualFieldCompound},
			},
		},
	}
	svc := NewPermissionService(repo, metadata, nil)

	access, err := svc.EffectiveAccess(ctx, user, "Widget")
	if err != nil {
		t.Fatalf("EffectiveAccess() error = %v", err)
	}
	if access.Object != (models.ObjectAccess{Create: true, Read: true, Update: false, Delete: false}) {
		t.Fatalf("object access = %+v, want C/R true and U/D false", access.Object)
	}
	if got := access.Fields["Physical__c"]; !got.Createable || got.Updateable || !got.Readable {
		t.Fatalf("Physical__c access = %+v, want createable/readable with update denied by object edit", got)
	}
	if got := access.Fields["ServerDeniedPhysical__c"]; got.Createable || got.Updateable || got.Readable {
		t.Fatalf("ServerDeniedPhysical__c access = %+v, want explicit field deny", got)
	}
	if got := access.Fields["PermissionsViewSetup"]; !got.Createable || got.Updateable || !got.Readable {
		t.Fatalf("PermissionsViewSetup access = %+v, want object-level CRUD projection", got)
	}
	if got := access.Fields["BillingAddress"]; got.Createable || got.Updateable || !got.Readable {
		t.Fatalf("BillingAddress access = %+v, want compound virtual override", got)
	}
}

func TestUserDeleteLifecycleGuardSuppressesDeleteabilityProjections(t *testing.T) {
	ctx := context.Background()
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	userSchema := &models.ObjectMetadata{
		APIName:                 constants.TableUser,
		RequiredWritePermission: string(constants.PermManageUsers),
		AccessPolicy: &models.ObjectAccessPolicy{
			WriteValidation: &models.RecordWriteValidationAccessPolicy{
				Validators: []models.RecordWriteValidationPolicy{{
					Phase:      "before_delete",
					Capability: userDeleteLifecycleWriteCapability,
				}},
			},
		},
	}
	granted := models.ObjectPermissions{
		SobjectType:       constants.TableUser,
		PermissionsRead:   true,
		PermissionsCreate: true,
		PermissionsEdit:   true,
		PermissionsDelete: true,
	}
	repo := &flsFallbackRepo{
		objectPerm:           &granted,
		effectiveObjectPerms: []models.ObjectPermissions{granted},
		systemPerms: map[string]bool{
			string(constants.PermManageUsers): true,
		},
	}
	metadata := &flsMetadataStub{
		schema:  userSchema,
		schemas: []*models.ObjectMetadata{userSchema},
	}
	svc := NewPermissionService(repo, metadata, nil)

	if svc.CheckObjectPermissionWithUser(ctx, constants.TableUser, constants.PermDelete, user) {
		t.Fatal("CheckObjectPermissionWithUser(Delete User) = true, want false from lifecycle metadata")
	}
	if !svc.CheckUserDeactivationPermissionWithUser(ctx, user) {
		t.Fatal("CheckUserDeactivationPermissionWithUser() = false, want true from Delete User grant")
	}
	access, err := svc.EffectiveAccess(ctx, user, constants.TableUser)
	if err != nil {
		t.Fatalf("EffectiveAccess() error = %v", err)
	}
	if access.Object.Delete {
		t.Fatalf("EffectiveAccess Delete = true, want false")
	}
	if !access.Object.Deactivate {
		t.Fatalf("EffectiveAccess Deactivate = false, want true")
	}
	if !access.Object.Read || !access.Object.Create || !access.Object.Update {
		t.Fatalf("EffectiveAccess non-delete grants = %+v, want read/create/update preserved", access.Object)
	}

	projected := svc.runtimeProjectedObjectPermission(ctx, constants.TableUser, user)
	if projected.PermissionsDelete {
		t.Fatalf("runtimeProjectedObjectPermission PermissionsDelete = true, want false")
	}
	if !projected.PermissionsRead || !projected.PermissionsCreate || !projected.PermissionsEdit {
		t.Fatalf("runtimeProjectedObjectPermission non-delete grants = %+v, want read/create/edit preserved", projected)
	}

	listed, err := svc.ListEffectiveObjectPermissionsForUser(ctx, user.ID, user.ProfileID)
	if err != nil {
		t.Fatalf("ListEffectiveObjectPermissionsForUser() error = %v", err)
	}
	if len(listed) != 1 {
		t.Fatalf("ListEffectiveObjectPermissionsForUser rows = %d, want 1: %#v", len(listed), listed)
	}
	if listed[0].PermissionsDelete {
		t.Fatalf("ListEffectiveObjectPermissionsForUser PermissionsDelete = true, want false")
	}
	if !listed[0].PermissionsRead || !listed[0].PermissionsCreate || !listed[0].PermissionsEdit {
		t.Fatalf("ListEffectiveObjectPermissionsForUser non-delete grants = %+v, want read/create/edit preserved", listed[0])
	}
}

func TestGenericDeleteSuppressionUsesBulkMetadataForStaleRows(t *testing.T) {
	ctx := context.Background()
	lifecycleSchema := &models.ObjectMetadata{
		APIName: "LifecycleObject",
		AccessPolicy: &models.ObjectAccessPolicy{
			WriteValidation: &models.RecordWriteValidationAccessPolicy{
				Validators: []models.RecordWriteValidationPolicy{{
					Phase:      "before_delete",
					Capability: userDeleteLifecycleWriteCapability,
				}},
			},
		},
	}
	repo := &flsFallbackRepo{
		effectiveObjectPerms: []models.ObjectPermissions{
			{
				SobjectType:       lifecycleSchema.APIName,
				PermissionsRead:   true,
				PermissionsDelete: true,
			},
			{
				SobjectType:       "RemovedPackageObject__c",
				PermissionsRead:   true,
				PermissionsDelete: true,
			},
		},
	}
	metadata := &flsMetadataStub{schemas: []*models.ObjectMetadata{lifecycleSchema}}
	svc := NewPermissionService(repo, metadata, nil)

	listed, err := svc.ListEffectiveObjectPermissionsForUser(ctx, "user-1", "profile-1")
	if err != nil {
		t.Fatalf("ListEffectiveObjectPermissionsForUser() error = %v", err)
	}
	if len(listed) != 2 {
		t.Fatalf("ListEffectiveObjectPermissionsForUser rows = %d, want 2: %#v", len(listed), listed)
	}
	lifecycle, ok := objectPermByType(listed, lifecycleSchema.APIName)
	if !ok {
		t.Fatalf("missing lifecycle object permission: %#v", listed)
	}
	if lifecycle.PermissionsDelete {
		t.Fatalf("lifecycle object PermissionsDelete = true, want false")
	}
	if !lifecycle.PermissionsRead {
		t.Fatalf("lifecycle object PermissionsRead = false, want true")
	}
	stale, ok := objectPermByType(listed, "RemovedPackageObject__c")
	if !ok {
		t.Fatalf("missing stale object permission: %#v", listed)
	}
	if !stale.PermissionsRead || !stale.PermissionsDelete {
		t.Fatalf("stale object grants = %#v, want read and delete preserved", stale)
	}
	if len(metadata.getSchemaCalls) != 0 {
		t.Fatalf("generic delete suppression metadata point lookups = %v, want none", metadata.getSchemaCalls)
	}
	if metadata.getSchemasCalls != 1 {
		t.Fatalf("bulk metadata snapshot calls = %d, want 1", metadata.getSchemasCalls)
	}
}
