package rest

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/application/services"
	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/backend/internal/features/metadata"
	"github.com/nexuscrm/backend/internal/features/permissions"
	"github.com/nexuscrm/backend/internal/features/records"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type recycleBinTestRecordRepo struct {
	entry         models.SObject
	mine          []models.SObject
	all           []models.SObject
	findAny       func(ctx context.Context, tableName string, id string) (models.SObject, error)
	updatedTable  string
	updatedID     string
	updatedValues models.SObject
	deletedTable  string
	deletedID     string
	physicalTable string
	physicalID    string
}

type recycleBinTestMetadataRepo struct{}

type recycleBinObjectPermCache struct {
	perms map[string]*models.ObjectPermissions
}

func (c *recycleBinObjectPermCache) GetObjectPermission(userID, profileID, objectAPIName string) (*models.ObjectPermissions, bool) {
	if c == nil {
		return nil, false
	}
	perm, ok := c.perms[userID+"|"+profileID+"|"+strings.ToLower(objectAPIName)]
	return perm, ok
}

func (c *recycleBinObjectPermCache) GetFieldPermission(string, string, string, string) (*models.FieldPermissions, bool) {
	return nil, false
}

func (c *recycleBinObjectPermCache) HasObjectPermsLoaded(string, string) bool { return true }
func (c *recycleBinObjectPermCache) HasFieldPermsLoaded(string, string) bool  { return false }
func (c *recycleBinObjectPermCache) InvalidateUser(string)                     {}
func (c *recycleBinObjectPermCache) Invalidate()                               {}

func (r *recycleBinTestMetadataRepo) GetSchemaByAPIName(_ context.Context, apiName string) (*models.ObjectMetadata, error) {
	if strings.EqualFold(apiName, "Account") {
		return &models.ObjectMetadata{APIName: "Account"}, nil
	}
	return nil, nil
}

func (r *recycleBinTestMetadataRepo) GetAllSchemas(_ context.Context) ([]*models.ObjectMetadata, error) {
	return []*models.ObjectMetadata{{APIName: "Account"}}, nil
}

func (r *recycleBinTestMetadataRepo) GetFieldsForObject(context.Context, string) ([]models.FieldMetadata, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetRecordTypes(context.Context, string) ([]*models.RecordType, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAutoNumbers(context.Context, string) ([]*models.AutoNumber, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAllAutoNumbers(context.Context) ([]*models.AutoNumber, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAllIdentifierRules(context.Context) ([]*models.IdentifierRule, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAllValidationRules(context.Context) ([]*models.ValidationRule, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAllRelationships(context.Context) ([]*models.Relationship, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetRelationships(context.Context, string) ([]*models.Relationship, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetFieldDependencies(context.Context, string) ([]*models.FieldDependency, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetChildRelationships(context.Context, string) ([]*models.ObjectMetadata, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) ConfigureAutoNumberField(context.Context, string, string, string, int64) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpsertUIComponent(context.Context, *models.UIComponent) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpsertSetupPage(context.Context, *models.SetupPage) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) GetSetupPages(context.Context) ([]*models.SetupPage, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) UpsertLayout(context.Context, *models.PageLayout) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) SaveLayout(context.Context, *models.PageLayout) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) GetLayout(context.Context, string) (*models.PageLayout, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetLayouts(context.Context, string) ([]*models.PageLayout, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) DeleteLayout(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) SetDefaultLayout(context.Context, string, string, *string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) MarkLayoutCustomized(context.Context, string) {}
func (r *recycleBinTestMetadataRepo) AssignLayoutToProfile(context.Context, string, string, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) GetLayoutIDForProfile(context.Context, string, string) (string, error) {
	return "", nil
}
func (r *recycleBinTestMetadataRepo) GetRelatedListConfigs(context.Context, string) ([]ports.RelatedListConfig, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetApprovalLayouts(context.Context, string) ([]*models.ApprovalPageLayout, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetApprovalLayout(context.Context, string) (*models.ApprovalPageLayout, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) SaveApprovalLayout(context.Context, *models.ApprovalPageLayout) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteApprovalLayout(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) SetDefaultApprovalLayout(context.Context, string, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) FindBestAssignment(context.Context, string, []string, string, string) (*models.LayoutAssignment, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateAssignment(context.Context, *models.LayoutAssignment) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteAssignment(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) GetAssignmentsForObject(context.Context, string) ([]*models.LayoutAssignment, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) DeleteAssignmentsByLayoutID(context.Context, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) WriteLayoutHistory(context.Context, *models.PageLayoutHistoryEntry) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) GetLayoutHistoryMaxVersion(context.Context, string) (int, error) {
	return 0, nil
}
func (r *recycleBinTestMetadataRepo) GetLayoutHistoryConfig(context.Context, string, int) (string, error) {
	return "", nil
}
func (r *recycleBinTestMetadataRepo) PruneLayoutHistory(context.Context, string, int) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) GetMigrationVersion(context.Context, string) (int, error) {
	return 0, nil
}
func (r *recycleBinTestMetadataRepo) SetMigrationVersion(context.Context, string, int, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) GetActiveTheme(context.Context) (*models.Theme, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetThemeByName(context.Context, string) (*models.Theme, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateTheme(context.Context, *models.Theme) error { return nil }
func (r *recycleBinTestMetadataRepo) UpdateTheme(context.Context, *models.Theme) error { return nil }
func (r *recycleBinTestMetadataRepo) ActivateTheme(context.Context, string) error      { return nil }
func (r *recycleBinTestMetadataRepo) GetAllApps(context.Context) ([]*models.AppConfig, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetApp(context.Context, string) (*models.AppConfig, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAppWithTx(context.Context, interface{}, string) (*models.AppConfig, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateApp(context.Context, *models.AppConfig) error { return nil }
func (r *recycleBinTestMetadataRepo) CreateAppMenuItem(context.Context, *models.AppConfig) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateApp(context.Context, string, *models.AppConfig) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateAppWithTx(context.Context, interface{}, string, *models.AppConfig) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteApp(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) GetAllDashboards(context.Context) ([]*models.DashboardConfig, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetDashboard(context.Context, string) (*models.DashboardConfig, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateDashboard(context.Context, *models.DashboardConfig) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateDashboard(context.Context, string, *models.DashboardConfig) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteDashboard(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) GetListViews(context.Context, string) ([]*models.ListView, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateListView(context.Context, *models.ListView) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateListView(context.Context, string, *models.ListView) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteListView(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) CountListViews(context.Context, string) (int, error) {
	return 0, nil
}
func (r *recycleBinTestMetadataRepo) GetActions(context.Context, string) ([]*models.ActionMetadata, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAction(context.Context, string) (*models.ActionMetadata, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAllActions(context.Context) ([]*models.ActionMetadata, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateAction(context.Context, *models.ActionMetadata) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateAction(context.Context, string, *models.ActionMetadata) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteAction(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) CheckActionExists(context.Context, string, string) (bool, error) {
	return false, nil
}
func (r *recycleBinTestMetadataRepo) GetSharingRules(context.Context, string) ([]*models.SharingRule, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetAllFlowDefinitions(context.Context) ([]*models.FlowDefinition, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetFlowDefinition(context.Context, string) (*models.FlowDefinition, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetFlowDefinitionByName(context.Context, string) (*models.FlowDefinition, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateFlowDefinition(context.Context, *models.FlowDefinition) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateFlowDefinition(context.Context, string, *models.FlowDefinition) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) PatchFlowDefinition(context.Context, string, *models.FlowDefinitionPatch) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) MarkFlowDefinitionCustomized(context.Context, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) SetLatestVersionId(context.Context, string, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) SetTriggerInfo(context.Context, string, string, string) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteFlowDefinition(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) GetFlowVersions(context.Context, string) ([]*models.FlowVersion, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetFlowVersion(context.Context, string) (*models.FlowVersion, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetFlowVersionsByIDs(context.Context, []string) (map[string]*models.FlowVersion, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetActiveFlowVersion(context.Context, string) (*models.FlowVersion, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateFlowVersion(context.Context, *models.FlowVersion) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) CreateSeededFlowVersion(context.Context, *models.FlowVersion) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) CreateTenantFlowVersion(context.Context, *models.FlowVersion) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateFlowVersion(context.Context, string, *models.FlowVersion) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) ActivateFlowVersion(context.Context, string) error   { return nil }
func (r *recycleBinTestMetadataRepo) DeactivateFlowVersion(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) DeleteFlowVersion(context.Context, string) error     { return nil }
func (r *recycleBinTestMetadataRepo) GetFlowInterviews(context.Context) ([]*models.FlowInterview, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetFlowInterview(context.Context, string) (*models.FlowInterview, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) SaveFlowInterview(context.Context, *models.FlowInterview) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteFlowInterview(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) GetValidationRules(context.Context, string) ([]*models.ValidationRule, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) GetValidationRule(context.Context, string) (*models.ValidationRule, error) {
	return nil, nil
}
func (r *recycleBinTestMetadataRepo) CreateValidationRule(context.Context, *models.ValidationRule) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) UpdateValidationRule(context.Context, string, *models.ValidationRule) error {
	return nil
}
func (r *recycleBinTestMetadataRepo) DeleteValidationRule(context.Context, string) error { return nil }
func (r *recycleBinTestMetadataRepo) CheckValidationRuleExists(context.Context, string, string) (bool, error) {
	return false, nil
}

func (r *recycleBinTestRecordRepo) Exists(context.Context, string, string) (bool, error) {
	return false, nil
}
func (r *recycleBinTestRecordRepo) GetLock(context.Context, string, string) (models.SObject, error) {
	return nil, nil
}
func (r *recycleBinTestRecordRepo) Insert(context.Context, string, models.SObject) error { return nil }
func (r *recycleBinTestRecordRepo) BulkInsert(context.Context, string, []models.SObject, []string, interface{}) error {
	return nil
}
func (r *recycleBinTestRecordRepo) BulkInsertWithLanding(_ context.Context, _ string, records []models.SObject, _ []string, _ interface{}) ([]bool, error) {
	landed := make([]bool, len(records))
	for i := range landed {
		landed[i] = true
	}
	return landed, nil
}
func (r *recycleBinTestRecordRepo) Update(_ context.Context, tableName string, id string, updates models.SObject) error {
	r.updatedTable = tableName
	r.updatedID = id
	r.updatedValues = updates
	return nil
}
func (r *recycleBinTestRecordRepo) Delete(_ context.Context, tableName string, id string) error {
	r.deletedTable = tableName
	r.deletedID = id
	return nil
}
func (r *recycleBinTestRecordRepo) GetChildren(context.Context, string, string, string) ([]models.SObject, error) {
	return nil, nil
}
func (r *recycleBinTestRecordRepo) ExistsByField(context.Context, string, string, interface{}) (bool, error) {
	return false, nil
}
func (r *recycleBinTestRecordRepo) FindOne(context.Context, string, string) (models.SObject, error) {
	return nil, nil
}
func (r *recycleBinTestRecordRepo) FindMany(context.Context, string, []string) (map[string]models.SObject, error) {
	return nil, nil
}
func (r *recycleBinTestRecordRepo) FindOneByField(context.Context, string, string, interface{}) (models.SObject, error) {
	return nil, nil
}
func (r *recycleBinTestRecordRepo) FindAny(ctx context.Context, tableName string, id string) (models.SObject, error) {
	if r.findAny != nil {
		return r.findAny(ctx, tableName, id)
	}
	if tableName == constants.TableUser && id == "user-1" {
		return models.SObject{
			constants.FieldID:        "user-1",
			constants.FieldFirstName: "Alice",
			constants.FieldLastName:  "Admin",
			constants.FieldUsername:  "alice@example.com",
		}, nil
	}
	return models.SObject{constants.FieldIsDeleted: true}, nil
}
func (r *recycleBinTestRecordRepo) PhysicalDelete(_ context.Context, tableName string, id string) error {
	r.physicalTable = tableName
	r.physicalID = id
	return nil
}
func (r *recycleBinTestRecordRepo) DeleteByField(context.Context, string, string, interface{}) error {
	return nil
}
func (r *recycleBinTestRecordRepo) CheckUniqueness(context.Context, *models.ObjectMetadata, string, interface{}, string) (bool, error) {
	return false, nil
}
func (r *recycleBinTestRecordRepo) FindRecycleBinItemsByUser(context.Context, string) ([]models.SObject, error) {
	return r.mine, nil
}
func (r *recycleBinTestRecordRepo) FindAllRecycleBinItems(context.Context) ([]models.SObject, error) {
	return r.all, nil
}
func (r *recycleBinTestRecordRepo) FindRecycleBinEntry(context.Context, string) (models.SObject, error) {
	return r.entry, nil
}
func (r *recycleBinTestRecordRepo) CheckQueueSupport(context.Context, string, string) (bool, error) {
	return false, nil
}
func (r *recycleBinTestRecordRepo) GetRecordType(context.Context, string) (string, error) {
	return "", nil
}
func (r *recycleBinTestRecordRepo) FindObjectTypesForIDs(context.Context, []string, []string) (map[string]string, error) {
	return nil, nil
}

var _ ports.RecordRepository = (*recycleBinTestRecordRepo)(nil)

type passthroughTxManager struct{}

func (passthroughTxManager) WithTransaction(ctx context.Context, fn func(context.Context) error) error {
	return fn(ctx)
}
func (passthroughTxManager) WithRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return fn(ctx)
}
func (passthroughTxManager) IsTransactionActive(context.Context) bool { return false }
func (passthroughTxManager) ExtractTx(context.Context) interface{}    { return nil }
func (passthroughTxManager) DetachTx(ctx context.Context) context.Context {
	return ctx
}

var _ ports.TransactionManager = (*passthroughTxManager)(nil)

func newRecycleBinTestContext(method, target string, systemPerms map[string]bool) (*gin.Context, *httptest.ResponseRecorder) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	req := httptest.NewRequest(method, target, nil)
	req = req.WithContext(constants.WithSecurityContext(req.Context(), constants.UserContext, "test recycle bin user request"))
	c.Request = req
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user-1",
		Name:      "Alice",
		ProfileId: "profile-1",
	})
	c.Set(constants.ContextKeySystemPermissions, systemPerms)
	return c, w
}

func newRecycleBinTestContextForUser(method, target string, user auth.UserSession, systemPerms map[string]bool) (*gin.Context, *httptest.ResponseRecorder) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	req := httptest.NewRequest(method, target, nil)
	req = req.WithContext(constants.WithSecurityContext(req.Context(), constants.UserContext, "test recycle bin user request"))
	c.Request = req
	c.Set(constants.ContextKeyUser, user)
	c.Set(constants.ContextKeySystemPermissions, systemPerms)
	return c, w
}

func newRecycleBinHandler(repo *recycleBinTestRecordRepo) *RecycleBinHandler {
	metadataSvc := metadata.NewMetadataService(&recycleBinTestMetadataRepo{}, nil, nil)
	permissionSvc := &permissions.PermissionService{}
	permissionSvc.SetCache(&recycleBinObjectPermCache{
		perms: map[string]*models.ObjectPermissions{
			"user-1|profile-1|account": {
				SobjectType:       "Account",
				PermissionsRead:   true,
				PermissionsEdit:   true,
				PermissionsDelete: true,
			},
		},
	})
	return NewRecycleBinHandler(&services.ServiceManager{
		TxManager:   passthroughTxManager{},
		Persistence: records.NewPersistenceService(repo, nil, metadataSvc, permissionSvc, nil, nil, passthroughTxManager{}, nil, nil, nil, nil, nil, nil),
		Permissions: permissionSvc,
	})
}

func TestRecycleBinHandler_ListRejectsOrgScopeWithoutGlobalPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler := newRecycleBinHandler(&recycleBinTestRecordRepo{})
	c, w := newRecycleBinTestContext(http.MethodGet, "/recyclebin?scope=all", map[string]bool{})

	handler.List(c)

	if w.Code != http.StatusForbidden {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusForbidden)
	}
}

func TestRecycleBinHandler_ListUsesServerScopeEndpoint(t *testing.T) {
	gin.SetMode(gin.TestMode)
	repo := &recycleBinTestRecordRepo{
		all: []models.SObject{{constants.FieldID: "rb-1"}},
	}
	handler := newRecycleBinHandler(repo)
	c, w := newRecycleBinTestContext(http.MethodGet, "/recyclebin?scope=all", map[string]bool{
		string(constants.PermViewAllData): true,
	})

	handler.List(c)

	if w.Code != http.StatusOK {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
	}
	if body := w.Body.String(); !strings.Contains(body, "\"records\":[{\"Id\":\"rb-1\"}]") {
		t.Fatalf("unexpected response body: %s", body)
	}
	if body := w.Body.String(); !strings.Contains(body, "\"canViewOrgScope\":true") {
		t.Fatalf("expected canViewOrgScope=true in response body: %s", body)
	}
	if body := w.Body.String(); !strings.Contains(body, "\"canMutateOrgScope\":false") {
		t.Fatalf("expected canMutateOrgScope=false in response body: %s", body)
	}
}

func TestRecycleBinHandler_RestoreUsesRecycleBinEntryOwnership(t *testing.T) {
	gin.SetMode(gin.TestMode)
	repo := &recycleBinTestRecordRepo{
		entry: models.SObject{
			constants.FieldID:            "rb-1",
			constants.FieldRecordID:      "001xx1",
			constants.FieldObjectAPIName: "Account",
			constants.FieldDeletedBy:     "user-1",
		},
	}
	handler := newRecycleBinHandler(repo)
	c, w := newRecycleBinTestContextForUser(http.MethodPost, "/recyclebin/restore/rb-1", auth.UserSession{
		ID:        "user-1",
		Name:      "Alice",
		ProfileId: "profile-1",
	}, map[string]bool{})
	c.Params = gin.Params{{Key: "id", Value: "rb-1"}}

	handler.Restore(c)

	if w.Code != http.StatusNoContent {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusNoContent)
	}
	if repo.updatedTable != "Account" || repo.updatedID != "001xx1" {
		t.Fatalf("restore updated %s/%s, want Account/001xx1", repo.updatedTable, repo.updatedID)
	}
	if repo.deletedTable != constants.TableRecycleBin || repo.deletedID != "rb-1" {
		t.Fatalf("restore deleted recycle bin entry %s/%s", repo.deletedTable, repo.deletedID)
	}
}

func TestRecycleBinHandler_PurgeRejectsEntryOutsideCallerScope(t *testing.T) {
	gin.SetMode(gin.TestMode)
	repo := &recycleBinTestRecordRepo{
		entry: models.SObject{
			constants.FieldID:            "rb-1",
			constants.FieldRecordID:      "001xx1",
			constants.FieldObjectAPIName: "Account",
			constants.FieldDeletedBy:     "user-2",
		},
	}
	handler := newRecycleBinHandler(repo)
	c, w := newRecycleBinTestContext(http.MethodDelete, "/recyclebin/rb-1", map[string]bool{})
	c.Params = gin.Params{{Key: "id", Value: "rb-1"}}

	handler.Purge(c)

	if w.Code != http.StatusForbidden {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusForbidden)
	}
	if repo.physicalTable != "" {
		t.Fatalf("purge should not run physical delete when scope is denied")
	}
}

func TestRecycleBinHandler_RestoreRejectsCrossUserEntryForViewAllData(t *testing.T) {
	gin.SetMode(gin.TestMode)
	repo := &recycleBinTestRecordRepo{
		entry: models.SObject{
			constants.FieldID:            "rb-1",
			constants.FieldRecordID:      "001xx1",
			constants.FieldObjectAPIName: "Account",
			constants.FieldDeletedBy:     "user-2",
		},
	}
	handler := newRecycleBinHandler(repo)
	c, w := newRecycleBinTestContext(http.MethodPost, "/recyclebin/restore/rb-1", map[string]bool{
		string(constants.PermViewAllData): true,
	})
	c.Params = gin.Params{{Key: "id", Value: "rb-1"}}

	handler.Restore(c)

	if w.Code != http.StatusForbidden {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusForbidden)
	}
	if repo.updatedTable != "" || repo.deletedTable != "" {
		t.Fatalf("restore should not mutate records when cross-user access is denied")
	}
}

func TestRecycleBinHandler_PurgeAllowsCrossUserEntryForModifyAllData(t *testing.T) {
	gin.SetMode(gin.TestMode)
	repo := &recycleBinTestRecordRepo{
		entry: models.SObject{
			constants.FieldID:            "rb-1",
			constants.FieldRecordID:      "001xx1",
			constants.FieldObjectAPIName: "Account",
			constants.FieldDeletedBy:     "user-2",
		},
	}
	handler := newRecycleBinHandler(repo)
	c, w := newRecycleBinTestContextForUser(http.MethodDelete, "/recyclebin/rb-1", auth.UserSession{
		ID:        "user-1",
		Name:      "Alice",
		ProfileId: "profile-1",
	}, map[string]bool{
		string(constants.PermModifyAllData): true,
	})
	c.Params = gin.Params{{Key: "id", Value: "rb-1"}}

	handler.Purge(c)

	if w.Code != http.StatusNoContent {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusNoContent)
	}
	if repo.physicalTable != "Account" || repo.physicalID != "001xx1" {
		t.Fatalf("purge physically deleted %s/%s, want Account/001xx1", repo.physicalTable, repo.physicalID)
	}
	if repo.deletedTable != constants.TableRecycleBin || repo.deletedID != "rb-1" {
		t.Fatalf("purge deleted recycle bin entry %s/%s", repo.deletedTable, repo.deletedID)
	}
}

func TestRecycleBinHandler_RestoreRejectsLiveTargetRows(t *testing.T) {
	gin.SetMode(gin.TestMode)
	repo := &recycleBinTestRecordRepo{
		entry: models.SObject{
			constants.FieldID:            "rb-1",
			constants.FieldRecordID:      "001xx1",
			constants.FieldObjectAPIName: "Account",
			constants.FieldDeletedBy:     "user-1",
		},
		findAny: func(_ context.Context, tableName string, id string) (models.SObject, error) {
			if tableName == constants.TableUser {
				return models.SObject{
					constants.FieldID:        "user-1",
					constants.FieldFirstName: "Alice",
					constants.FieldLastName:  "Admin",
					constants.FieldUsername:  "alice@example.com",
				}, nil
			}
			return models.SObject{constants.FieldIsDeleted: false}, nil
		},
	}
	handler := newRecycleBinHandler(repo)
	c, w := newRecycleBinTestContextForUser(http.MethodPost, "/recyclebin/restore/rb-1", auth.UserSession{
		ID:        "user-1",
		Name:      "Alice",
		ProfileId: constants.ProfileSystemAdmin,
	}, map[string]bool{})
	c.Params = gin.Params{{Key: "id", Value: "rb-1"}}

	handler.Restore(c)

	if w.Code != http.StatusConflict {
		t.Fatalf("status = %d, want %d", w.Code, http.StatusConflict)
	}
	if repo.updatedTable != "" {
		t.Fatalf("restore should not update live records")
	}
}
