package permissions

// access_classification_test.go — Test plan for Section 2: Object Access Classification
//
// Design ref: SECURITY_REFACTOR_DESIGN.md §2 (Fix for RC1, RC6, RC7)
// Coverage target:
//   - Read-exempt policy: standard users CAN read setup objects
//   - Write-gated policy: standard users CANNOT write setup objects without permission
//   - Security infrastructure (BLOCKED) tables: never writable via generic CRUD
//   - Immutable security fields on _System_Object and _System_Field
//
// All tests are pure unit tests using mockPermRepo / mockUserRepo / newTestService.
//
// HOW TO RUN:
//   go test ./internal/features/permissions/ -run TestAccessClassification -v

import (
	"context"
	"testing"

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

// newServiceWithSchema returns a PermissionService whose metadata mock returns
// the given schema for the given object name. Used by access classification tests.
func newServiceWithSchema(apiName string, schema *models.ObjectMetadata, permRepo *mockPermRepo) *PermissionService {
	meta := &mockMetadata{
		schemas: map[string]*models.ObjectMetadata{
			apiName: schema,
		},
	}
	return newTestServiceWithMeta(permRepo, &mockUserRepo{}, nil, meta)
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

// makeSchemaWithType creates a minimal ObjectMetadata with the given TableType
// and RequiredWritePermission value.
func makeSchemaWithType(apiName string, tableType constants.TableType, requiredWritePerm string) *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:                 apiName,
		TableType:               tableType,
		RequiredWritePermission: requiredWritePerm,
	}
}

// standardUser returns a non-admin UserSession for use in access tests.
func standardUser(userID string) *models.UserSession {
	return &models.UserSession{
		ID:            userID,
		ProfileID:     constants.ProfileStandardUser,
		IsSystemAdmin: false,
	}
}

// ---------------------------------------------------------------------------
// Section 2.2 — Read-exempt model (P15: setup objects readable by all)
// Design: For any non-custom/standard TableType, operation=Read always returns nil.
// ---------------------------------------------------------------------------

// TestSetupObject_StandardUserCanRead_User verifies that a standard user can READ
// the User object (Read-exempt policy from SF model).
//
// Setup:
//   schema: User, TableType=system_core, RequiredWritePermission="ManageUsers"
//   user: standard (non-admin)
//   operation: constants.PermRead
//
// Assert: requiredSystemPermissionsForSetupObject returns nil (no gate)
func TestSetupObject_StandardUserCanRead_User(t *testing.T) {
	schema := makeSchemaWithType(constants.TableUser, constants.TableTypeSystemCore, "ManageUsers")
	ps := newServiceWithSchema(constants.TableUser, schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableUser, constants.PermRead)

	if len(perms) != 0 {
		t.Errorf("expected no required perms for Read on User (read-exempt), got %v", perms)
	}
}

// TestSetupObject_StandardUserCanRead_Profile verifies Read-exempt for Profile.
//
// Assert: requiredSystemPermissionsForSetupObject returns nil for Read
func TestSetupObject_StandardUserCanRead_Profile(t *testing.T) {
	schema := makeSchemaWithType(constants.TableProfile, constants.TableTypeSystemCore, "ManageUsers")
	ps := newServiceWithSchema(constants.TableProfile, schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableProfile, constants.PermRead)

	if len(perms) != 0 {
		t.Errorf("expected no required perms for Read on Profile (read-exempt), got %v", perms)
	}
}

// TestSetupObject_StandardUserCanRead_UserRole verifies Read-exempt for UserRole.
//
// Assert: requiredSystemPermissionsForSetupObject returns nil for Read
func TestSetupObject_StandardUserCanRead_UserRole(t *testing.T) {
	schema := makeSchemaWithType(constants.TableUserRole, constants.TableTypeSystemCore, "ManageUsers")
	ps := newServiceWithSchema(constants.TableUserRole, schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableUserRole, constants.PermRead)

	if len(perms) != 0 {
		t.Errorf("expected no required perms for Read on UserRole (read-exempt), got %v", perms)
	}
}

// TestSetupObject_StandardUserCanRead_Group verifies Read-exempt for Group.
//
// Assert: requiredSystemPermissionsForSetupObject returns nil for Read
func TestSetupObject_StandardUserCanRead_Group(t *testing.T) {
	schema := makeSchemaWithType(constants.TableGroup, constants.TableTypeSystemCore, "ManageUsers")
	ps := newServiceWithSchema(constants.TableGroup, schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableGroup, constants.PermRead)

	if len(perms) != 0 {
		t.Errorf("expected no required perms for Read on Group (read-exempt), got %v", perms)
	}
}

// ---------------------------------------------------------------------------
// Section 2.2 — Write-gated policy
// ---------------------------------------------------------------------------

// TestSetupObject_StandardUserCannotWrite_User_WithoutManageUsers verifies that
// writing to the User object requires the ManageUsers system permission.
//
// Setup:
//   schema: User with RequiredWritePermission="ManageUsers"
//   operation: constants.PermEdit
//   mockPermRepo.getEffSysPerms returns map without ManageUsers
//
// Assert: requiredSystemPermissionsForSetupObject returns ["ManageUsers"]
//         hasAnySystemPermission returns false
func TestSetupObject_StandardUserCannotWrite_User_WithoutManageUsers(t *testing.T) {
	schema := makeSchemaWithType(constants.TableUser, constants.TableTypeSystemCore, "ManageUsers")
	permRepo := &mockPermRepo{
		getEffSysPerms: func(ctx context.Context, userID, profileID string) (map[string]bool, error) {
			return map[string]bool{}, nil // no ManageUsers
		},
	}
	ps := newServiceWithSchema(constants.TableUser, schema, permRepo)

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableUser, constants.PermEdit)

	if len(perms) == 0 || perms[0] != "ManageUsers" {
		t.Errorf("expected requiredSystemPermissionsForSetupObject to return ['ManageUsers'], got %v", perms)
	}

	user := standardUser("u1")
	hasPerm := ps.hasAnySystemPermission(context.Background(), user, perms)
	if hasPerm {
		t.Errorf("expected hasAnySystemPermission to return false for user without ManageUsers")
	}
}

// TestSetupObject_AdminCanWrite_User_WithManageUsers verifies that a user with
// ManageUsers CAN write to User.
//
// Setup:
//   mockPermRepo.getEffSysPerms returns {"ManageUsers": true}
//
// Assert: hasAnySystemPermission returns true
func TestSetupObject_AdminCanWrite_User_WithManageUsers(t *testing.T) {
	schema := makeSchemaWithType(constants.TableUser, constants.TableTypeSystemCore, "ManageUsers")
	permRepo := &mockPermRepo{
		getEffSysPerms: func(ctx context.Context, userID, profileID string) (map[string]bool, error) {
			return map[string]bool{"ManageUsers": true}, nil
		},
	}
	ps := newServiceWithSchema(constants.TableUser, schema, permRepo)

	user := standardUser("u1")
	hasPerm := ps.hasAnySystemPermission(context.Background(), user, []string{"ManageUsers"})
	if !hasPerm {
		t.Errorf("expected hasAnySystemPermission to return true for user with ManageUsers")
	}
}

// ---------------------------------------------------------------------------
// Section 2.2 — BLOCKED tables: never writable via generic CRUD (RC6 fix)
// ---------------------------------------------------------------------------

// TestSetupObject_SessionTable_IsBlocked verifies that _System_Session writes
// are rejected for ANY user, including admins.
//
// Design: securityInfrastructureTables maps _System_Session to BLOCKED.
//   requiredSystemPermissionsForSetupObject returns [blockedPermission].
//   hasAnySystemPermission always returns false for blockedPermission.
//
// Setup:
//   schema: _System_Session with RequiredWritePermission="BLOCKED"
//   user: system admin (IsSystemAdmin=true)
//   operation: constants.PermCreate
//
// Assert: requiredSystemPermissionsForSetupObject returns non-nil (the blocked sentinel)
//         hasAnySystemPermission returns false (blocked permission is unsatisfiable)
func TestSetupObject_SessionTable_IsBlockedForEveryone(t *testing.T) {
	schema := makeSchemaWithType("_System_Session", constants.TableTypeSystemCore, "BLOCKED")
	permRepo := &mockPermRepo{
		getEffSysPerms: func(ctx context.Context, userID, profileID string) (map[string]bool, error) {
			return map[string]bool{"ManageUsers": true, "ModifyMetadata": true}, nil
		},
	}
	ps := newServiceWithSchema("_System_Session", schema, permRepo)

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), "_System_Session", constants.PermCreate)

	if len(perms) == 0 {
		t.Errorf("expected blocked sentinel returned for _System_Session create, got empty")
	}
	// Even admin cannot satisfy the blocked sentinel
	adminUser := &models.UserSession{ID: "admin", ProfileID: "standard", IsSystemAdmin: false}
	hasPerm := ps.hasAnySystemPermission(context.Background(), adminUser, perms)
	if hasPerm {
		t.Errorf("expected hasAnySystemPermission to return false for blocked permission (even with ManageUsers+ModifyMetadata)")
	}
}

// TestSetupObject_OAuthCodeTable_IsBlocked verifies _System_OAuthCode is BLOCKED.
//
// Assert: same as TestSetupObject_SessionTable_IsBlockedForEveryone
func TestSetupObject_OAuthCodeTable_IsBlocked(t *testing.T) {
	schema := makeSchemaWithType("_System_OAuthCode", constants.TableTypeSystemCore, "BLOCKED")
	ps := newServiceWithSchema("_System_OAuthCode", schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), "_System_OAuthCode", constants.PermCreate)

	if len(perms) == 0 {
		t.Errorf("expected blocked sentinel for _System_OAuthCode create, got empty")
	}
	user := standardUser("u1")
	if ps.hasAnySystemPermission(context.Background(), user, perms) {
		t.Errorf("expected blocked permission to be unsatisfiable")
	}
}

// TestSetupObject_LoginHistoryTable_IsBlocked verifies _System_LoginHistory is BLOCKED.
func TestSetupObject_LoginHistoryTable_IsBlocked(t *testing.T) {
	schema := makeSchemaWithType("_System_LoginHistory", constants.TableTypeSystemCore, "BLOCKED")
	ps := newServiceWithSchema("_System_LoginHistory", schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), "_System_LoginHistory", constants.PermCreate)

	if len(perms) == 0 {
		t.Errorf("expected blocked sentinel for _System_LoginHistory create, got empty")
	}
	if ps.hasAnySystemPermission(context.Background(), standardUser("u1"), perms) {
		t.Errorf("expected blocked permission to be unsatisfiable")
	}
}

// TestSetupObject_SetupAuditTrailTable_IsBlocked verifies _System_SetupAuditTrail is BLOCKED.
func TestSetupObject_SetupAuditTrailTable_IsBlocked(t *testing.T) {
	schema := makeSchemaWithType("_System_SetupAuditTrail", constants.TableTypeSystemCore, "BLOCKED")
	ps := newServiceWithSchema("_System_SetupAuditTrail", schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), "_System_SetupAuditTrail", constants.PermCreate)

	if len(perms) == 0 {
		t.Errorf("expected blocked sentinel for _System_SetupAuditTrail create, got empty")
	}
	if ps.hasAnySystemPermission(context.Background(), standardUser("u1"), perms) {
		t.Errorf("expected blocked permission to be unsatisfiable")
	}
}

// ---------------------------------------------------------------------------
// Section 2.2 — _System_Object and _System_Field require ModifyMetadata
// ---------------------------------------------------------------------------

// TestSetupObject_SystemObject_RequiresModifyMetadataToWrite verifies that
// writing to _System_Object requires ModifyMetadata system permission.
//
// Assert: requiredSystemPermissionsForSetupObject("_System_Object", "edit") returns ["ModifyMetadata"]
func TestSetupObject_SystemObjectTable_RequiresModifyMetadata(t *testing.T) {
	schema := makeSchemaWithType(constants.TableObject, constants.TableTypeSystemCore, "ModifyMetadata")
	ps := newServiceWithSchema(constants.TableObject, schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableObject, constants.PermEdit)

	if len(perms) == 0 || perms[0] != "ModifyMetadata" {
		t.Errorf("expected ['ModifyMetadata'] for _System_Object edit, got %v", perms)
	}
}

// TestSetupObject_SystemField_RequiresModifyMetadataToWrite verifies same for _System_Field.
func TestSetupObject_SystemFieldTable_RequiresModifyMetadata(t *testing.T) {
	schema := makeSchemaWithType(constants.TableField, constants.TableTypeSystemCore, "ModifyMetadata")
	ps := newServiceWithSchema(constants.TableField, schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), constants.TableField, constants.PermEdit)

	if len(perms) == 0 || perms[0] != "ModifyMetadata" {
		t.Errorf("expected ['ModifyMetadata'] for _System_Field edit, got %v", perms)
	}
}

// ---------------------------------------------------------------------------
// Section 2.3 — Immutable security fields on metadata tables
// ---------------------------------------------------------------------------

// TestImmutableField_SharingModel_RejectedOnSystemObjectWrite verifies that
// a generic CRUD Update that includes "SharingModel" in the update map is
// rejected with a validation error when the target object is _System_Object.
//
// Design ref: §2.3 — immutableObjectFields map + persistence_service.go Update path.
//
// Setup:
//   objectName = "_System_Object" (or constants.TableSystemObject)
//   updates = map[string]interface{}{"SharingModel": "Private"}
//
// Assert: validation error returned, error message references "SharingModel"
//         and "cannot be modified via generic CRUD"
//
// NOTE: this test lives at the persistence_service level. Add it here as a
// placeholder; the implementation agent may need to move it to the records
// package if checkImmutableFields is not accessible from this package.
func TestImmutableField_SharingModel_RejectedOnSystemObjectWrite(t *testing.T) {
	t.Skip("not yet implemented — requires persistence_service.Update call or extracted checkImmutableFields function")
	t.Log("Assert: updating SharingModel on _System_Object via generic CRUD returns validation error")
}

// TestImmutableField_Createable_RejectedOnSystemFieldWrite verifies that
// writing the "Createable" field on _System_Field is rejected.
//
// Assert: validation error references "Createable"
func TestImmutableField_Createable_RejectedOnSystemFieldWrite(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: updating Createable on _System_Field via generic CRUD returns validation error")
}

// TestImmutableField_EnableHierarchySharing_RejectedOnSystemObjectWrite verifies
// that EnableHierarchySharing is immutable via generic CRUD.
func TestImmutableField_EnableHierarchySharing_RejectedOnSystemObjectWrite(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: updating EnableHierarchySharing on _System_Object via generic CRUD returns validation error")
}

// TestImmutableField_IsSystem_RejectedOnSystemFieldWrite verifies that IsSystem
// on _System_Field cannot be changed via generic CRUD.
func TestImmutableField_IsSystem_RejectedOnSystemFieldWrite(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: updating IsSystem on _System_Field via generic CRUD returns validation error")
}

// ---------------------------------------------------------------------------
// Section 2.2 — Custom / Standard objects: no setup gate
// ---------------------------------------------------------------------------

// TestSetupObject_CustomObject_NoGate verifies that custom objects (TableType=custom_object)
// do not require any additional system permission for write operations.
//
// Assert: requiredSystemPermissionsForSetupObject returns nil for custom object
func TestSetupObject_CustomObjectHasNoGate(t *testing.T) {
	schema := makeSchemaWithType("MyCustomObj__c", constants.TableTypeCustomObject, "")
	ps := newServiceWithSchema("MyCustomObj__c", schema, &mockPermRepo{})

	perms := ps.requiredSystemPermissionsForSetupObject(context.Background(), "MyCustomObj__c", constants.PermEdit)

	if len(perms) != 0 {
		t.Errorf("expected no required perms for custom object edit, got %v", perms)
	}
}

// ---------------------------------------------------------------------------
// Section 8.5 — Delete restriction uses TableType, not hardcoded list
// ---------------------------------------------------------------------------

// TestDeleteRestriction_SystemCoreObject_RequiresPermission verifies that
// the metadata-driven delete path (§8.5 fix) blocks deletion of system_core
// objects for users lacking the required permission.
//
// Design: persistence_delete.go checks schema.TableType instead of a hardcoded list.
//
// Assert: delete of a system_core object fails with a permission error
func TestDeleteRestriction_SystemCoreObject_RequiresPermission(t *testing.T) {
	t.Skip("not yet implemented — requires persistence_service.Delete or extracted check")
	t.Log("Assert: deleting a TableType=system_core record fails unless user has RequiredWritePermission")
}

// TestDeleteRestriction_CustomObject_NoRestriction verifies that custom objects
// do not trigger the metadata-driven delete restriction.
//
// Assert: delete of a custom_object record proceeds (no permission error from the system gate)
func TestDeleteRestriction_CustomObject_NoRestriction(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: deleting a TableType=custom_object record passes the system restriction check")
}

// ---------------------------------------------------------------------------
// Section 7.1 — defaultSharingModel per-object values
// ---------------------------------------------------------------------------

// TestDefaultSharingModel_ContactIsControlledByParent verifies that the
// defaultSharingModel function returns ControlledByParent for Contact, matching
// the Salesforce default.
//
// Assert: defaultSharingModel(standard_object, "contact") == SharingModelControlledByParent
func TestDefaultSharingModel_ContactIsControlledByParent(t *testing.T) {
	t.Skip("not yet implemented — defaultSharingModel may not be exported yet")
	// Implementation agent: export defaultSharingModel (or test via bootstrap path).
	// Call defaultSharingModel(constants.TableTypeStandardObject, "contact").
	// Assert result == constants.SharingModelControlledByParent.
	t.Log("Assert: defaultSharingModel returns ControlledByParent for Contact")
}

// TestDefaultSharingModel_AccountIsPublicRead verifies Account defaults to PublicRead.
//
// Assert: defaultSharingModel(standard_object, "account") == SharingModelPublicRead
func TestDefaultSharingModel_AccountIsPublicRead(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: defaultSharingModel returns PublicRead for Account")
}

// TestDefaultSharingModel_OpportunityIsPrivate verifies Opportunity defaults to Private.
//
// Assert: defaultSharingModel(standard_object, "opportunity") == SharingModelPrivate
func TestDefaultSharingModel_OpportunityIsPrivate(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: defaultSharingModel returns Private for Opportunity")
}

// TestDefaultSharingModel_CustomObjectIsPrivate verifies custom objects default to Private.
//
// Assert: defaultSharingModel(custom_object, "My_Custom__c") == SharingModelPrivate
func TestDefaultSharingModel_CustomObjectIsPrivate(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: defaultSharingModel returns Private for custom objects")
}

// TestDefaultSharingModel_SystemCoreIsPublicReadWrite verifies system_core objects
// default to PublicReadWrite (no RLS on system tables).
//
// Assert: defaultSharingModel(system_core, "_System_Session") == SharingModelPublicReadWrite
func TestDefaultSharingModel_SystemCoreIsPublicReadWrite(t *testing.T) {
	t.Skip("not yet implemented")
	t.Log("Assert: defaultSharingModel returns PublicReadWrite for system_core TableType")
}

// ---------------------------------------------------------------------------
// Section 7.2 — Bootstrap does NOT overwrite admin-configured SharingModel
// ---------------------------------------------------------------------------

// TestBootstrapDoesNotOverwriteSharingModel verifies that running the bootstrap
// process on an object that already has a SharingModel set does NOT reset it.
//
// This is an integration-level test requiring a DB. Declared as stub here;
// full test goes in bootstrap_integration_test.go with //go:build integration.
//
// Assert (integration): bootstrapping Account twice preserves the SharingModel
//   set by an admin between the two bootstrap runs.
func TestBootstrapDoesNotOverwriteSharingModel_IntegrationStub(t *testing.T) {
	t.Skip("integration test — run with go test -tags=integration")
	t.Log("Integration: bootstrap Account → admin sets SharingModel=Private → bootstrap again → SharingModel still Private")
}
