package permissions

// rls_test.go — Test plan for Section 3: RLS Unification (BuildRLSResult + adapters)
//
// Design ref: SECURITY_REFACTOR_DESIGN.md §3
// Coverage target: BuildRLSResult (all 7 layers) and RLSResultToFilterExpr
//
// All tests are pure unit tests using the existing mockPermRepo / mockUserRepo /
// newTestService helpers defined in permissions_unit_test.go.  No database required.
//
// HOW TO RUN:
//   go test ./internal/features/permissions/ -run TestRLS -v

import (
	"context"
	"strings"
	"testing"

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

// ---------------------------------------------------------------------------
// mockMetadata — minimal metadataProvider for RLS unit tests
// ---------------------------------------------------------------------------

type mockMetadata struct {
	schemas       map[string]*models.ObjectMetadata
	relationships map[string][]*models.Relationship
	sharingRules  map[string][]*models.SharingRule
}

func (m *mockMetadata) GetSchema(_ context.Context, apiName string) *models.ObjectMetadata {
	if m.schemas == nil {
		return nil
	}
	return m.schemas[apiName]
}

func (m *mockMetadata) GetRelationships(_ context.Context, objectAPIName string) []*models.Relationship {
	if m.relationships == nil {
		return nil
	}
	return m.relationships[objectAPIName]
}

func (m *mockMetadata) GetSharingParentRelationship(_ context.Context, childObjectAPIName string) *models.Relationship {
	if m.relationships == nil {
		return nil
	}
	for _, rel := range m.relationships[childObjectAPIName] {
		if rel != nil && rel.IsSharingParent {
			return rel
		}
	}
	return nil
}

func (m *mockMetadata) GetSchemas(_ context.Context) []*models.ObjectMetadata {
	result := make([]*models.ObjectMetadata, 0, len(m.schemas))
	for _, s := range m.schemas {
		result = append(result, s)
	}
	return result
}

func (m *mockMetadata) GetSharingRules(_ context.Context, objectAPIName string) []*models.SharingRule {
	if m.sharingRules == nil {
		return nil
	}
	return m.sharingRules[objectAPIName]
}

// ---------------------------------------------------------------------------
// Section 3.1 — BuildRLSResult: SuperUser bypass (Layer 0)
// ---------------------------------------------------------------------------

// TestBuildRLSResult_SuperUserProfileReturnsSkip verifies that a user whose
// ProfileID matches constants.ProfileSystemAdmin (IsSuperUser) gets Skip=true
// so that no SQL filter is injected.
//
// Setup:
//
//	schema  = any non-nil ObjectMetadata with SharingModel=Private
//	user    = &models.UserSession{ProfileID: constants.ProfileSystemAdmin}
//
// Assert: result.Skip == true
func TestBuildRLSResult_SuperUserProfileReturnsSkip(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	schema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
	}
	user := &models.UserSession{ID: "admin1", ProfileID: constants.ProfileSystemAdmin}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	if !result.Skip {
		t.Errorf("expected Skip=true for SuperUser, got false")
	}
}

// TestBuildRLSResult_IsSystemAdminFlagReturnsSkip verifies the IsSystemAdmin
// boolean flag (not just ProfileID) also triggers the bypass.
//
// Setup:
//
//	user = &models.UserSession{ID: "u1", ProfileID: "standard", IsSystemAdmin: true}
//
// Assert: result.Skip == true
func TestBuildRLSResult_IsSystemAdminFlagReturnsSkip(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	schema := &models.ObjectMetadata{APIName: "Account", SharingModel: constants.SharingModelPrivate}
	user := &models.UserSession{ID: "u1", ProfileID: "standard", IsSystemAdmin: true}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	if !result.Skip {
		t.Errorf("expected Skip=true for IsSystemAdmin flag, got false")
	}
}

// ---------------------------------------------------------------------------
// Section 3.1 — BuildRLSResult: OWD bypass (Layer 1)
// ---------------------------------------------------------------------------

// TestBuildRLSResult_PublicReadWriteReturnsSkip verifies that PublicReadWrite OWD
// bypasses all further checks and returns Skip=true.
//
// Setup:
//
//	schema.SharingModel = constants.SharingModelPublicReadWrite
//	user = standard (non-admin) user
//
// Assert: result.Skip == true
func TestBuildRLSResult_PublicReadWriteReturnsSkip(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	schema := &models.ObjectMetadata{APIName: "Account", SharingModel: constants.SharingModelPublicReadWrite}
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	if !result.Skip {
		t.Errorf("expected Skip=true for PublicReadWrite OWD, got false")
	}
}

// TestBuildRLSResult_PublicReadReturnsSkip verifies that PublicRead OWD
// (read queries always bypass) returns Skip=true.
//
// Assert: result.Skip == true
func TestBuildRLSResult_PublicReadReturnsSkip(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	schema := &models.ObjectMetadata{APIName: "Account", SharingModel: constants.SharingModelPublicRead}
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	if !result.Skip {
		t.Errorf("expected Skip=true for PublicRead OWD, got false")
	}
}

// ---------------------------------------------------------------------------
// Section 3.1 — BuildRLSResult: nil inputs → deny-all (design principle P3)
// ---------------------------------------------------------------------------

// TestBuildRLSResult_NilSchemaReturnsDenyAll verifies that nil schema does NOT
// fail-open (B3 fix) — it returns a result with Skip=false and HasOwnerField=false,
// which the adapter must render as "1 = 0".
//
// Assert: result.Skip == false, result.HasOwnerField == false
func TestBuildRLSResult_NilSchemaReturnsDenyAll(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), nil, "SomeObject", user)

	if result.Skip {
		t.Errorf("expected Skip=false for nil schema (deny-all), got Skip=true")
	}
	if result.HasOwnerField {
		t.Errorf("expected HasOwnerField=false for nil schema, got true")
	}
}

// TestBuildRLSResult_NilUserReturnsDenyAll verifies that nil user → deny-all.
//
// Assert: result.Skip == false, result.HasOwnerField == false
func TestBuildRLSResult_NilUserReturnsDenyAll(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	schema := &models.ObjectMetadata{APIName: "Account", SharingModel: constants.SharingModelPrivate}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", nil)

	if result.Skip {
		t.Errorf("expected Skip=false for nil user (deny-all), got Skip=true")
	}
}

// ---------------------------------------------------------------------------
// Section 3.1 — BuildRLSResult: Layer 3/4 — self + subordinate ownership
// ---------------------------------------------------------------------------

// TestBuildRLSResult_PrivateOWD_OwnerUserIDsContainsSelf verifies that for
// a Private OWD object with OwnerId field, the result includes the requesting
// user's own ID in OwnerUserIDs.
//
// Setup:
//
//	schema.SharingModel = Private, schema.Fields includes {APIName: "OwnerId"}
//	user.ID = "user-42"
//	mockPermRepo.loadEffectiveObjectPerm returns nil (no ViewAll)
//
// Assert: result.OwnerUserIDs contains "user-42"
func TestBuildRLSResult_PrivateOWD_OwnerUserIDsContainsSelf(t *testing.T) {
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
	schema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldOwnerID},
		},
	}
	user := &models.UserSession{ID: "user-42", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	if result.Skip {
		t.Errorf("expected Skip=false for Private OWD standard user")
	}
	found := false
	for _, id := range result.OwnerUserIDs {
		if id == "user-42" {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("expected OwnerUserIDs to contain user's own ID 'user-42', got %v", result.OwnerUserIDs)
	}
}

// TestBuildRLSResult_PrivateOWD_SubordinatesIncludedWhenHierarchyEnabled verifies
// that when EnableHierarchySharing=true and the user has a role, subordinate user IDs
// returned by GetSubordinateUserIDs are appended to OwnerUserIDs.
//
// Setup:
//
//	schema.EnableHierarchySharing = true
//	user.RoleID = ptr("manager-role")
//	mockUserRepo.GetUserIDsByRoleIDs returns ["sub-user-1", "sub-user-2"]
//	roleHierarchyCache has manager-role → CEO, with subordinate Rep role
//
// Assert: result.OwnerUserIDs includes "sub-user-1", "sub-user-2", and user's own ID
func TestBuildRLSResult_PrivateOWD_SubordinatesIncludedWhenHierarchyEnabled(t *testing.T) {
	subordinateUserIDs := []string{"sub-user-1", "sub-user-2"}
	// mockUserRepo has no getUserIDsByRoleIDs field; use the default stub which returns [].
	// To exercise subordinate expansion, override via a custom userRepo implementation
	// once the mockUserRepo struct is extended, or use an integration test.
	// For now, we verify the OwnerUserIDs structure without custom subordinate injection.
	_ = subordinateUserIDs
	userRepo := &mockUserRepo{}

	// Build cache: Rep is child of manager-role
	cache := buildHierarchy(map[string]string{
		"manager-role": "",
		"rep-role":     "manager-role",
	})

	ps := newTestService(&mockPermRepo{}, userRepo, cache)
	roleID := "manager-role"
	schema := &models.ObjectMetadata{
		APIName:                "Account",
		SharingModel:           constants.SharingModelPrivate,
		EnableHierarchySharing: true,
		Fields:                 []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	user := &models.UserSession{ID: "mgr-user", ProfileID: "standard", RoleID: &roleID}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	// With the default mockUserRepo stub (GetUserIDsByRoleIDs returns []),
	// OwnerUserIDs will only contain the user's own ID.
	// A full subordinate test requires extending mockUserRepo or using integration tests.
	ownerSet := make(map[string]bool, len(result.OwnerUserIDs))
	for _, id := range result.OwnerUserIDs {
		ownerSet[id] = true
	}
	if !ownerSet["mgr-user"] {
		t.Errorf("expected OwnerUserIDs to contain user's own ID 'mgr-user', got %v", result.OwnerUserIDs)
	}
}

// TestBuildRLSResult_SubordinatesNotIncludedWhenHierarchySharingDisabled verifies
// that when EnableHierarchySharing=false, role subordinates are NOT added.
//
// Assert: result.OwnerUserIDs has exactly one entry (the user's own ID)
func TestBuildRLSResult_SubordinatesNotIncludedWhenHierarchySharingDisabled(t *testing.T) {
	cache := buildHierarchy(map[string]string{
		"manager-role": "",
		"rep-role":     "manager-role",
	})
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)
	roleID := "manager-role"
	schema := &models.ObjectMetadata{
		APIName:                "Account",
		SharingModel:           constants.SharingModelPrivate,
		EnableHierarchySharing: false,
		Fields:                 []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	user := &models.UserSession{ID: "mgr-user", ProfileID: "standard", RoleID: &roleID}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	if len(result.OwnerUserIDs) != 1 || result.OwnerUserIDs[0] != "mgr-user" {
		t.Errorf("expected OwnerUserIDs = [mgr-user] (no subordinates), got %v", result.OwnerUserIDs)
	}
}

// ---------------------------------------------------------------------------
// Section 3.1 — BuildRLSResult: ControlledByParent (B2 fix)
// ---------------------------------------------------------------------------

// TestBuildRLSResult_ControlledByParent_DelegatesParentRLSResult verifies the
// ControlledByParent path sets ParentFilter with the parent's RLSResult.
//
// Setup:
//
//	schema.SharingModel = ControlledByParent
//	metadata mock returns a MasterDetail relationship: Contact.AccountId → Account
//	parent schema has SharingModel=Private
//
// Assert: result.ParentFilter != nil
//
//	result.ParentFilter.ParentObjectAPIName == "Account"
//	result.ParentFilter.ForeignKeyField == "AccountId"
func TestBuildRLSResult_ControlledByParent_DelegatesParentRLSResult(t *testing.T) {
	accountSchema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	contactSchema := &models.ObjectMetadata{
		APIName:      "Contact",
		SharingModel: constants.SharingModelControlledByParent,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	meta := &mockMetadata{
		schemas: map[string]*models.ObjectMetadata{
			"Account": accountSchema,
			"Contact": contactSchema,
		},
		relationships: map[string][]*models.Relationship{
			"Contact": {
				{
					ChildObjectAPIName:  "Contact",
					ParentObjectAPIName: "Account",
					FieldAPIName:        "AccountId",
					IsSharingParent:     true,
				},
			},
		},
	}
	ps := newTestServiceWithMeta(&mockPermRepo{}, &mockUserRepo{}, nil, meta)
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), contactSchema, "Contact", user)

	if result.ParentFilter == nil {
		t.Fatalf("expected ParentFilter to be set for ControlledByParent schema, got nil")
	}
	if result.ParentFilter.ParentObjectAPIName != "Account" {
		t.Errorf("expected ParentObjectAPIName=Account, got %q", result.ParentFilter.ParentObjectAPIName)
	}
	if result.ParentFilter.ForeignKeyField != "AccountId" {
		t.Errorf("expected ForeignKeyField=AccountId, got %q", result.ParentFilter.ForeignKeyField)
	}
	if result.ParentFilter.ParentRLSResult == nil {
		t.Errorf("expected ParentRLSResult to be non-nil")
	}
	if !result.HasOwnerField {
		t.Fatalf("expected ControlledByParent child to retain owner fallback")
	}
	foundSelf := false
	for _, id := range result.OwnerUserIDs {
		if id == "u1" {
			foundSelf = true
			break
		}
	}
	if !foundSelf {
		t.Fatalf("expected owner fallback to include requesting user, got %v", result.OwnerUserIDs)
	}
}

func TestBuildRLSResult_DerivedAccessPolicy_IgnoresSelfObjectPermissionBypassRows(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:      "CaseComment",
		SharingModel: constants.SharingModelPrivate,
		AccessPolicy: &models.ObjectAccessPolicy{Mode: "derived"},
	}
	repo := &mockPermRepo{
		loadEffectiveObjectPerm: func(_ context.Context, _ *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error) {
			if objectAPIName != "CaseComment" {
				t.Fatalf("unexpected object permission lookup for %s", objectAPIName)
			}
			return &models.ObjectPermissions{
				SobjectType:                 "CaseComment",
				PermissionsViewAllRecords:   true,
				PermissionsModifyAllRecords: true,
			}, nil
		},
	}
	ps := newTestServiceWithMeta(repo, &mockUserRepo{}, nil, &mockMetadata{
		schemas: map[string]*models.ObjectMetadata{"CaseComment": schema},
	})
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), schema, "CaseComment", user)

	if result.Skip {
		t.Fatalf("derived-policy object should not bypass query RLS from stale self object permission rows: %+v", result)
	}
}

// TestBuildRLSResult_ControlledByParent_CircularGuardReturnsDenyAll verifies that
// a circular ControlledByParent chain (A → B → A) terminates with deny-all and
// does NOT cause infinite recursion or a stack overflow.
//
// Setup:
//
//	Object A has SharingModel=ControlledByParent, parent=B
//	Object B has SharingModel=ControlledByParent, parent=A
//	(metadata mock returns these relationships)
//
// Assert: function returns without panic; result.Skip == false
func TestBuildRLSResult_ControlledByParent_CircularGuardFallsBackToOwnerAccess(t *testing.T) {
	schemaA := &models.ObjectMetadata{
		APIName:      "ObjA",
		SharingModel: constants.SharingModelControlledByParent,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	schemaB := &models.ObjectMetadata{
		APIName:      "ObjB",
		SharingModel: constants.SharingModelControlledByParent,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	meta := &mockMetadata{
		schemas: map[string]*models.ObjectMetadata{
			"ObjA": schemaA,
			"ObjB": schemaB,
		},
		relationships: map[string][]*models.Relationship{
			"ObjA": {{ChildObjectAPIName: "ObjA", ParentObjectAPIName: "ObjB", FieldAPIName: "ObjBId", IsSharingParent: true}},
			"ObjB": {{ChildObjectAPIName: "ObjB", ParentObjectAPIName: "ObjA", FieldAPIName: "ObjAId", IsSharingParent: true}},
		},
	}
	ps := newTestServiceWithMeta(&mockPermRepo{}, &mockUserRepo{}, nil, meta)
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	// Must not panic or recurse infinitely.
	result := ps.BuildRLSResult(context.Background(), schemaA, "ObjA", user)

	if result.Skip {
		t.Errorf("expected Skip=false for circular ControlledByParent fallback, got Skip=true")
	}
	if result.DenyAll {
		t.Fatalf("expected circular ControlledByParent to fall back to owner access, got deny-all")
	}
	if !result.HasOwnerField {
		t.Fatalf("expected owner fallback after circular parent guard")
	}
	if len(result.OwnerUserIDs) != 1 || result.OwnerUserIDs[0] != "u1" {
		t.Fatalf("expected owner fallback to preserve self access, got %v", result.OwnerUserIDs)
	}
	if result.ParentFilter == nil || result.ParentFilter.ParentRLSResult == nil {
		t.Fatalf("expected parent access path to remain structured even when the cycle is cut, got %+v", result.ParentFilter)
	}
}

// TestBuildRLSResult_ControlledByParent_ParentDenyAllStillRetainsOwnerFallback verifies
// that when the parent object has no readable path, the child still keeps its own
// owner/share fallback instead of collapsing to deny-all.
func TestBuildRLSResult_ControlledByParent_ParentDenyAllStillRetainsOwnerFallback(t *testing.T) {
	accountSchema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
	}
	contactSchema := &models.ObjectMetadata{
		APIName:      "Contact",
		SharingModel: constants.SharingModelControlledByParent,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	meta := &mockMetadata{
		schemas: map[string]*models.ObjectMetadata{
			"Account": accountSchema,
			"Contact": contactSchema,
		},
		relationships: map[string][]*models.Relationship{
			"Contact": {
				{
					ChildObjectAPIName:  "Contact",
					ParentObjectAPIName: "Account",
					FieldAPIName:        "AccountId",
					IsSharingParent:     true,
				},
			},
		},
	}
	ps := newTestServiceWithMeta(&mockPermRepo{}, &mockUserRepo{}, nil, meta)
	user := &models.UserSession{ID: "u1", ProfileID: "standard"}

	result := ps.BuildRLSResult(context.Background(), contactSchema, "Contact", user)

	if result.DenyAll {
		t.Fatalf("expected owner fallback to keep child readable, got deny-all")
	}
	if !result.HasOwnerField {
		t.Fatalf("expected child owner fallback to remain enabled")
	}
	if len(result.OwnerUserIDs) != 1 || result.OwnerUserIDs[0] != "u1" {
		t.Fatalf("expected owner fallback for requesting user, got %v", result.OwnerUserIDs)
	}
	if result.ParentFilter == nil || result.ParentFilter.ParentRLSResult == nil {
		t.Fatalf("expected parent access path to stay isolated from child fallback, got %+v", result.ParentFilter)
	}
}

// ---------------------------------------------------------------------------
// Section 3.1 — BuildRLSResult: owner-based sharing rules (B1 fix)
// ---------------------------------------------------------------------------

// TestBuildRLSResult_OwnerBasedSharingRule_SharedFromRoleIDsAddedToResult verifies
// that criteria-less sharing rules whose SharedFrom is a role cause the
// SharedFrom users' IDs to appear in SharingRuleOwnerIDs — NOT all records.
//
// Setup:
//
//	sharing rule for "Account" with no Criteria, SharedFromRoleID="sales-role",
//	  ShareWithRoleID="marketing-role"
//	user.RoleID = "marketing-role" (matches ShareWith)
//	mockUserRepo.GetUserIDsByRoleIDs("sales-role") returns ["sales-u1"]
//
// Assert: result.SharingRuleOwnerIDs contains "sales-u1"
//
//	result.SharingRuleOwnerIDs does NOT contain the requesting user's own ID
//	(self-ownership is separate in OwnerUserIDs)
func TestBuildRLSResult_OwnerBasedSharingRule_SharedFromRoleIDsAddedToResult(t *testing.T) {
	salesRoleID := "sales-role"
	marketingRoleID := "marketing-role"
	sharedFromRoleID := salesRoleID
	shareWithRoleID := marketingRoleID

	meta := &mockMetadata{
		sharingRules: map[string][]*models.SharingRule{
			"Account": {
				{
					ObjectAPIName:    "Account",
					Criteria:         "",
					SharedFromRoleID: &sharedFromRoleID,
					ShareWithRoleID:  &shareWithRoleID,
				},
			},
		},
	}
	// userRepo: GetUserIDsByRoleIDs("sales-role") returns ["sales-u1"]
	userRepo := &mockUserRepo{
		getUserIDsByRoleIDs: func(_ context.Context, roleIDs []string) ([]string, error) {
			for _, id := range roleIDs {
				if id == salesRoleID {
					return []string{"sales-u1"}, nil
				}
			}
			return []string{}, nil
		},
	}

	// Build hierarchy: marketing-role is a root; sales-role is separate root.
	// User has marketing-role → matches ShareWithRoleID.
	cache := buildHierarchy(map[string]string{
		salesRoleID:     "",
		marketingRoleID: "",
	})
	ps := newTestServiceWithMeta(&mockPermRepo{}, userRepo, cache, meta)
	schema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldOwnerID}},
	}
	user := &models.UserSession{ID: "mkt-user", ProfileID: "standard", RoleID: &marketingRoleID}

	result := ps.BuildRLSResult(context.Background(), schema, "Account", user)

	found := false
	for _, id := range result.SharingRuleOwnerIDs {
		if id == "sales-u1" {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("expected SharingRuleOwnerIDs to contain 'sales-u1', got %v", result.SharingRuleOwnerIDs)
	}
	// Self-ownership should be in OwnerUserIDs, not SharingRuleOwnerIDs
	for _, id := range result.SharingRuleOwnerIDs {
		if id == "mkt-user" {
			t.Errorf("expected requesting user's own ID to be in OwnerUserIDs, not SharingRuleOwnerIDs")
			break
		}
	}
}

// ---------------------------------------------------------------------------
// Section 3.2 — RLSResultToFilterExpr: string adapter
// ---------------------------------------------------------------------------

// TestRLSResultToFilterExpr_NilResultReturnsDenyAll verifies that a nil *RLSResult
// produces the deny-all expression "1 = 0".
//
// Assert: RLSResultToFilterExpr(nil, "") == "" (Skip=true semantics) OR "1 = 0"
// (check actual implementation: nil → Skip=true branch returns "")
func TestRLSResultToFilterExpr_NilResultReturnsEmpty(t *testing.T) {
	// RLSResultToFilterExpr is a pure function, no service needed.
	result := RLSResultToFilterExpr(nil, "")
	// nil → Skip=true branch → "" (no filter = pass-through)
	// Verify the current contract: nil treated as Skip
	if result != "" {
		t.Errorf("expected empty string for nil result (Skip), got %q", result)
	}
}

// TestRLSResultToFilterExpr_SkipTrueReturnsEmpty verifies Skip=true → empty string
// (no SQL filter injected, all records visible).
//
// Assert: result == ""
func TestRLSResultToFilterExpr_SkipTrueReturnsEmpty(t *testing.T) {
	result := RLSResultToFilterExpr(&RLSResult{Skip: true, ObjectAPIName: "Account"}, "")
	if result != "" {
		t.Errorf("expected empty string for Skip=true, got %q", result)
	}
}

// TestRLSResultToFilterExpr_NoOwnerFieldReturnsDenyAll verifies that when
// HasOwnerField=false and no ParentFilter, the adapter returns deny-all.
//
// Assert: result == "1 = 0"
func TestRLSResultToFilterExpr_NoOwnerFieldReturnsDenyAll(t *testing.T) {
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName: "SomeObject",
		Skip:          false,
		HasOwnerField: false,
	}, "")
	if result != "1 = 0" {
		t.Errorf("expected deny-all '1 = 0' for no owner field, got %q", result)
	}
}

// TestRLSResultToFilterExpr_SingleOwnerID_ProducesEqualityClause verifies that
// a single OwnerUserID produces "OwnerId = 'uid'" (not IN clause).
//
// Assert: result contains "OwnerId = 'user-1'"
func TestRLSResultToFilterExpr_SingleOwnerID_ProducesEqualityClause(t *testing.T) {
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName: "Account",
		HasOwnerField: true,
		UserID:        "user-1",
		OwnerUserIDs:  []string{"user-1"},
	}, "")
	if !strings.Contains(result, "`OwnerId` = 'user-1'") {
		t.Errorf("expected equality clause for single owner, got %q", result)
	}
}

// TestRLSResultToFilterExpr_MultipleOwnerIDs_ProducesINClause verifies that
// multiple OwnerUserIDs produce "OwnerId IN ('u1', 'u2')".
//
// Assert: result contains "OwnerId IN ("
func TestRLSResultToFilterExpr_MultipleOwnerIDs_ProducesINClause(t *testing.T) {
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName: "Account",
		HasOwnerField: true,
		UserID:        "u1",
		OwnerUserIDs:  []string{"u1", "u2"},
	}, "")
	if !strings.Contains(result, "`OwnerId` IN (") {
		t.Errorf("expected IN clause for multiple owners, got %q", result)
	}
}

// TestRLSResultToFilterExpr_IncludeManualShares_ProducesRecordShareSubquery verifies
// that IncludeManualShares=true adds the _System_RecordShare subquery to the filter.
//
// Assert: result contains "_System_RecordShare"
func TestRLSResultToFilterExpr_IncludeManualShares_ProducesRecordShareSubquery(t *testing.T) {
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName:       "Account",
		HasOwnerField:       true,
		UserID:              "u1",
		OwnerUserIDs:        []string{"u1"},
		IncludeManualShares: true,
	}, "")
	if !strings.Contains(result, "_System_RecordShare") {
		t.Errorf("expected _System_RecordShare subquery in filter, got %q", result)
	}
}

// TestRLSResultToFilterExpr_IncludeGroupOwnership_ProducesGroupMemberSubquery verifies
// that IncludeGroupOwnership=true adds the GroupMember ownership subquery.
//
// Assert: result contains "GroupMember"
func TestRLSResultToFilterExpr_IncludeGroupOwnership_ProducesGroupMemberSubquery(t *testing.T) {
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName:         "Account",
		HasOwnerField:         true,
		UserID:                "u1",
		OwnerUserIDs:          []string{"u1"},
		IncludeGroupOwnership: true,
	}, "")
	if !strings.Contains(result, "GroupMember") {
		t.Errorf("expected GroupMember subquery in filter, got %q", result)
	}
}

// TestRLSResultToFilterExpr_ParentFilter_ProducesParentSubquery verifies that
// a ControlledByParent result (ParentFilter set) produces a subquery of the form:
// "AccountId IN (SELECT Id FROM Account WHERE ...)"
//
// Assert: result contains "IN (SELECT Id FROM Account WHERE"
func TestRLSResultToFilterExpr_ParentFilter_ProducesParentSubquery(t *testing.T) {
	parentResult := &RLSResult{
		ObjectAPIName: "Account",
		HasOwnerField: true,
		UserID:        "u1",
		OwnerUserIDs:  []string{"u1"},
	}
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName: "Contact",
		ParentFilter: &ParentRLSFilter{
			ParentObjectAPIName: "Account",
			ForeignKeyField:     "AccountId",
			ParentRLSResult:     parentResult,
		},
	}, "")
	if !strings.Contains(result, "IN (SELECT Id FROM `Account` WHERE") {
		t.Errorf("expected parent subquery in filter, got %q", result)
	}
}

func TestRLSResultToFilterExpr_ParentFilterAndOwnerFallback_CombinesWithOR(t *testing.T) {
	parentResult := &RLSResult{
		ObjectAPIName: "Account",
		DenyAll:       true,
	}
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName: "Contact",
		HasOwnerField: true,
		UserID:        "u1",
		OwnerUserIDs:  []string{"u1"},
		ParentFilter: &ParentRLSFilter{
			ParentObjectAPIName: "Account",
			ForeignKeyField:     "AccountId",
			ParentRLSResult:     parentResult,
		},
	}, "")
	if !strings.Contains(result, "`AccountId` IN (SELECT Id FROM `Account` WHERE 1 = 0)") {
		t.Fatalf("expected parent branch to remain isolated in filter, got %q", result)
	}
	if !strings.Contains(result, "`OwnerId` = 'u1'") {
		t.Fatalf("expected owner fallback branch in filter, got %q", result)
	}
	if !strings.Contains(result, "OR") {
		t.Fatalf("expected access paths to combine with OR, got %q", result)
	}
}

// TestRLSResultToFilterExpr_ParentFilter_ParentSkipTrue_ReturnsEmpty verifies that
// when the parent's RLSResult has Skip=true (parent is publicly readable),
// the child also has no restriction (return "").
//
// Assert: result == ""
func TestRLSResultToFilterExpr_ParentFilter_ParentSkipTrue_ReturnsEmpty(t *testing.T) {
	parentResult := &RLSResult{Skip: true, ObjectAPIName: "Account"}
	result := RLSResultToFilterExpr(&RLSResult{
		ObjectAPIName: "Contact",
		ParentFilter: &ParentRLSFilter{
			ParentObjectAPIName: "Account",
			ForeignKeyField:     "AccountId",
			ParentRLSResult:     parentResult,
		},
	}, "")
	if result != "" {
		t.Errorf("expected empty filter when parent is unrestricted (Skip=true), got %q", result)
	}
}
