package analytics

import (
	"context"
	"errors"
	"testing"

	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/backend/internal/domain/rls"
	"github.com/nexuscrm/backend/internal/features/metadata"
	"github.com/nexuscrm/backend/internal/features/permissions"
	"github.com/nexuscrm/shared/pkg/api"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

// --- Mock QueryRepository (internal package, can access unexported types) ---

type unitMockQueryRepo struct {
	mock.Mock
}

func (m *unitMockQueryRepo) Find(ctx context.Context, tableSchema *models.ObjectMetadata, req models.QueryRequest, rlsResult *rls.Result, visibleFields []string) (*models.QueryResult, error) {
	args := m.Called(ctx, tableSchema, req, rlsResult, visibleFields)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.QueryResult), args.Error(1)
}

func (m *unitMockQueryRepo) Search(ctx context.Context, schema *models.ObjectMetadata, term string, searchFields []string, selectFields []string, limit int, offset int, orderBy string, orderDir string, filterExpr string, rlsResult *rls.Result) ([]models.SObject, error) {
	args := m.Called(ctx, schema, term, searchFields, selectFields, limit, offset, orderBy, orderDir, filterExpr, rlsResult)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]models.SObject), args.Error(1)
}

func (m *unitMockQueryRepo) RunAnalytics(ctx context.Context, tableSchema *models.ObjectMetadata, q models.AnalyticsQuery, rlsResult *rls.Result) (interface{}, error) {
	args := m.Called(ctx, tableSchema, q, rlsResult)
	return args.Get(0), args.Error(1)
}

func (m *unitMockQueryRepo) ExecuteRawSQL(ctx context.Context, sql string, params []interface{}) ([]models.SObject, error) {
	args := m.Called(ctx, sql, params)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]models.SObject), args.Error(1)
}

func (m *unitMockQueryRepo) GetLookupNames(ctx context.Context, tableSchema *models.ObjectMetadata, ids []string, idField string, nameField string) ([]models.SObject, error) {
	args := m.Called(ctx, tableSchema, ids, idField, nameField)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]models.SObject), args.Error(1)
}

func (m *unitMockQueryRepo) GetRecordsByIDs(ctx context.Context, tableSchema *models.ObjectMetadata, ids []string, fields []string) ([]models.SObject, error) {
	args := m.Called(ctx, tableSchema, ids, fields)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]models.SObject), args.Error(1)
}

// --- Minimal Mock MetadataRepository ---

type unitMockMetaRepo struct {
	schemas []*models.ObjectMetadata
}

func newUnitMockMetaRepo(schemas ...*models.ObjectMetadata) *unitMockMetaRepo {
	return &unitMockMetaRepo{schemas: schemas}
}

func (m *unitMockMetaRepo) GetAllSchemas(ctx context.Context) ([]*models.ObjectMetadata, error) {
	return m.schemas, nil
}

func (m *unitMockMetaRepo) GetSchemaByAPIName(ctx context.Context, apiName string) (*models.ObjectMetadata, error) {
	for _, s := range m.schemas {
		if s.APIName == apiName {
			return s, nil
		}
	}
	return nil, nil
}

// Stub all other MetadataRepository methods
func (m *unitMockMetaRepo) GetFieldsForObject(ctx context.Context, objectID string) ([]models.FieldMetadata, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetRecordTypes(ctx context.Context, objectAPIName string) ([]*models.RecordType, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAutoNumbers(ctx context.Context, objectAPIName string) ([]*models.AutoNumber, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAllAutoNumbers(_ context.Context) ([]*models.AutoNumber, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAllIdentifierRules(_ context.Context) ([]*models.IdentifierRule, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAllValidationRules(_ context.Context) ([]*models.ValidationRule, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAllRelationships(_ context.Context) ([]*models.Relationship, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetRelationships(ctx context.Context, childObjectAPIName string) ([]*models.Relationship, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetFieldDependencies(ctx context.Context, objectAPIName string) ([]*models.FieldDependency, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetChildRelationships(ctx context.Context, parentObjectAPIName string) ([]*models.ObjectMetadata, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) ConfigureAutoNumberField(ctx context.Context, objectAPIName, fieldAPIName, format string, start int64) error {
	return nil
}
func (m *unitMockMetaRepo) UpsertUIComponent(ctx context.Context, component *models.UIComponent) error {
	return nil
}
func (m *unitMockMetaRepo) UpsertSetupPage(ctx context.Context, page *models.SetupPage) error {
	return nil
}
func (m *unitMockMetaRepo) GetSetupPages(ctx context.Context) ([]*models.SetupPage, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) UpsertLayout(ctx context.Context, layout *models.PageLayout) error {
	return nil
}
func (m *unitMockMetaRepo) SaveLayout(ctx context.Context, layout *models.PageLayout) error {
	return nil
}
func (m *unitMockQueryRepo) MarkLayoutCustomized(_ context.Context, _ string) {}
func (m *unitMockMetaRepo) GetLayout(ctx context.Context, id string) (*models.PageLayout, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetLayouts(ctx context.Context, objectAPIName string) ([]*models.PageLayout, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) DeleteLayout(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) AssignLayoutToProfile(ctx context.Context, profileID, objectAPIName, layoutID string) error {
	return nil
}
func (m *unitMockMetaRepo) GetLayoutIDForProfile(ctx context.Context, objectAPIName, profileID string) (string, error) {
	return "", nil
}
func (m *unitMockMetaRepo) GetRelatedListConfigs(ctx context.Context, layoutObjectAPIName string) ([]ports.RelatedListConfig, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetActiveTheme(ctx context.Context) (*models.Theme, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetThemeByName(ctx context.Context, name string) (*models.Theme, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateTheme(ctx context.Context, theme *models.Theme) error { return nil }
func (m *unitMockMetaRepo) UpdateTheme(ctx context.Context, theme *models.Theme) error { return nil }
func (m *unitMockMetaRepo) ActivateTheme(ctx context.Context, themeID string) error    { return nil }
func (m *unitMockMetaRepo) GetAllApps(ctx context.Context) ([]*models.AppConfig, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetApp(ctx context.Context, id string) (*models.AppConfig, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAppWithTx(ctx context.Context, tx interface{}, id string) (*models.AppConfig, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateApp(ctx context.Context, app *models.AppConfig) error { return nil }
func (m *unitMockMetaRepo) CreateAppMenuItem(ctx context.Context, app *models.AppConfig) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateApp(ctx context.Context, id string, app *models.AppConfig) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateAppWithTx(ctx context.Context, tx interface{}, id string, app *models.AppConfig) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteApp(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) GetAllDashboards(ctx context.Context) ([]*models.DashboardConfig, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetDashboard(ctx context.Context, id string) (*models.DashboardConfig, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateDashboard(ctx context.Context, dashboard *models.DashboardConfig) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateDashboard(ctx context.Context, id string, dashboard *models.DashboardConfig) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteDashboard(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) GetListViews(ctx context.Context, objectAPIName string) ([]*models.ListView, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateListView(ctx context.Context, lv *models.ListView) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateListView(ctx context.Context, id string, lv *models.ListView) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteListView(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) CountListViews(ctx context.Context, objectAPIName string) (int, error) {
	return 0, nil
}
func (m *unitMockMetaRepo) GetActions(ctx context.Context, objectAPIName string) ([]*models.ActionMetadata, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAction(ctx context.Context, id string) (*models.ActionMetadata, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAllActions(ctx context.Context) ([]*models.ActionMetadata, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateAction(ctx context.Context, action *models.ActionMetadata) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateAction(ctx context.Context, actionID string, updates *models.ActionMetadata) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteAction(ctx context.Context, actionID string) error { return nil }
func (m *unitMockMetaRepo) CheckActionExists(ctx context.Context, objectAPIName, name string) (bool, error) {
	return false, nil
}
func (m *unitMockMetaRepo) GetSharingRules(ctx context.Context, objectAPIName string) ([]*models.SharingRule, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetAllFlowDefinitions(ctx context.Context) ([]*models.FlowDefinition, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetFlowDefinition(ctx context.Context, id string) (*models.FlowDefinition, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetFlowDefinitionByName(ctx context.Context, developerName string) (*models.FlowDefinition, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateFlowDefinition(ctx context.Context, def *models.FlowDefinition) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateFlowDefinition(ctx context.Context, id string, def *models.FlowDefinition) error {
	return nil
}
func (m *unitMockMetaRepo) PatchFlowDefinition(ctx context.Context, id string, patch *models.FlowDefinitionPatch) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteFlowDefinition(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) GetFlowVersions(ctx context.Context, definitionId string) ([]*models.FlowVersion, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetFlowVersionsByIDs(_ context.Context, _ []string) (map[string]*models.FlowVersion, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetFlowVersion(ctx context.Context, id string) (*models.FlowVersion, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetActiveFlowVersion(ctx context.Context, definitionId string) (*models.FlowVersion, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateFlowVersion(ctx context.Context, version *models.FlowVersion) error {
	return nil
}
func (m *unitMockMetaRepo) CreateSeededFlowVersion(ctx context.Context, version *models.FlowVersion) error {
	return nil
}
func (m *unitMockMetaRepo) CreateTenantFlowVersion(ctx context.Context, version *models.FlowVersion) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateFlowVersion(ctx context.Context, id string, version *models.FlowVersion) error {
	return nil
}
func (m *unitMockMetaRepo) ActivateFlowVersion(ctx context.Context, id string) error   { return nil }
func (m *unitMockMetaRepo) DeactivateFlowVersion(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) DeleteFlowVersion(ctx context.Context, id string) error     { return nil }
func (m *unitMockMetaRepo) SetLatestVersionId(ctx context.Context, definitionId string, versionId string) error {
	return nil
}
func (m *unitMockMetaRepo) SetTriggerInfo(ctx context.Context, definitionId string, triggerType string, objectOrEvent string) error {
	return nil
}
func (m *unitMockMetaRepo) SetDefaultLayout(ctx context.Context, objectAPIName string, layoutID string, actorID *string) error {
	return nil
}
func (m *unitMockMetaRepo) GetFlowInterviews(ctx context.Context) ([]*models.FlowInterview, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetFlowInterview(ctx context.Context, id string) (*models.FlowInterview, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) SaveFlowInterview(ctx context.Context, interview *models.FlowInterview) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteFlowInterview(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) GetValidationRules(ctx context.Context, objectAPIName string) ([]*models.ValidationRule, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetValidationRule(ctx context.Context, id string) (*models.ValidationRule, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateValidationRule(ctx context.Context, rule *models.ValidationRule) error {
	return nil
}
func (m *unitMockMetaRepo) UpdateValidationRule(ctx context.Context, id string, updates *models.ValidationRule) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteValidationRule(ctx context.Context, id string) error { return nil }
func (m *unitMockMetaRepo) CheckValidationRuleExists(ctx context.Context, objectAPIName, name string) (bool, error) {
	return false, nil
}
func (m *unitMockMetaRepo) GetApprovalLayouts(ctx context.Context, objectAPIName string) ([]*models.ApprovalPageLayout, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) GetApprovalLayout(ctx context.Context, id string) (*models.ApprovalPageLayout, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) SaveApprovalLayout(ctx context.Context, layout *models.ApprovalPageLayout) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteApprovalLayout(ctx context.Context, id string) error {
	return nil
}
func (m *unitMockMetaRepo) SetDefaultApprovalLayout(ctx context.Context, objectAPIName string, layoutID string) error {
	return nil
}

// --- Mock PermissionRepository ---

type unitMockPermRepo struct {
	mock.Mock
	grantsByPSetID map[string]map[string]bool
}

func (m *unitMockPermRepo) LoadEffectiveObjectPermission(ctx context.Context, user *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error) {
	args := m.Called(ctx, user, objectAPIName)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.ObjectPermissions), args.Error(1)
}
func (m *unitMockPermRepo) LoadEffectiveFieldPermission(ctx context.Context, user *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	args := m.Called(ctx, user, objectAPIName, fieldAPIName)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.FieldPermissions), args.Error(1)
}

// Stubs for the rest of PermissionRepository
func (m *unitMockPermRepo) LoadObjectPermission(ctx context.Context, profileID, objectAPIName string) (*models.ObjectPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) LoadFieldPermission(ctx context.Context, profileID, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListObjectPermissions(ctx context.Context, profileID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListFieldPermissions(ctx context.Context, profileID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) UpsertObjectPermission(ctx context.Context, perm models.ObjectPermissions) error {
	return nil
}
func (m *unitMockPermRepo) BatchUpsertObjectPermissions(ctx context.Context, perms []models.ObjectPermissions) error {
	return nil
}
func (m *unitMockPermRepo) BatchUpsertFieldPermissions(ctx context.Context, perms []models.FieldPermissions) error {
	return nil
}
func (m *unitMockPermRepo) DeleteFieldPermissionsForFields(ctx context.Context, objectAPIName string, fieldAPINames []string) error {
	return nil
}
func (m *unitMockPermRepo) CountObjectPermissionsByProfile(ctx context.Context, profileID string) (int, error) {
	return 0, nil
}
func (m *unitMockPermRepo) UpsertFieldPermission(ctx context.Context, perm models.FieldPermissions) error {
	return nil
}
func (m *unitMockPermRepo) ListPermissionSetObjectPermissions(ctx context.Context, permissionSetID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListPermissionSetFieldPermissions(ctx context.Context, permissionSetID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListEffectiveObjectPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListEffectiveFieldPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *unitMockPermRepo) CreatePermissionSet(ctx context.Context, name, label, description string) (string, error) {
	return "", nil
}
func (m *unitMockPermRepo) UpdatePermissionSet(ctx context.Context, id, name, label, description string) error {
	return nil
}
func (m *unitMockPermRepo) DeletePermissionSet(ctx context.Context, id string) error { return nil }
func (m *unitMockPermRepo) GetUserProfileID(ctx context.Context, userID string) (string, error) {
	return "", nil
}
func (m *unitMockPermRepo) GetAllRoles(ctx context.Context) ([]*models.UserRole, error) {
	return nil, nil
}
func (m *unitMockPermRepo) CreateRole(ctx context.Context, name, description string, parentRoleID *string) (string, error) {
	return "", nil
}
func (m *unitMockPermRepo) GetRole(ctx context.Context, id string) (*models.UserRole, error) {
	return nil, nil
}
func (m *unitMockPermRepo) UpdateRole(ctx context.Context, id, name, description string, parentRoleID *string) error {
	return nil
}
func (m *unitMockPermRepo) DeleteRole(ctx context.Context, id string) error { return nil }
func (m *unitMockPermRepo) IsUserInGroup(ctx context.Context, groupID, userID string) (bool, error) {
	return false, nil
}
func (m *unitMockPermRepo) GetManualShareAccessLevels(ctx context.Context, objectAPIName, recordID, userID string) ([]string, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetTeamMemberAccessLevel(ctx context.Context, objectAPIName, recordID, userID string) (*string, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetRecordOwnerID(ctx context.Context, tableName, recordID string) (string, error) {
	return "", nil
}
func (m *unitMockPermRepo) GetRecordByID(ctx context.Context, tableName, recordID string) (models.SObject, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetPendingApprovalLockInfo(ctx context.Context, targetObjectID string) (string, string, error) {
	return "", "", nil
}
func (m *unitMockPermRepo) HasPendingApprovalWorkitemForUser(ctx context.Context, processInstanceID, userID string) (bool, error) {
	return false, nil
}
func (m *unitMockPermRepo) GetUserGroupIDs(ctx context.Context, userID string) ([]string, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetManualShareRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetTeamMemberRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetAllProfiles(ctx context.Context) ([]*models.Profile, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListSystemPermissions(ctx context.Context) ([]models.SystemPermission, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetEffectiveSystemPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetEffectiveCustomPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	return nil, nil
}
func (m *unitMockPermRepo) ListCustomPermissions(ctx context.Context) ([]models.SystemCustomPermission, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetCustomPermission(ctx context.Context, id string) (*models.SystemCustomPermission, error) {
	return nil, nil
}
func (m *unitMockPermRepo) CreateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error {
	return nil
}
func (m *unitMockPermRepo) UpdateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error {
	return nil
}
func (m *unitMockPermRepo) DeleteCustomPermission(ctx context.Context, id string) error {
	return nil
}
func (m *unitMockPermRepo) ListPermissionSetGroups(ctx context.Context) ([]models.PermissionSetGroup, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetPermissionSetGroupByID(ctx context.Context, id string) (*models.PermissionSetGroup, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetPermissionSetGroupComponents(ctx context.Context, groupID string) ([]ports.PermissionSetRef, error) {
	return nil, nil
}
func (m *unitMockPermRepo) CreatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error {
	return nil
}
func (m *unitMockPermRepo) UpdatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error {
	return nil
}
func (m *unitMockPermRepo) DeletePermissionSetGroup(ctx context.Context, id string) error {
	return nil
}
func (m *unitMockPermRepo) CleanupDirectProfilePermissions(ctx context.Context, profileIDs []string) error {
	return nil
}
func (m *unitMockPermRepo) BulkEnsureProfilePermissionSets(_ context.Context, _ []struct{ ID, Name string }) (map[string]string, error) {
	return nil, nil
}
func (m *unitMockPermRepo) BulkCountObjectPermissionsByProfiles(_ context.Context, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *unitMockPermRepo) BulkCountFieldPermissionsByProfiles(_ context.Context, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *unitMockPermRepo) BulkCountObjectPermissionsByProfilesAndTypes(_ context.Context, _ []string, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *unitMockPermRepo) BulkCountFieldPermissionsByProfilesAndTypes(_ context.Context, _ []string, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *unitMockPermRepo) EnsureProfilePermissionSet(ctx context.Context, profileID, profileName string) (string, error) {
	return "ps_" + profileID, nil
}
func (m *unitMockPermRepo) CleanupProfilePermissionSet(ctx context.Context, profileID string) error {
	return nil
}
func (m *unitMockPermRepo) LockPermissionSet(_ context.Context, _ string) (bool, error) {
	return true, nil
}
func (m *unitMockPermRepo) GetPermissionSetGrants(ctx context.Context, permSetID string) (map[string]bool, error) {
	return nil, nil
}
func (m *unitMockPermRepo) GetPermissionSetGrantsMany(ctx context.Context, permSetIDs []string) (map[string]map[string]bool, error) {
	return m.grantsByPSetID, nil
}
func (m *unitMockPermRepo) SetSystemPermission(ctx context.Context, permSetID, apiName string, enabled bool) error {
	return nil
}

// --- Helper to build QueryService with mocks (admin-friendly, no permission mock needed) ---

func newTestQueryService(schemas []*models.ObjectMetadata, repo *unitMockQueryRepo) *QueryService {
	metaRepo := newUnitMockMetaRepo(schemas...)
	permRepo := new(unitMockPermRepo)
	// Default: allow all for admin users (system admin bypass is handled by PermissionService)
	// For non-admin tests, use newTestQueryServiceWithPermRepo instead.
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, mock.Anything).
		Return(&models.ObjectPermissions{PermissionsRead: true, PermissionsCreate: true, PermissionsEdit: true, PermissionsDelete: true}, nil).Maybe()
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: true, PermissionsEdit: true}, nil).Maybe()
	metaSvc := metadata.NewMetadataService(metaRepo, nil, nil)
	permSvc := permissions.NewPermissionService(permRepo, metaSvc, nil)
	return NewQueryService(repo, metaSvc, permSvc, nil)
}

// newTestQueryServiceWithPermRepo builds a QueryService with a specific permission repo for fine-grained permission testing.
func newTestQueryServiceWithPermRepo(schemas []*models.ObjectMetadata, repo *unitMockQueryRepo, permRepo *unitMockPermRepo) *QueryService {
	metaRepo := newUnitMockMetaRepo(schemas...)
	metaSvc := metadata.NewMetadataService(metaRepo, nil, nil)
	permSvc := permissions.NewPermissionService(permRepo, metaSvc, nil)
	return NewQueryService(repo, metaSvc, permSvc, nil)
}

// === QueryService.Query Tests ===

func TestQueryService_Query_PermissionDenied(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	// Non-admin user with read denied
	user := &models.UserSession{ID: "user-1", ProfileID: "standard-profile"}
	ctx := context.Background()

	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, user, "Account").Return(
		&models.ObjectPermissions{PermissionsRead: false}, nil)

	_, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		Limit:         10,
	}, user)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot read")
}

func TestQueryService_Query_ScopedInternalReadBypassesObjectPermissionAndRLS(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
			{APIName: "InternalNote", Type: constants.FieldTypeString},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard-profile"}
	ctx := constants.WithStandardActionInternalAccessScopes(context.Background(), "sa_convertlead", []constants.StandardActionInternalAccessScope{{
		StandardActionID: "sa_convertlead",
		ObjectAPIName:    "Account",
		Operation:        constants.PermRead,
	}})

	expectedResult := &models.QueryResult{TotalSize: 1, Done: true, Records: []models.SObject{{"Id": "001"}}}
	repo.On("Find",
		mock.Anything,
		mock.Anything,
		mock.Anything,
		(*rls.Result)(nil),
		mock.MatchedBy(func(fields []string) bool {
			for _, field := range fields {
				if field == "InternalNote" {
					return true
				}
			}
			return false
		}),
	).Return(expectedResult, nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		Limit:         10,
	}, user)

	require.NoError(t, err)
	assert.Equal(t, 1, result.TotalSize)
	permRepo.AssertNotCalled(t, "LoadEffectiveObjectPermission", mock.Anything, user, "Account")
}

func TestQueryService_Query_ObjectNotFound(t *testing.T) {
	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{}, repo)

	// Admin user but object doesn't exist
	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	_, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "NonExistent",
		Limit:         10,
	}, user)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
}

func TestQueryService_Query_Normal(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	expectedResult := &models.QueryResult{
		TotalSize: 1,
		Done:      true,
		Records:   []models.SObject{{"Id": "001", "Name": "Acme"}},
	}
	repo.On("Find", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedResult, nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		Limit:         10,
	}, user)

	require.NoError(t, err)
	assert.Equal(t, 1, result.TotalSize)
	assert.True(t, result.Done)
	assert.Len(t, result.Records, 1)
}

func TestQueryService_Query_ExplicitPermissionVirtualProjectsOnlyRequestedField(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: constants.TablePermissionSet,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true},
			{
				APIName:              "PermissionsViewSetup",
				Type:                 constants.FieldTypeBoolean,
				IsVirtual:            true,
				VirtualKind:          constants.VirtualFieldSystemPermission,
				VirtualSourceAPIName: "ViewSetup",
			},
			{
				APIName:              "PermissionsApiEnabled",
				Type:                 constants.FieldTypeBoolean,
				IsVirtual:            true,
				VirtualKind:          constants.VirtualFieldSystemPermission,
				VirtualSourceAPIName: "ApiEnabled",
			},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := &unitMockPermRepo{
		grantsByPSetID: map[string]map[string]bool{
			"ps-1": {
				"ViewSetup":  true,
				"ApiEnabled": true,
			},
		},
	}
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.QueryResult{
			TotalSize: 1,
			Done:      true,
			Records:   []models.SObject{{constants.FieldID: "ps-1"}},
		}, nil).Once()

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: constants.TablePermissionSet,
		SelectFields:  []string{"PermissionsViewSetup"},
		Limit:         10,
	}, user)

	require.NoError(t, err)
	require.Len(t, result.Records, 1)
	assert.Equal(t, true, result.Records[0]["PermissionsViewSetup"])
	assert.NotContains(t, result.Records[0], "PermissionsApiEnabled")
}

func TestQueryService_Query_WithCriteria(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find", mock.Anything, mock.Anything, mock.MatchedBy(func(req models.QueryRequest) bool {
		return len(req.Criteria) == 1 && req.Criteria[0].Field == "Name" && req.Criteria[0].Op == "=" && req.Criteria[0].Val == "Acme"
	}), mock.Anything, mock.Anything).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		Criteria:      []api.QueryCriterion{{Field: "Name", Op: "=", Val: "Acme"}},
		Limit:         10,
	}, user)

	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestQueryService_Query_WithOrderBy(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find", mock.Anything, mock.Anything, mock.MatchedBy(func(req models.QueryRequest) bool {
		return len(req.OrderBy) == 1 && req.OrderBy[0].Field == "Name" && req.OrderBy[0].Direction == "ASC"
	}), mock.Anything, mock.Anything).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		OrderBy:       []api.SortCriterion{{Field: "Name", Direction: "ASC"}},
		Limit:         10,
	}, user)

	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestQueryService_Query_AggregateCount(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	// RunAnalytics returns a scalar for COUNT
	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.MatchedBy(func(q models.AnalyticsQuery) bool {
		return q.Operation == constants.AnalyticsOperationCount
	}), mock.Anything).Return(int64(42), nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{"COUNT(Id)"},
	}, user)

	require.NoError(t, err)
	require.Len(t, result.Records, 1)
	assert.Equal(t, int64(42), result.Records[0]["expr0"])
}

func TestQueryService_Query_AggregateGroupBy(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "StageName", Type: constants.FieldTypeString},
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	// RunAnalytics returns grouped results
	groupedResults := []models.SObject{
		{"name": "Closed Won", "val": float64(100000)},
		{"name": "Prospecting", "val": float64(25000)},
	}
	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.MatchedBy(func(q models.AnalyticsQuery) bool {
		return q.Operation == constants.AnalyticsOperationGroupBy
	}), mock.Anything).Return(groupedResults, nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{"SUM(Amount)"},
		GroupBy:       "StageName",
	}, user)

	require.NoError(t, err)
	require.Len(t, result.Records, 2)
	assert.Equal(t, "Closed Won", result.Records[0]["StageName"])
	assert.Equal(t, float64(100000), result.Records[0]["expr0"])
}

func TestQueryService_Query_AggregateError(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("analytics error"))

	_, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{"COUNT(Id)"},
	}, user)

	assert.Error(t, err)
}

func TestQueryService_Query_SystemContext_SubqueryBypassesChildObjectPermission(t *testing.T) {
	relationshipName := "Contacts"
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}
	contactSchema := &models.ObjectMetadata{
		APIName: "Contact",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "LastName", Type: constants.FieldTypeString},
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}, RelationshipName: &relationshipName},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, mock.Anything).
		Return(&models.ObjectPermissions{PermissionsRead: false}, nil).Maybe()
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: false}, nil).Maybe()
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{accountSchema, contactSchema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard-profile"}
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "test: subquery bypasses child object perm")

	repo.On("Find",
		mock.Anything,
		mock.MatchedBy(func(schema *models.ObjectMetadata) bool { return schema.APIName == "Account" }),
		mock.Anything,
		mock.MatchedBy(func(result *rls.Result) bool { return result == nil }),
		mock.Anything,
	).Return(&models.QueryResult{
		Records: []models.SObject{{"Id": "acc-1", "Name": "Acme"}},
	}, nil).Once()

	repo.On("Find",
		mock.Anything,
		mock.MatchedBy(func(schema *models.ObjectMetadata) bool { return schema.APIName == "Contact" }),
		mock.MatchedBy(func(req models.QueryRequest) bool {
			return req.ObjectAPIName == "Contact"
		}),
		mock.MatchedBy(func(result *rls.Result) bool { return result == nil }),
		mock.Anything,
	).Return(&models.QueryResult{
		Records: []models.SObject{{"Id": "con-1", "AccountId": "acc-1", "LastName": "Smith"}},
	}, nil).Once()

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		Limit:         10,
		SubQueries: []api.SubQueryRequest{
			{
				RelationshipName: "Contacts",
				SelectFields:     []string{"LastName"},
			},
		},
	}, user)

	require.NoError(t, err)
	contacts, ok := result.Records[0]["Contacts"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, 1, contacts["totalSize"])
	rows, ok := contacts["records"].([]models.SObject)
	require.True(t, ok)
	require.Len(t, rows, 1)
	assert.Equal(t, "Smith", rows[0]["LastName"])
}

func TestQueryService_Query_Aggregate_SystemContextBypassesBusinessChecks(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, mock.Anything).
		Return(&models.ObjectPermissions{PermissionsRead: false}, nil).Maybe()
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: false}, nil).Maybe()
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard-profile"}
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "test: aggregate bypasses business checks")

	repo.On("RunAnalytics",
		mock.Anything,
		mock.MatchedBy(func(schema *models.ObjectMetadata) bool { return schema.APIName == "Opportunity" }),
		mock.MatchedBy(func(q models.AnalyticsQuery) bool {
			return q.ObjectAPIName == "Opportunity" &&
				q.Operation == constants.AnalyticsOperationSum &&
				q.Field != nil &&
				*q.Field == "Amount"
		}),
		mock.MatchedBy(func(result *rls.Result) bool { return result == nil }),
	).Return(float64(50000), nil).Once()

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{"SUM(Amount)"},
	}, user)

	require.NoError(t, err)
	require.Len(t, result.Records, 1)
	assert.Equal(t, float64(50000), result.Records[0]["expr0"])
}

// === QueryService.QueryWithFilter Tests ===

func TestQueryService_QueryWithFilter(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find", mock.Anything, mock.Anything, mock.MatchedBy(func(req models.QueryRequest) bool {
		return req.ObjectAPIName == "Account" && req.FilterExpr == "Name = 'Acme'" && req.SortField == "Name" && req.SortDirection == "ASC" && req.Limit == 50
	}), mock.Anything, mock.Anything).Return(&models.QueryResult{Records: []models.SObject{{"Id": "001"}}}, nil)

	result, err := qs.QueryWithFilter(ctx, "Account", "Name = 'Acme'", user, "Name", "ASC", 50)
	require.NoError(t, err)
	assert.Len(t, result.Records, 1)
}

// === QueryService.RunAnalytics Tests ===

func TestQueryService_RunAnalytics_PermissionDenied(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard"}
	ctx := context.Background()

	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, user, "Opportunity").Return(
		&models.ObjectPermissions{PermissionsRead: false}, nil)

	_, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationCount,
	}, user)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot read")
}

func TestQueryService_RunAnalytics_ObjectNotFound(t *testing.T) {
	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	_, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "NonExistent",
		Operation:     constants.AnalyticsOperationCount,
	}, user)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
}

func TestQueryService_RunAnalytics_ScalarResult(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	field := "Amount"
	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(float64(50000), nil)

	result, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationSum,
		Field:         &field,
	}, user)

	require.NoError(t, err)
	assert.Equal(t, float64(50000), result)
}

func TestQueryService_RunAnalytics_GroupBy(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "StageName", Type: constants.FieldTypeString},
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	groupBy := "StageName"
	field := "Amount"
	groupedResult := []models.SObject{
		{"name": "Closed Won", "val": float64(100000)},
		{"name": "Open", "val": float64(50000)},
	}
	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(groupedResult, nil)

	result, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationGroupBy,
		GroupBy:       &groupBy,
		Field:         &field,
	}, user)

	require.NoError(t, err)
	rows, ok := result.([]models.SObject)
	require.True(t, ok)
	assert.Len(t, rows, 2)
}

func TestQueryService_RunAnalytics_GroupByLookupHydration(t *testing.T) {
	// Schema where the group by field is a lookup reference
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
		},
	}

	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema, accountSchema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	groupBy := "AccountId"
	field := "Amount"
	groupedResult := []models.SObject{
		{"name": "acc-001", "val": float64(100000)},
		{"name": "acc-002", "val": float64(50000)},
	}
	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(groupedResult, nil)

	// GetLookupNames resolves IDs to names
	repo.On("GetLookupNames", mock.Anything, mock.MatchedBy(func(s *models.ObjectMetadata) bool {
		return s.APIName == "Account"
	}), mock.Anything, constants.FieldID, "Name").Return([]models.SObject{
		{"Id": "acc-001", "Name": "Acme Corp"},
		{"Id": "acc-002", "Name": "Globex Inc"},
	}, nil)

	result, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationGroupBy,
		GroupBy:       &groupBy,
		Field:         &field,
	}, user)

	require.NoError(t, err)
	rows, ok := result.([]models.SObject)
	require.True(t, ok)
	assert.Equal(t, "Acme Corp", rows[0]["name"])
	assert.Equal(t, "Globex Inc", rows[1]["name"])
}

func TestQueryService_RunAnalytics_FLSDeniedField(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Salary", Type: constants.FieldTypeCurrency},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard-user"}
	ctx := context.Background()

	// Object read allowed, but field visibility denied
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, user, "Opportunity").Return(
		&models.ObjectPermissions{PermissionsRead: true}, nil)
	field := "Salary"
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, user, "Opportunity", "Salary").Return(
		&models.FieldPermissions{PermissionsRead: false}, nil)

	_, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationSum,
		Field:         &field,
	}, user)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot read")
}

func TestQueryService_RunAnalytics_FLSDeniedGroupBy(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "SecretField", Type: constants.FieldTypeString},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard-user"}
	ctx := context.Background()

	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, user, "Opportunity").Return(
		&models.ObjectPermissions{PermissionsRead: true}, nil)
	groupBy := "SecretField"
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, user, "Opportunity", "SecretField").Return(
		&models.FieldPermissions{PermissionsRead: false}, nil)

	_, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationGroupBy,
		GroupBy:       &groupBy,
	}, user)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot read")
}

func TestQueryService_RunAnalytics_RepoError(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("db error"))

	_, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationCount,
	}, user)

	assert.Error(t, err)
}

// === hydrateAnalyticsGroupNames Tests ===

func TestHydrateAnalyticsGroupNames_NonLookupField(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "StageName", Type: constants.FieldTypeString},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	results := []models.SObject{
		{"name": "Closed Won", "val": float64(1000)},
	}

	hydrated := qs.hydrateAnalyticsGroupNames(context.Background(), results, schema, "StageName")
	assert.Equal(t, "Closed Won", hydrated[0]["name"]) // Unchanged (not a lookup)
}

func TestHydrateAnalyticsGroupNames_FieldNotFound(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields:  []models.FieldMetadata{},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	results := []models.SObject{
		{"name": "some-id", "val": float64(100)},
	}

	hydrated := qs.hydrateAnalyticsGroupNames(context.Background(), results, schema, "UnknownField")
	assert.Equal(t, "some-id", hydrated[0]["name"]) // Unchanged
}

func TestHydrateAnalyticsGroupNames_LookupNoReferenceTo(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "CustomRef", Type: constants.FieldTypeReference}, // No ReferenceTo
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	results := []models.SObject{
		{"name": "ref-123", "val": float64(100)},
	}

	hydrated := qs.hydrateAnalyticsGroupNames(context.Background(), results, schema, "CustomRef")
	assert.Equal(t, "ref-123", hydrated[0]["name"]) // Unchanged
}

func TestHydrateAnalyticsGroupNames_EmptyIDs(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		},
	}
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema, accountSchema}, repo)

	// Results with no name values
	results := []models.SObject{
		{"val": float64(100)},
	}

	hydrated := qs.hydrateAnalyticsGroupNames(context.Background(), results, schema, "AccountId")
	assert.Len(t, hydrated, 1) // Unchanged, no IDs to resolve
}

func TestHydrateAnalyticsGroupNames_LookupError(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		},
	}
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema, accountSchema}, repo)

	results := []models.SObject{
		{"name": "acc-001", "val": float64(100)},
	}

	repo.On("GetLookupNames", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("db error"))

	hydrated := qs.hydrateAnalyticsGroupNames(context.Background(), results, schema, "AccountId")
	assert.Equal(t, "acc-001", hydrated[0]["name"]) // Fallback: unchanged on error
}

func TestHydrateAnalyticsGroupNames_RefSchemaNotFound(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"UnknownObj"}},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	results := []models.SObject{
		{"name": "ref-001", "val": float64(100)},
	}

	hydrated := qs.hydrateAnalyticsGroupNames(context.Background(), results, schema, "AccountId")
	assert.Equal(t, "ref-001", hydrated[0]["name"]) // Unchanged
}

// === QueryService.ExecuteRawSQL Tests ===

func TestQueryService_ExecuteRawSQL_AdminUser(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	expectedRows := []models.SObject{{"Id": "001", "Name": "Acme"}}
	repo.On("ExecuteRawSQL", mock.Anything, mock.Anything, mock.Anything).Return(expectedRows, nil)

	rows, err := qs.ExecuteRawSQL(ctx, "SELECT Id, Name FROM Account", nil, user)
	require.NoError(t, err)
	assert.Len(t, rows, 1)
}

// === hydrateVirtualFields Tests ===

func TestHydrateVirtualFields_NoFormulaFields(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin}
	ctx := context.Background()

	rows := []models.SObject{
		{"Id": "001", "Name": "Acme"},
	}

	result := qs.hydrateVirtualFields(ctx, rows, schema, []string{"Id", "Name"}, user)
	assert.Len(t, result, 1)
}

func TestHydrateVirtualFields_WithFormulaField(t *testing.T) {
	formulaExpr := "1 + 1"
	returnType := constants.FieldTypeDouble
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
			{APIName: "CalcField", Type: constants.FieldTypeFormula, Formula: &formulaExpr, ReturnType: &returnType},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin}
	ctx := context.Background()

	rows := []models.SObject{
		{"Id": "001", "Name": "Acme"},
	}

	result := qs.hydrateVirtualFields(ctx, rows, schema, []string{"Id", "Name", "CalcField"}, user)
	assert.Len(t, result, 1)
	// With TiDB Views, formula evaluation is done at DB level; Go hydration returns nil
	assert.Nil(t, result[0]["CalcField"])
}

func TestHydrateVirtualFields_FormulaError(t *testing.T) {
	formulaExpr := "INVALID_FUNC()" // Invalid formula that should fail
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "BadFormula", Type: constants.FieldTypeFormula, Formula: &formulaExpr},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin}
	ctx := context.Background()

	rows := []models.SObject{
		{"Id": "001"},
	}

	result := qs.hydrateVirtualFields(ctx, rows, schema, []string{"Id", "BadFormula"}, user)
	assert.Len(t, result, 1)
	// On formula error, the field should be set to nil
	assert.Nil(t, result[0]["BadFormula"])
}

func TestHydrateVirtualFields_WithLookupField(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		},
	}

	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema, accountSchema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin}
	ctx := context.Background()

	rows := []models.SObject{
		{"Id": "opp-001", "AccountId": "acc-001"},
	}

	repo.On("GetRecordsByIDs", mock.Anything, mock.MatchedBy(func(s *models.ObjectMetadata) bool {
		return s.APIName == "Account"
	}), []string{"acc-001"}, mock.Anything).Return([]models.SObject{
		{"Id": "acc-001", "Name": "Acme Corp"},
	}, nil)

	result := qs.hydrateVirtualFields(ctx, rows, schema, []string{"Id", "AccountId"}, user)
	assert.Len(t, result, 1)
	assert.Equal(t, "Acme Corp", result[0]["AccountId_Name"])
}

func TestHydrateVirtualFields_NilUser(t *testing.T) {
	formulaExpr := "1 + 2"
	returnType := constants.FieldTypeDouble
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "CalcField", Type: constants.FieldTypeFormula, Formula: &formulaExpr, ReturnType: &returnType},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	rows := []models.SObject{
		{"Id": "001"},
	}

	result := qs.hydrateVirtualFields(context.Background(), rows, schema, []string{"Id", "CalcField"}, nil)
	assert.Len(t, result, 1)
	// With TiDB Views, formula evaluation is done at DB level; Go hydration returns nil
	assert.Nil(t, result[0]["CalcField"])
}

// === hydrateLookupNames Tests ===

func TestHydrateLookupNames_EmptyRows(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	lookupFields := []models.FieldMetadata{schema.Fields[0]}
	result := qs.hydrateLookupNames(context.Background(), []models.SObject{}, lookupFields, nil)
	assert.Empty(t, result)
}

func TestHydrateLookupNames_EmptyLookupFields(t *testing.T) {
	repo := new(unitMockQueryRepo)
	qs := newTestQueryService(nil, repo)

	rows := []models.SObject{{"Id": "001"}}
	result := qs.hydrateLookupNames(context.Background(), rows, []models.FieldMetadata{}, nil)
	assert.Len(t, result, 1)
}

func TestHydrateLookupNames_LookupError(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		},
	}
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema, accountSchema}, repo)

	rows := []models.SObject{
		{"Id": "opp-001", "AccountId": "acc-001"},
	}

	repo.On("GetRecordsByIDs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("lookup error"))

	lookupFields := []models.FieldMetadata{schema.Fields[0]}
	result := qs.hydrateLookupNames(context.Background(), rows, lookupFields, nil)
	assert.Len(t, result, 1)
	// Name should NOT be populated on error
	_, hasName := result[0]["AccountId_Name"]
	assert.False(t, hasName)
}

func TestHydrateLookupNames_NilLookupValue(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		},
	}
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema, accountSchema}, repo)

	rows := []models.SObject{
		{"Id": "opp-001", "AccountId": nil}, // nil lookup value
	}

	lookupFields := []models.FieldMetadata{schema.Fields[0]}
	result := qs.hydrateLookupNames(context.Background(), rows, lookupFields, nil)
	assert.Len(t, result, 1)
}

func TestHydrateLookupNames_RefSchemaNotFound(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"UnknownObj"}},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	rows := []models.SObject{
		{"Id": "opp-001", "AccountId": "acc-001"},
	}

	lookupFields := []models.FieldMetadata{schema.Fields[0]}
	result := qs.hydrateLookupNames(context.Background(), rows, lookupFields, nil)
	assert.Len(t, result, 1)
	_, hasName := result[0]["AccountId_Name"]
	assert.False(t, hasName)
}

// === QueryService.GlobalSearch Tests (via external test) ===
// Note: GlobalSearch at 0% requires testing with searchable schemas.

// === Query with polymorphic fields ===

func TestQueryService_Query_PolymorphicField(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Task",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "WhoId", Type: constants.FieldTypeReference, IsPolymorphic: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	result, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Task",
		Limit:         10,
	}, user)

	require.NoError(t, err)
	assert.NotNil(t, result)
}

// === Query Repo error propagation ===

func TestQueryService_Query_FindError(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("db error"))

	_, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Account",
		Limit:         10,
	}, user)

	assert.Error(t, err)
}

// === RunAnalytics GroupBy non-SObject result passthrough ===

func TestQueryService_RunAnalytics_GroupByNonSObjectResult(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "StageName", Type: constants.FieldTypeString},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	groupBy := "StageName"
	// Return a non-[]SObject result for a group_by query (edge case)
	repo.On("RunAnalytics", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("unexpected", nil)

	result, err := qs.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationGroupBy,
		GroupBy:       &groupBy,
	}, user)

	require.NoError(t, err)
	assert.Equal(t, "unexpected", result) // Passthrough for non-SObject
}

// === Calculate Tests ===

func TestQueryService_Calculate_ObjectNotFound(t *testing.T) {
	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	_, err := qs.Calculate(ctx, "NonExistent", models.SObject{"Name": "Test"}, user)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
}

func TestQueryService_Calculate_SimpleFormula(t *testing.T) {
	formulaExpr := "1 + 2"
	returnType := constants.FieldTypeDouble
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
			{APIName: "CalcField", Type: constants.FieldTypeFormula, Formula: &formulaExpr, ReturnType: &returnType},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true, Name: "Admin User"}
	ctx := context.Background()

	record := models.SObject{"Id": "001", "Name": "Test"}
	result, err := qs.Calculate(ctx, "Account", record, user)
	require.NoError(t, err)
	// With TiDB Views, formula evaluation is done at DB level; Go-level Calculate returns nil for formula fields
	assert.Nil(t, result["CalcField"])
}

func TestQueryService_Calculate_FormulaUsingRecordField(t *testing.T) {
	formulaExpr := "Amount * 0.1"
	returnType := constants.FieldTypeDouble
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
			{APIName: "Amount", Type: constants.FieldTypeCurrency},
			{APIName: "Commission", Type: constants.FieldTypeFormula, Formula: &formulaExpr, ReturnType: &returnType},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	record := models.SObject{"Id": "001", "Name": "Big Deal", "Amount": float64(10000)}
	result, err := qs.Calculate(ctx, "Opportunity", record, user)
	require.NoError(t, err)
	// With TiDB Views, formula evaluation is done at DB level; Go-level Calculate returns nil for formula fields
	assert.Nil(t, result["Commission"])
}

func TestQueryService_Calculate_NoFormulaFields(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	record := models.SObject{"Id": "001", "Name": "Test"}
	result, err := qs.Calculate(ctx, "Account", record, user)
	require.NoError(t, err)
	assert.Equal(t, "Test", result["Name"])
}

func TestQueryService_Calculate_FormulaFieldWithNilFormula(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "CalcField", Type: constants.FieldTypeFormula}, // nil formula
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	record := models.SObject{"Id": "001"}
	result, err := qs.Calculate(ctx, "Account", record, user)
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestQueryService_Calculate_InvalidFormula(t *testing.T) {
	formulaExpr := "INVALID_FUNCTION()"
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "BadCalc", Type: constants.FieldTypeFormula, Formula: &formulaExpr},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	record := models.SObject{"Id": "001"}
	result, err := qs.Calculate(ctx, "Account", record, user)
	require.NoError(t, err)
	assert.Nil(t, result["BadCalc"])
}

// === GlobalSearch Tests ===

func TestQueryService_GlobalSearch_NoSearchableSchemas(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:    "Account",
		Searchable: false,
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	results, err := qs.GlobalSearch(ctx, "test", user)
	require.NoError(t, err)
	assert.Empty(t, results) // No searchable schemas
}

func TestQueryService_GlobalSearch_WithSearchableSchema(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:     "Account",
		PluralLabel: "Accounts",
		Icon:        "account-icon",
		Searchable:  true,
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	// SearchSingleObject is called internally, but it calls Search which calls repo.Search
	// For global search with no searchFields, it returns empty immediately
	// So we expect empty results since searchFields is nil for global search

	results, err := qs.GlobalSearch(ctx, "Acme", user)
	require.NoError(t, err)
	// Global search calls SearchSingleObject with nil searchFields -> returns empty
	assert.Empty(t, results)
}

func TestQueryService_GlobalSearch_PermissionDenied(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:     "Secret",
		PluralLabel: "Secrets",
		Searchable:  true,
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-1", ProfileID: "standard"}
	ctx := context.Background()

	// Object not readable
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, user, "Secret").Return(
		&models.ObjectPermissions{PermissionsRead: false}, nil)

	results, err := qs.GlobalSearch(ctx, "test", user)
	require.NoError(t, err)
	assert.Empty(t, results) // Silently skipped due to permission denial
}

// === ExecuteRawSQL error path ===

func TestQueryService_ExecuteRawSQL_ValidationError(t *testing.T) {
	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	// Invalid SQL should fail validation
	_, err := qs.ExecuteRawSQL(ctx, "DROP TABLE users", nil, user)
	assert.Error(t, err)
}

func TestQueryService_ExecuteRawSQL_RepoError(t *testing.T) {
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
		},
	}

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{accountSchema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("ExecuteRawSQL", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("db error"))

	_, err := qs.ExecuteRawSQL(ctx, "SELECT Id, Name FROM Account", nil, user)
	assert.Error(t, err)
}
func (m *unitMockMetaRepo) MarkLayoutCustomized(_ context.Context, _ string) {}
func (m *unitMockMetaRepo) MarkFlowDefinitionCustomized(_ context.Context, _ string) error {
	return nil
}

// --- Layout Assignment interface stubs (WP2) ---
func (m *unitMockMetaRepo) FindBestAssignment(_ context.Context, _ string, _ []string, _ string, _ string) (*models.LayoutAssignment, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) CreateAssignment(_ context.Context, _ *models.LayoutAssignment) error {
	return nil
}
func (m *unitMockMetaRepo) DeleteAssignment(_ context.Context, _ string) error { return nil }
func (m *unitMockMetaRepo) GetAssignmentsForObject(_ context.Context, _ string) ([]*models.LayoutAssignment, error) {
	return nil, nil
}
func (m *unitMockMetaRepo) DeleteAssignmentsByLayoutID(_ context.Context, _ string) error { return nil }
func (m *unitMockMetaRepo) WriteLayoutHistory(_ context.Context, _ *models.PageLayoutHistoryEntry) error {
	return nil
}
func (m *unitMockMetaRepo) GetLayoutHistoryMaxVersion(_ context.Context, _ string) (int, error) {
	return 0, nil
}
func (m *unitMockMetaRepo) GetMigrationVersion(_ context.Context, _ string) (int, error) {
	return 0, nil
}
func (m *unitMockMetaRepo) SetMigrationVersion(_ context.Context, _ string, _ int, _ string) error {
	return nil
}

func (m *unitMockMetaRepo) GetLayoutHistoryConfig(_ context.Context, _ string, _ int) (string, error) {
	return "", nil
}

func (m *unitMockMetaRepo) PruneLayoutHistory(_ context.Context, _ string, _ int) error { return nil }

// === RLS filter tests ===

// privateSchemaWithOwner builds an ObjectMetadata with Private OWD and an OwnerId field.
func privateSchemaWithOwner(apiName string) *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:      apiName,
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference},
		},
	}
}

// TestRLS_StandardUser_PrivateObject verifies that query services keep the user
// filter separate and pass owner-based RLS through the dedicated repository arg.
func TestRLS_StandardUser_PrivateObject(t *testing.T) {
	schema := privateSchemaWithOwner("Opportunity")

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	// Grant read permission but NOT ViewAllRecords
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, "Opportunity").
		Return(&models.ObjectPermissions{PermissionsRead: true}, nil)
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: true}, nil).Maybe()

	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-42", ProfileID: "standard-profile"}
	ctx := context.Background()

	repo.On("Find",
		mock.Anything,
		mock.Anything,
		mock.MatchedBy(func(req models.QueryRequest) bool {
			return req.FilterExpr == ""
		}),
		mock.MatchedBy(func(result *rls.Result) bool {
			return result != nil &&
				!result.Skip &&
				result.HasOwnerField &&
				len(result.OwnerUserIDs) == 1 &&
				result.OwnerUserIDs[0] == "user-42"
		}),
		mock.Anything,
	).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	_, err := qs.Query(ctx, api.QueryRequest{ObjectAPIName: "Opportunity", Limit: 10}, user)
	require.NoError(t, err)
	repo.AssertExpectations(t)
}

// TestRLS_AdminUser_NoFilter verifies that a system admin gets Skip=true RLS and
// the original query filter remains untouched.
func TestRLS_AdminUser_NoFilter(t *testing.T) {
	schema := privateSchemaWithOwner("Opportunity")

	repo := new(unitMockQueryRepo)
	qs := newTestQueryService([]*models.ObjectMetadata{schema}, repo)

	user := &models.UserSession{ID: "admin-1", ProfileID: constants.ProfileSystemAdmin, IsSystemAdmin: true}
	ctx := context.Background()

	repo.On("Find",
		mock.Anything,
		mock.Anything,
		mock.MatchedBy(func(req models.QueryRequest) bool {
			return req.FilterExpr == ""
		}),
		mock.MatchedBy(func(result *rls.Result) bool {
			return result != nil && result.Skip
		}),
		mock.Anything,
	).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	_, err := qs.Query(ctx, api.QueryRequest{ObjectAPIName: "Opportunity", Limit: 10}, user)
	require.NoError(t, err)
	repo.AssertExpectations(t)
}

// TestRLS_PublicReadWrite_NoFilter verifies that PublicReadWrite objects pass
// through with Skip=true rather than rewriting the user filter.
func TestRLS_PublicReadWrite_NoFilter(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:      "Contact",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Type: constants.FieldTypeString, IsNameField: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference},
		},
	}

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, "Contact").
		Return(&models.ObjectPermissions{PermissionsRead: true}, nil)
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: true}, nil).Maybe()

	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-99", ProfileID: "standard-profile"}
	ctx := context.Background()

	repo.On("Find",
		mock.Anything,
		mock.Anything,
		mock.MatchedBy(func(req models.QueryRequest) bool {
			return req.FilterExpr == ""
		}),
		mock.MatchedBy(func(result *rls.Result) bool {
			return result != nil && result.Skip
		}),
		mock.Anything,
	).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	_, err := qs.Query(ctx, api.QueryRequest{ObjectAPIName: "Contact", Limit: 10}, user)
	require.NoError(t, err)
	repo.AssertExpectations(t)
}

// TestRLS_ViewAllRecords_NoFilter verifies that ViewAllRecords resolves to
// Skip=true RLS without mutating the request filter.
func TestRLS_ViewAllRecords_NoFilter(t *testing.T) {
	schema := privateSchemaWithOwner("Lead")

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	// Grant read + ViewAllRecords
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, "Lead").
		Return(&models.ObjectPermissions{PermissionsRead: true, PermissionsViewAllRecords: true}, nil)
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: true}, nil).Maybe()

	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-77", ProfileID: "standard-profile"}
	ctx := context.Background()

	repo.On("Find",
		mock.Anything,
		mock.Anything,
		mock.MatchedBy(func(req models.QueryRequest) bool {
			return req.FilterExpr == ""
		}),
		mock.MatchedBy(func(result *rls.Result) bool {
			return result != nil && result.Skip
		}),
		mock.Anything,
	).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	_, err := qs.Query(ctx, api.QueryRequest{ObjectAPIName: "Lead", Limit: 10}, user)
	require.NoError(t, err)
	repo.AssertExpectations(t)
}

// TestRLS_ExistingFilter_Combined verifies that the user-supplied filter stays
// intact and RLS is delivered separately to the repository.
func TestRLS_ExistingFilter_Combined(t *testing.T) {
	schema := privateSchemaWithOwner("Case")

	repo := new(unitMockQueryRepo)
	permRepo := new(unitMockPermRepo)
	permRepo.On("LoadEffectiveObjectPermission", mock.Anything, mock.Anything, "Case").
		Return(&models.ObjectPermissions{PermissionsRead: true}, nil)
	permRepo.On("LoadEffectiveFieldPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
		Return(&models.FieldPermissions{PermissionsRead: true}, nil).Maybe()

	qs := newTestQueryServiceWithPermRepo([]*models.ObjectMetadata{schema}, repo, permRepo)

	user := &models.UserSession{ID: "user-55", ProfileID: "standard-profile"}
	ctx := context.Background()

	repo.On("Find",
		mock.Anything,
		mock.Anything,
		mock.MatchedBy(func(req models.QueryRequest) bool {
			return req.FilterExpr == "Status = 'Open'"
		}),
		mock.MatchedBy(func(result *rls.Result) bool {
			return result != nil &&
				!result.Skip &&
				result.HasOwnerField &&
				len(result.OwnerUserIDs) == 1 &&
				result.OwnerUserIDs[0] == "user-55"
		}),
		mock.Anything,
	).Return(&models.QueryResult{Records: []models.SObject{}}, nil)

	_, err := qs.Query(ctx, api.QueryRequest{
		ObjectAPIName: "Case",
		FilterExpr:    "Status = 'Open'",
		Limit:         10,
	}, user)
	require.NoError(t, err)
	repo.AssertExpectations(t)
}
