package service

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/repository"
	"context"
	"errors"
	"fmt"
	"net/mail"
	"os"
	"sort"
	"strings"
	"time"
)

const (
	HanmacFamilyTenantSlug       = "hanmac-family"
	worksmobileExcludedConfigKey = "worksmobileExcluded"
)

type WorksmobileSyncer interface {
	EnqueueTenantUpsertIfInScope(ctx context.Context, tenant domain.Tenant) error
	EnqueueTenantDeleteIfInScope(ctx context.Context, tenant domain.Tenant) error
	EnqueueUserUpsertIfInScope(ctx context.Context, user domain.User) error
	EnqueueUserDeleteIfInScope(ctx context.Context, user domain.User) error
}

type WorksmobileAdminService interface {
	GetTenantOverview(ctx context.Context, tenantID string) (WorksmobileTenantOverview, error)
	GetComparison(ctx context.Context, tenantID string, includeMatched bool) (WorksmobileComparison, error)
	EnqueueBackfillDryRun(ctx context.Context, tenantID string) (WorksmobileBackfillDryRun, error)
	EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error)
	EnqueueOrgUnitDelete(ctx context.Context, tenantID, worksmobileOrgUnitID string) (*domain.WorksmobileOutbox, error)
	EnqueueUserSync(ctx context.Context, tenantID, userID, credentialBatchID, initialPassword string) (*domain.WorksmobileOutbox, error)
	EnqueueUserPasswordReset(ctx context.Context, tenantID, userID, credentialBatchID string) (*domain.WorksmobileOutbox, error)
	RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error)
	DeletePendingJobs(ctx context.Context, tenantID string) (WorksmobilePendingJobDeleteResult, error)
	ListInitialPasswordCredentials(ctx context.Context, tenantID, credentialBatchID string) ([]WorksmobileInitialPasswordCredential, error)
	ListCredentialBatches(ctx context.Context, tenantID string) ([]WorksmobileCredentialBatch, error)
	DeleteCredentialBatchPasswords(ctx context.Context, tenantID, credentialBatchID string) (WorksmobileCredentialBatch, error)
}

type WorksmobileConfigSummary struct {
	Enabled         bool             `json:"enabled"`
	DomainMappings  map[string]int64 `json:"domainMappings"`
	TokenConfigured bool             `json:"tokenConfigured"`
	AdminTenantID   string           `json:"adminTenantId,omitempty"`
}

type WorksmobileTenantOverview struct {
	Tenant     domain.Tenant              `json:"tenant"`
	Config     WorksmobileConfigSummary   `json:"config"`
	RecentJobs []domain.WorksmobileOutbox `json:"recentJobs"`
}

type WorksmobileBackfillDryRun struct {
	OrgUnitCount int `json:"orgUnitCount"`
	UserCount    int `json:"userCount"`
}

type WorksmobilePendingJobDeleteResult struct {
	DeletedCount int `json:"deletedCount"`
}

type WorksmobileInitialPasswordCredential struct {
	Email              string `json:"email"`
	Name               string `json:"name,omitempty"`
	PrimaryLeafOrgName string `json:"primaryLeafOrgName,omitempty"`
	InitialPassword    string `json:"initialPassword"`
	Status             string `json:"status"`
	LastError          string `json:"lastError,omitempty"`
}

type WorksmobileCredentialBatch struct {
	BatchID         string                              `json:"batchId"`
	Operation       string                              `json:"operation,omitempty"`
	UserCount       int                                 `json:"userCount"`
	PendingCount    int                                 `json:"pendingCount"`
	ProcessingCount int                                 `json:"processingCount"`
	ProcessedCount  int                                 `json:"processedCount"`
	FailedCount     int                                 `json:"failedCount"`
	HasPasswords    bool                                `json:"hasPasswords"`
	DeletedAt       string                              `json:"deletedAt,omitempty"`
	Failures        []WorksmobileCredentialBatchFailure `json:"failures,omitempty"`
	CreatedAt       time.Time                           `json:"createdAt"`
	UpdatedAt       time.Time                           `json:"updatedAt"`
}

type WorksmobileCredentialBatchFailure struct {
	UserID     string `json:"userId,omitempty"`
	Email      string `json:"email,omitempty"`
	Status     string `json:"status"`
	RetryCount int    `json:"retryCount"`
	LastError  string `json:"lastError,omitempty"`
	UpdatedAt  string `json:"updatedAt,omitempty"`
}

type WorksmobileComparison struct {
	Users  []WorksmobileComparisonItem `json:"users"`
	Groups []WorksmobileComparisonItem `json:"groups"`
}

type WorksmobileComparisonItem struct {
	ResourceType                      string `json:"resourceType"`
	BaronID                           string `json:"baronId,omitempty"`
	BaronSlug                         string `json:"baronSlug,omitempty"`
	BaronName                         string `json:"baronName,omitempty"`
	BaronEmail                        string `json:"baronEmail,omitempty"`
	BaronPrimaryOrgID                 string `json:"baronPrimaryOrgId,omitempty"`
	BaronPrimaryOrgSlug               string `json:"baronPrimaryOrgSlug,omitempty"`
	BaronPrimaryOrgName               string `json:"baronPrimaryOrgName,omitempty"`
	BaronParentID                     string `json:"baronParentId,omitempty"`
	BaronParentSlug                   string `json:"baronParentSlug,omitempty"`
	BaronParentName                   string `json:"baronParentName,omitempty"`
	WorksmobileID                     string `json:"worksmobileId,omitempty"`
	ExternalKey                       string `json:"externalKey,omitempty"`
	WorksmobileName                   string `json:"worksmobileName,omitempty"`
	WorksmobileEmail                  string `json:"worksmobileEmail,omitempty"`
	WorksmobileLevelID                string `json:"worksmobileLevelId,omitempty"`
	WorksmobileLevelName              string `json:"worksmobileLevelName,omitempty"`
	WorksmobileTask                   string `json:"worksmobileTask,omitempty"`
	WorksmobileDomainID               int64  `json:"worksmobileDomainId,omitempty"`
	WorksmobileDomainName             string `json:"worksmobileDomainName,omitempty"`
	WorksmobilePrimaryOrgID           string `json:"worksmobilePrimaryOrgId,omitempty"`
	WorksmobilePrimaryOrgName         string `json:"worksmobilePrimaryOrgName,omitempty"`
	WorksmobilePrimaryOrgPositionID   string `json:"worksmobilePrimaryOrgPositionId,omitempty"`
	WorksmobilePrimaryOrgPositionName string `json:"worksmobilePrimaryOrgPositionName,omitempty"`
	WorksmobilePrimaryOrgIsManager    *bool  `json:"worksmobilePrimaryOrgIsManager,omitempty"`
	BaronParentWorksmobileID          string `json:"baronParentWorksmobileId,omitempty"`
	BaronParentWorksmobileName        string `json:"baronParentWorksmobileName,omitempty"`
	BaronParentWorksmobileEmail       string `json:"baronParentWorksmobileEmail,omitempty"`
	WorksmobileParentID               string `json:"worksmobileParentId,omitempty"`
	WorksmobileParentName             string `json:"worksmobileParentName,omitempty"`
	WorksmobileParentEmail            string `json:"worksmobileParentEmail,omitempty"`
	WorksmobileParentExternalKey      string `json:"worksmobileParentExternalKey,omitempty"`
	WorksmobileJobStatus              string `json:"worksmobileJobStatus,omitempty"`
	WorksmobileJobRetryCount          int    `json:"worksmobileJobRetryCount,omitempty"`
	WorksmobileLastError              string `json:"worksmobileLastError,omitempty"`
	WorksmobileLastAttemptAt          string `json:"worksmobileLastAttemptAt,omitempty"`
	Status                            string `json:"status"`
}

type worksmobileSyncService struct {
	tenantService TenantService
	userRepo      repository.UserRepository
	outboxRepo    repository.WorksmobileOutboxRepository
	client        WorksmobileDirectoryClient
}

func NewWorksmobileSyncService(tenantService TenantService, userRepo repository.UserRepository, outboxRepo repository.WorksmobileOutboxRepository, client WorksmobileDirectoryClient) *worksmobileSyncService {
	return &worksmobileSyncService{
		tenantService: tenantService,
		userRepo:      userRepo,
		outboxRepo:    outboxRepo,
		client:        client,
	}
}

func (s *worksmobileSyncService) GetTenantOverview(ctx context.Context, tenantID string) (WorksmobileTenantOverview, error) {
	tenant, err := s.tenantService.GetTenant(ctx, tenantID)
	if err != nil {
		return WorksmobileTenantOverview{}, err
	}
	jobs, _ := s.outboxRepo.ListRecent(ctx, 50)
	jobs = redactWorksmobileOutboxPayloads(jobs)
	return WorksmobileTenantOverview{
		Tenant: *tenant,
		Config: WorksmobileConfigSummary{
			Enabled:         WorksmobileEnabled(tenant.Config),
			DomainMappings:  WorksmobileDomainMappings(tenant.Config),
			TokenConfigured: worksmobileDirectoryAuthConfigured(),
			AdminTenantID:   strings.TrimSpace(os.Getenv("WORKS_ADMIN_TENANT_ID")),
		},
		RecentJobs: jobs,
	}, nil
}

func worksmobileDirectoryAuthConfigured() bool {
	if strings.TrimSpace(os.Getenv("WORKS_ADMIN_ACCESS_TOKEN")) != "" || strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_ACCESS_TOKEN")) != "" {
		return true
	}
	return strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_ID")) != "" &&
		strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SECRET")) != "" &&
		strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SERVICE_ACCOUNT")) != "" &&
		(strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY")) != "" ||
			strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE")) != "")
}

func WorksmobileExcluded(config domain.JSONMap) bool {
	rawValue, ok := config[worksmobileExcludedConfigKey]
	if !ok {
		return false
	}
	switch value := rawValue.(type) {
	case bool:
		return value
	case string:
		return strings.EqualFold(strings.TrimSpace(value), "true")
	default:
		return false
	}
}

func redactWorksmobileOutboxPayloads(jobs []domain.WorksmobileOutbox) []domain.WorksmobileOutbox {
	for i := range jobs {
		jobs[i].Payload = safeWorksmobileOutboxPayload(jobs[i].Payload)
	}
	return jobs
}

func safeWorksmobileOutboxPayload(payload domain.JSONMap) domain.JSONMap {
	if payload == nil {
		return nil
	}
	safe := domain.JSONMap{}
	for _, key := range []string{
		"tenantRootId",
		"loginEmail",
		"displayName",
		"primaryLeafOrgName",
		"credentialBatchId",
		"credentialOperation",
		"credentialBatchCreatedAt",
		"worksmobileId",
		"externalKey",
		"domainId",
		"name",
		"email",
		"matchLocalPart",
		"baronStatus",
	} {
		if value, ok := payload[key]; ok && safeWorksmobilePayloadValue(value) != nil {
			safe[key] = value
		}
	}
	if summary := safeWorksmobileRequestSummary(payload["request"]); len(summary) > 0 {
		safe["requestSummary"] = summary
	}
	return safe
}

func safeWorksmobilePayloadValue(value any) any {
	switch v := value.(type) {
	case string:
		if strings.TrimSpace(v) == "" {
			return nil
		}
		return v
	case nil:
		return nil
	default:
		return value
	}
}

func safeWorksmobileRequestSummary(request any) domain.JSONMap {
	switch v := request.(type) {
	case WorksmobileUserPayload:
		summary := domain.JSONMap{}
		safeSetWorksmobileSummary(summary, "email", v.Email)
		safeSetWorksmobileSummary(summary, "displayName", v.UserName.LastName)
		safeSetWorksmobileSummary(summary, "userExternalKey", v.UserExternalKey)
		safeSetWorksmobileSummary(summary, "cellPhone", v.CellPhone)
		safeSetWorksmobileSummary(summary, "employeeNumber", v.EmployeeNumber)
		safeSetWorksmobileSummary(summary, "task", v.Task)
		return summary
	case WorksmobilePasswordResetPayload:
		summary := domain.JSONMap{}
		safeSetWorksmobileSummary(summary, "email", v.Email)
		return summary
	case WorksmobileOrgUnitPayload:
		summary := domain.JSONMap{}
		safeSetWorksmobileSummary(summary, "email", v.Email)
		safeSetWorksmobileSummary(summary, "orgUnitName", v.OrgUnitName)
		safeSetWorksmobileSummary(summary, "orgUnitExternalKey", v.OrgUnitExternalKey)
		safeSetWorksmobileSummary(summary, "parentOrgUnitId", v.ParentOrgUnitID)
		if v.DomainID > 0 {
			summary["domainId"] = v.DomainID
		}
		return summary
	case map[string]any:
		return safeWorksmobileRequestSummaryFromMap(v)
	case domain.JSONMap:
		return safeWorksmobileRequestSummaryFromMap(map[string]any(v))
	default:
		return nil
	}
}

func safeWorksmobileRequestSummaryFromMap(request map[string]any) domain.JSONMap {
	summary := domain.JSONMap{}
	for _, key := range []string{
		"email",
		"userExternalKey",
		"cellPhone",
		"employeeNumber",
		"task",
		"orgUnitName",
		"orgUnitExternalKey",
		"parentOrgUnitId",
		"domainId",
	} {
		if value, ok := request[key]; ok && safeWorksmobilePayloadValue(value) != nil {
			summary[key] = value
		}
	}
	if userName, ok := request["userName"].(map[string]any); ok {
		safeSetWorksmobileSummary(summary, "displayName", stringValue(userName["lastName"]))
	}
	return summary
}

func safeSetWorksmobileSummary(summary domain.JSONMap, key string, value string) {
	if value = strings.TrimSpace(value); value != "" {
		summary[key] = value
	}
}

func (s *worksmobileSyncService) GetComparison(ctx context.Context, tenantID string, includeMatched bool) (WorksmobileComparison, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return WorksmobileComparison{}, err
	}
	if s.client == nil {
		return WorksmobileComparison{}, errors.New("worksmobile client is not configured")
	}

	tenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return WorksmobileComparison{}, err
	}
	tenantByID := worksmobileTenantByID(tenants)
	tenantByID[root.ID] = *root
	tenantIDs := make([]string, 0, len(tenants))
	for _, tenant := range tenants {
		if isWorksmobileUserScopeTenant(tenant) {
			tenantIDs = append(tenantIDs, tenant.ID)
		}
	}
	users, err := s.userRepo.FindByTenantIDs(ctx, tenantIDs)
	if err != nil {
		return WorksmobileComparison{}, err
	}
	remoteUsers, err := s.client.ListUsers(ctx)
	if err != nil {
		return WorksmobileComparison{}, err
	}
	remoteGroups, err := s.client.ListGroups(ctx)
	if err != nil {
		return WorksmobileComparison{}, err
	}

	recentJobs, _ := s.outboxRepo.ListRecent(ctx, 1000)

	return WorksmobileComparison{
		Users:  compareWorksmobileUsers(users, remoteUsers, includeMatched, tenantByID, worksmobileUserJobSummaries(recentJobs)),
		Groups: compareWorksmobileGroups(append([]domain.Tenant{*root}, tenants...), remoteGroups, includeMatched),
	}, nil
}

func (s *worksmobileSyncService) EnqueueBackfillDryRun(ctx context.Context, tenantID string) (WorksmobileBackfillDryRun, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return WorksmobileBackfillDryRun{}, err
	}
	tenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return WorksmobileBackfillDryRun{}, err
	}
	orgUnitTenantIDs := make([]string, 0, len(tenants))
	userTenantIDs := make([]string, 0, len(tenants))
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, tenants...))
	for _, tenant := range tenants {
		if isWorksmobileOrgUnitTenant(tenant, tenantByID) {
			orgUnitTenantIDs = append(orgUnitTenantIDs, tenant.ID)
		}
		if isWorksmobileUserScopeTenant(tenant) {
			userTenantIDs = append(userTenantIDs, tenant.ID)
		}
	}
	users, err := s.userRepo.FindByTenantIDs(ctx, userTenantIDs)
	if err != nil {
		return WorksmobileBackfillDryRun{}, err
	}
	users = worksmobileSyncScopeUsers(users)
	_ = s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceOrgUnit,
		ResourceID:   root.ID,
		Action:       domain.WorksmobileActionDryRun,
		DedupeKey:    "backfill:dry-run:" + root.ID,
		Payload: domain.JSONMap{
			"tenantIds": orgUnitTenantIDs,
			"userCount": len(users),
		},
	})
	return WorksmobileBackfillDryRun{OrgUnitCount: len(orgUnitTenantIDs), UserCount: len(users)}, nil
}

func (s *worksmobileSyncService) EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	tenant, err := s.tenantService.GetTenant(ctx, orgUnitID)
	if err != nil {
		return nil, err
	}
	tenantRoot, ok, err := s.rootForTenant(ctx, *tenant)
	if err != nil {
		return nil, err
	}
	if !ok || tenantRoot.ID != root.ID {
		return nil, errors.New("target orgunit is outside hanmac-family subtree")
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return nil, err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[tenant.ID]; !ok {
		return nil, errors.New("target tenant is excluded from Worksmobile sync")
	}
	if !isWorksmobileOrgUnitTenant(*tenant, tenantByID) {
		return nil, errors.New("target tenant is not a worksmobile orgunit tenant")
	}
	return s.enqueueOrgUnitUpsert(ctx, root, *tenant, scopeTenants)
}

func (s *worksmobileSyncService) enqueueOrgUnitUpsert(ctx context.Context, root *domain.Tenant, tenant domain.Tenant, scopeTenants []domain.Tenant) (*domain.WorksmobileOutbox, error) {
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant(
		tenant,
		worksmobileDomainClassificationTenant(tenant, tenantByID),
		root.Config,
		0,
	)
	if err != nil {
		return nil, err
	}
	payload = normalizeWorksmobileOrgUnitParent(payload, tenant, tenantByID, root.ID)
	item := &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceOrgUnit,
		ResourceID:   tenant.ID,
		Action:       domain.WorksmobileActionUpsert,
		DedupeKey:    "orgunit:upsert:" + tenant.ID,
		Payload: domain.JSONMap{
			"request":        payload,
			"matchLocalPart": tenant.Slug,
		},
	}
	if err := s.outboxRepo.Create(ctx, item); err != nil {
		return nil, err
	}
	return item, nil
}

func (s *worksmobileSyncService) EnqueueOrgUnitDelete(ctx context.Context, tenantID, worksmobileOrgUnitID string) (*domain.WorksmobileOutbox, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	if s.client == nil {
		return nil, errors.New("worksmobile client is not configured")
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return nil, err
	}
	worksmobileOrgUnitID = strings.TrimSpace(worksmobileOrgUnitID)
	if worksmobileOrgUnitID == "" {
		return nil, errors.New("worksmobile orgunit id is required")
	}
	groups, err := s.client.ListGroups(ctx)
	if err != nil {
		return nil, err
	}
	var target *WorksmobileRemoteGroup
	for i := range groups {
		if strings.TrimSpace(groups[i].ID) == worksmobileOrgUnitID {
			target = &groups[i]
			break
		}
	}
	if target == nil {
		return nil, errors.New("worksmobile orgunit not found")
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if tenant, ok := findWorksmobileOrgUnitTenantByRemoteLocalPart(*target, scopeTenants, tenantByID); ok {
		return s.enqueueOrgUnitUpsert(ctx, root, tenant, scopeTenants)
	}
	if isProtectedWorksmobileRemoteOrgUnit(*root, scopeTenants, *target) {
		return nil, errors.New("protected worksmobile domain root orgunit cannot be deleted")
	}
	item := &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceOrgUnit,
		ResourceID:   worksmobileOrgUnitID,
		Action:       domain.WorksmobileActionDelete,
		DedupeKey:    "orgunit:delete:works:" + worksmobileOrgUnitID,
		Payload: domain.JSONMap{
			"worksmobileId": worksmobileOrgUnitID,
			"externalKey":   target.ExternalID,
			"domainId":      target.DomainID,
			"name":          target.DisplayName,
			"email":         target.Email,
		},
	}
	if err := s.outboxRepo.Create(ctx, item); err != nil {
		return nil, err
	}
	return item, nil
}

func (s *worksmobileSyncService) EnqueueUserSync(ctx context.Context, tenantID, userID, credentialBatchID, initialPassword string) (*domain.WorksmobileOutbox, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	user, err := s.userRepo.FindByID(ctx, userID)
	if err != nil {
		return nil, err
	}
	if user.TenantID == nil {
		return nil, errors.New("target user has no tenant")
	}
	tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID)
	if err != nil {
		return nil, err
	}
	tenantRoot, ok, err := s.rootForTenant(ctx, *tenant)
	if err != nil {
		return nil, err
	}
	if !ok || tenantRoot.ID != root.ID {
		return nil, errors.New("target user is outside hanmac-family subtree")
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return nil, err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[tenant.ID]; !ok {
		return nil, errors.New("target user tenant is excluded from Worksmobile sync")
	}
	if domain.IsWorksDeprovisionUserStatus(user.Status) {
		return s.enqueueUserDelete(ctx, *user, "user:delete:"+user.ID, root.ID)
	}
	if !domain.IsWorksProvisionedUserStatus(user.Status) {
		return nil, errors.New("target user status is excluded from Worksmobile sync")
	}
	payload, err := BuildWorksmobileUserPayloadForDomainTenants(
		*user,
		*tenant,
		tenantByID,
		root.Config,
	)
	if err != nil {
		return nil, err
	}
	initialPassword = strings.TrimSpace(initialPassword)
	if initialPassword != "" {
		payload.PasswordConfig = WorksmobilePasswordConfig{
			PasswordCreationType: "ADMIN",
			Password:             initialPassword,
		}
	}
	if err := s.validateUserAliasLocalParts(ctx, root, *user, payload); err != nil {
		return nil, err
	}
	action := WorksmobileUserStatusAction(user.Status)
	item := &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceUser,
		ResourceID:   user.ID,
		Action:       action,
		DedupeKey:    "user:" + strings.ToLower(action) + ":" + user.ID,
		Payload:      worksmobileUserOutboxPayload(root.ID, payload, user.Status),
	}
	item.Payload["displayName"] = strings.TrimSpace(user.Name)
	item.Payload["primaryLeafOrgName"] = worksmobileUserPrimaryOrgName(*user, tenantByID)
	if batchID := strings.TrimSpace(credentialBatchID); batchID != "" {
		item.Payload["credentialBatchId"] = batchID
		item.Payload["credentialOperation"] = "worksmobile_user_sync"
		item.Payload["credentialBatchCreatedAt"] = time.Now().UTC().Format(time.RFC3339Nano)
	}
	if err := s.outboxRepo.Create(ctx, item); err != nil {
		return nil, err
	}
	return item, nil
}

func (s *worksmobileSyncService) EnqueueUserPasswordReset(ctx context.Context, tenantID, userID, credentialBatchID string) (*domain.WorksmobileOutbox, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	user, err := s.userRepo.FindByID(ctx, userID)
	if err != nil {
		return nil, err
	}
	if user.TenantID == nil {
		return nil, errors.New("target user has no tenant")
	}
	tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID)
	if err != nil {
		return nil, err
	}
	tenantRoot, ok, err := s.rootForTenant(ctx, *tenant)
	if err != nil {
		return nil, err
	}
	if !ok || tenantRoot.ID != root.ID {
		return nil, errors.New("target user is outside hanmac-family subtree")
	}
	if !domain.IsWorksProvisionedUserStatus(user.Status) {
		return nil, errors.New("target user status is excluded from Worksmobile password reset")
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return nil, err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[tenant.ID]; !ok {
		return nil, errors.New("target user tenant is excluded from Worksmobile sync")
	}
	payload, err := BuildWorksmobileUserPayloadForDomainTenants(*user, *tenant, tenantByID, root.Config)
	if err != nil {
		return nil, err
	}
	password := GenerateWorksmobileInitialPassword()
	request := WorksmobilePasswordResetPayload{
		Email: strings.TrimSpace(payload.Email),
		PasswordConfig: WorksmobilePasswordConfig{
			PasswordCreationType: "ADMIN",
			Password:             password,
		},
	}
	batchID := strings.TrimSpace(credentialBatchID)
	batchCreatedAt := time.Now().UTC().Format(time.RFC3339Nano)
	dedupeSuffix := batchID
	if dedupeSuffix == "" {
		dedupeSuffix = batchCreatedAt
	}
	item := &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceUser,
		ResourceID:   user.ID,
		Action:       domain.WorksmobileActionPasswordReset,
		DedupeKey:    "user:password-reset:" + user.ID + ":" + dedupeSuffix,
		Payload: domain.JSONMap{
			"tenantRootId":             root.ID,
			"loginEmail":               request.Email,
			"userExternalKey":          user.ID,
			"initialPassword":          password,
			"displayName":              strings.TrimSpace(user.Name),
			"primaryLeafOrgName":       worksmobileUserPrimaryOrgName(*user, tenantByID),
			"credentialBatchId":        batchID,
			"credentialOperation":      "worksmobile_password_reset",
			"credentialBatchCreatedAt": batchCreatedAt,
			"request":                  request,
		},
	}
	if err := s.outboxRepo.Create(ctx, item); err != nil {
		return nil, err
	}
	return item, nil
}

func (s *worksmobileSyncService) ListInitialPasswordCredentials(ctx context.Context, tenantID, credentialBatchID string) ([]WorksmobileInitialPasswordCredential, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	credentialBatchID = strings.TrimSpace(credentialBatchID)
	var jobs []domain.WorksmobileOutbox
	if credentialBatchID != "" {
		jobs, err = s.outboxRepo.ListCredentialBatchJobs(ctx, root.ID, credentialBatchID)
	} else {
		jobs, err = s.outboxRepo.ListRecent(ctx, 1000)
	}
	if err != nil {
		return nil, err
	}
	credentials := make([]WorksmobileInitialPasswordCredential, 0)
	seen := map[string]bool{}
	for _, job := range jobs {
		if job.ResourceType != domain.WorksmobileResourceUser {
			continue
		}
		if stringValue(job.Payload["tenantRootId"]) != root.ID {
			continue
		}
		if credentialBatchID != "" && stringValue(job.Payload["credentialBatchId"]) != credentialBatchID {
			continue
		}
		email := stringValue(job.Payload["loginEmail"])
		password := stringValue(job.Payload["initialPassword"])
		if email == "" || password == "" || seen[email] {
			continue
		}
		seen[email] = true
		credentials = append(credentials, WorksmobileInitialPasswordCredential{
			Email:              email,
			Name:               stringValue(job.Payload["displayName"]),
			PrimaryLeafOrgName: stringValue(job.Payload["primaryLeafOrgName"]),
			InitialPassword:    password,
			Status:             job.Status,
			LastError:          job.LastError,
		})
	}
	return credentials, nil
}

func (s *worksmobileSyncService) ListCredentialBatches(ctx context.Context, tenantID string) ([]WorksmobileCredentialBatch, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	jobs, err := s.outboxRepo.ListCredentialBatchJobs(ctx, root.ID, "")
	if err != nil {
		return nil, err
	}
	return aggregateWorksmobileCredentialBatches(jobs), nil
}

func (s *worksmobileSyncService) DeleteCredentialBatchPasswords(ctx context.Context, tenantID, credentialBatchID string) (WorksmobileCredentialBatch, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return WorksmobileCredentialBatch{}, err
	}
	credentialBatchID = strings.TrimSpace(credentialBatchID)
	if credentialBatchID == "" {
		return WorksmobileCredentialBatch{}, errors.New("credential batch id is required")
	}
	jobs, err := s.outboxRepo.ListCredentialBatchJobs(ctx, root.ID, credentialBatchID)
	if err != nil {
		return WorksmobileCredentialBatch{}, err
	}
	if len(jobs) == 0 {
		return WorksmobileCredentialBatch{}, errors.New("credential batch not found")
	}
	deletedAt := time.Now().UTC().Format(time.RFC3339)
	for i := range jobs {
		nextPayload := scrubWorksmobileCredentialPayload(jobs[i].Payload, deletedAt)
		if err := s.outboxRepo.UpdatePayload(ctx, jobs[i].ID, nextPayload); err != nil {
			return WorksmobileCredentialBatch{}, err
		}
		jobs[i].Payload = nextPayload
	}
	batches := aggregateWorksmobileCredentialBatches(jobs)
	if len(batches) == 0 {
		return WorksmobileCredentialBatch{}, errors.New("credential batch not found")
	}
	return batches[0], nil
}

func (s *worksmobileSyncService) RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error) {
	if _, err := s.hanmacRoot(ctx, tenantID); err != nil {
		return nil, err
	}
	if err := s.outboxRepo.MarkRetry(ctx, jobID); err != nil {
		return nil, err
	}
	return s.outboxRepo.FindByID(ctx, jobID)
}

func (s *worksmobileSyncService) DeletePendingJobs(ctx context.Context, tenantID string) (WorksmobilePendingJobDeleteResult, error) {
	root, err := s.hanmacRoot(ctx, tenantID)
	if err != nil {
		return WorksmobilePendingJobDeleteResult{}, err
	}
	deleted, err := s.outboxRepo.DeletePendingByTenantRoot(ctx, root.ID)
	if err != nil {
		return WorksmobilePendingJobDeleteResult{}, err
	}
	return WorksmobilePendingJobDeleteResult{DeletedCount: int(deleted)}, nil
}

func (s *worksmobileSyncService) EnqueueTenantUpsertIfInScope(ctx context.Context, tenant domain.Tenant) error {
	root, ok, err := s.rootForTenant(ctx, tenant)
	if err != nil || !ok {
		return err
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[tenant.ID]; !ok {
		return nil
	}
	if !isWorksmobileOrgUnitTenant(tenant, tenantByID) {
		return nil
	}
	payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant(
		tenant,
		worksmobileDomainClassificationTenant(tenant, tenantByID),
		root.Config,
		0,
	)
	if err != nil {
		return err
	}
	payload = normalizeWorksmobileOrgUnitParent(payload, tenant, tenantByID, root.ID)
	return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceOrgUnit,
		ResourceID:   tenant.ID,
		Action:       domain.WorksmobileActionUpsert,
		DedupeKey:    "orgunit:upsert:" + tenant.ID,
		Payload: domain.JSONMap{
			"request":        payload,
			"matchLocalPart": tenant.Slug,
		},
	})
}

func (s *worksmobileSyncService) EnqueueTenantDeleteIfInScope(ctx context.Context, tenant domain.Tenant) error {
	root, ok, err := s.rootForTenant(ctx, tenant)
	if err != nil || !ok {
		return err
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[tenant.ID]; !ok {
		return nil
	}
	if !isWorksmobileOrgUnitTenant(tenant, tenantByID) {
		return nil
	}
	return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceOrgUnit,
		ResourceID:   tenant.ID,
		Action:       domain.WorksmobileActionDelete,
		DedupeKey:    "orgunit:delete:" + tenant.ID,
		Payload:      domain.JSONMap{"orgUnitExternalKey": tenant.ID},
	})
}

func (s *worksmobileSyncService) EnqueueUserUpsertIfInScope(ctx context.Context, user domain.User) error {
	if user.TenantID == nil || *user.TenantID == "" {
		return nil
	}
	tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID)
	if err != nil {
		return err
	}
	root, ok, err := s.rootForTenant(ctx, *tenant)
	if err != nil || !ok {
		return err
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[*user.TenantID]; !ok {
		return nil
	}
	if domain.IsWorksDeprovisionUserStatus(user.Status) {
		_, err := s.enqueueUserDelete(ctx, user, "user:delete:"+user.ID, root.ID)
		return err
	}
	if !domain.IsWorksProvisionedUserStatus(user.Status) {
		return nil
	}
	payload, err := BuildWorksmobileUserPayloadForDomainTenants(
		user,
		*tenant,
		tenantByID,
		root.Config,
	)
	if err != nil {
		return err
	}
	if err := s.validateUserAliasLocalParts(ctx, root, user, payload); err != nil {
		return err
	}
	action := WorksmobileUserStatusAction(user.Status)
	return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceUser,
		ResourceID:   user.ID,
		Action:       action,
		DedupeKey:    "user:" + strings.ToLower(action) + ":" + user.ID,
		Payload:      worksmobileUserOutboxPayload(root.ID, payload, user.Status),
	})
}

func (s *worksmobileSyncService) EnqueueUserDeleteIfInScope(ctx context.Context, user domain.User) error {
	if user.TenantID == nil || *user.TenantID == "" {
		return nil
	}
	tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID)
	if err != nil {
		return err
	}
	root, ok, err := s.rootForTenant(ctx, *tenant)
	if err != nil || !ok {
		return err
	}
	scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return err
	}
	tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
	if _, ok := tenantByID[*user.TenantID]; !ok {
		return nil
	}
	_, err = s.enqueueUserDelete(ctx, user, "user:delete:"+user.ID, "")
	return err
}

func (s *worksmobileSyncService) enqueueUserDelete(ctx context.Context, user domain.User, dedupeKey string, rootID string) (*domain.WorksmobileOutbox, error) {
	payload := domain.JSONMap{
		"userExternalKey": user.ID,
		"loginEmail":      user.Email,
	}
	if rootID != "" {
		payload["tenantRootId"] = rootID
	}
	if status := domain.NormalizeUserStatus(user.Status); status != "" {
		payload["baronStatus"] = status
	}
	item := &domain.WorksmobileOutbox{
		ResourceType: domain.WorksmobileResourceUser,
		ResourceID:   user.ID,
		Action:       domain.WorksmobileActionDelete,
		DedupeKey:    dedupeKey,
		Payload:      payload,
	}
	if err := s.outboxRepo.Create(ctx, item); err != nil {
		return nil, err
	}
	return item, nil
}

func (s *worksmobileSyncService) hanmacRoot(ctx context.Context, tenantID string) (*domain.Tenant, error) {
	tenant, err := s.tenantService.GetTenant(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	if tenant.Slug != HanmacFamilyTenantSlug || tenant.ParentID != nil {
		return nil, errors.New("worksmobile is only available for hanmac-family root tenant")
	}
	return tenant, nil
}

func (s *worksmobileSyncService) hanmacSubtree(ctx context.Context, rootID string) ([]domain.Tenant, error) {
	all, _, err := s.tenantService.ListTenants(ctx, 10000, 0, "", "")
	if err != nil {
		return nil, err
	}
	byParent := map[string][]domain.Tenant{}
	for _, tenant := range all {
		if tenant.ParentID != nil {
			byParent[*tenant.ParentID] = append(byParent[*tenant.ParentID], tenant)
		}
	}
	result := []domain.Tenant{}
	var visit func(id string)
	visit = func(id string) {
		for _, child := range byParent[id] {
			if WorksmobileExcluded(child.Config) {
				continue
			}
			result = append(result, child)
			visit(child.ID)
		}
	}
	visit(rootID)
	sort.Slice(result, func(i, j int) bool {
		return result[i].Name < result[j].Name
	})
	return result, nil
}

func (s *worksmobileSyncService) rootForTenant(ctx context.Context, tenant domain.Tenant) (*domain.Tenant, bool, error) {
	current := tenant
	for current.ParentID != nil && *current.ParentID != "" {
		parent, err := s.tenantService.GetTenant(ctx, *current.ParentID)
		if err != nil {
			return nil, false, err
		}
		current = *parent
	}
	return &current, current.Slug == HanmacFamilyTenantSlug, nil
}

func (s *worksmobileSyncService) validateUserAliasLocalParts(ctx context.Context, root *domain.Tenant, user domain.User, payload WorksmobileUserPayload) error {
	if len(payload.AliasEmails) == 0 {
		return nil
	}
	tenants, err := s.hanmacSubtree(ctx, root.ID)
	if err != nil {
		return err
	}
	tenantByID := make(map[string]domain.Tenant, len(tenants)+1)
	tenantByID[root.ID] = *root
	tenantIDs := make([]string, 0, len(tenants)+1)
	tenantIDs = append(tenantIDs, root.ID)
	for _, tenant := range tenants {
		tenantByID[tenant.ID] = tenant
		if isWorksmobileUserScopeTenant(tenant) {
			tenantIDs = append(tenantIDs, tenant.ID)
		}
	}
	users, err := s.userRepo.FindByTenantIDs(ctx, tenantIDs)
	if err != nil {
		return err
	}
	existing := map[string]string{}
	for _, existingUser := range users {
		if existingUser.ID == user.ID {
			continue
		}
		addWorksmobileEmail(existing, existingUser.Email, existingUser.ID)
		if existingUser.TenantID == nil {
			continue
		}
		tenant, ok := tenantByID[*existingUser.TenantID]
		if !ok {
			continue
		}
		for _, alias := range BuildWorksmobileAliasEmails(existingUser, tenant) {
			addWorksmobileEmail(existing, alias, existingUser.ID)
		}
	}
	return ValidateWorksmobileAliasEmails(payload.Email, payload.AliasEmails, existing)
}

func addWorksmobileEmail(target map[string]string, email string, owner string) {
	normalized := strings.ToLower(strings.TrimSpace(email))
	if _, err := mail.ParseAddress(normalized); err == nil && normalized != "" {
		target[normalized] = owner
	}
}

func findWorksmobileOrgUnitTenantByRemoteLocalPart(remote WorksmobileRemoteGroup, localTenants []domain.Tenant, tenantByID map[string]domain.Tenant) (domain.Tenant, bool) {
	candidates := worksmobileRemoteGroupLocalPartCandidates(remote)
	for _, tenant := range localTenants {
		if !isWorksmobileOrgUnitTenant(tenant, tenantByID) {
			continue
		}
		if candidates[normalizeWorksmobileSlugLocalPart(tenant.Slug)] {
			return tenant, true
		}
	}
	return domain.Tenant{}, false
}

func isProtectedWorksmobileRemoteOrgUnit(root domain.Tenant, localTenants []domain.Tenant, remote WorksmobileRemoteGroup) bool {
	if strings.TrimSpace(remote.ParentID) == "" {
		return true
	}
	candidates := worksmobileRemoteGroupLocalPartCandidates(remote)
	if len(candidates) == 0 {
		return false
	}
	for _, tenant := range localTenants {
		if tenant.ParentID == nil || *tenant.ParentID != root.ID || tenant.Type != domain.TenantTypeCompany {
			continue
		}
		if candidates[normalizeWorksmobileSlugLocalPart(tenant.Slug)] {
			return true
		}
	}
	return false
}

func worksmobileRemoteGroupLocalPartCandidates(remote WorksmobileRemoteGroup) map[string]bool {
	result := map[string]bool{}
	if localPart := normalizeWorksmobileSlugLocalPart(remote.MailLocalPart); localPart != "" {
		result[localPart] = true
	}
	if localPart, err := domain.ExtractNormalizedEmailLocalPart(remote.Email); err == nil && localPart != "" {
		result[localPart] = true
	}
	return result
}

func normalizeWorksmobileSlugLocalPart(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func isWorksmobileOrgUnitTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) bool {
	if isWorksmobileDomainRootTenant(tenant) {
		return false
	}
	if tenant.Type == domain.TenantTypeOrganization {
		return true
	}
	if tenant.Type == domain.TenantTypeUserGroup {
		return true
	}
	if tenant.Type == domain.TenantTypeCompany {
		return isWorksmobileBarongroupChildCompany(tenant, tenantByID)
	}
	return false
}

func isWorksmobileUserScopeTenant(tenant domain.Tenant) bool {
	return tenant.Type == domain.TenantTypeCompany || tenant.Type == domain.TenantTypeOrganization || tenant.Type == domain.TenantTypeUserGroup
}

func worksmobileDomainClassificationTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) domain.Tenant {
	current := tenant
	for {
		if isWorksmobileDomainRootTenant(current) {
			return current
		}
		parentID := worksmobileTenantParentID(current)
		if parentID == "" {
			return tenant
		}
		parent, ok := tenantByID[parentID]
		if !ok {
			return tenant
		}
		current = parent
	}
}

func isWorksmobileDomainRootTenant(tenant domain.Tenant) bool {
	slug := strings.ToLower(strings.TrimSpace(tenant.Slug))
	switch slug {
	case "saman", "hanmac", "gpdtdc", "halla", "hanlla", "baron-group":
		return true
	}
	if tenantHasDomain(tenant, "samaneng.com") ||
		tenantHasDomain(tenant, "hanmaceng.co.kr") ||
		tenantHasDomain(tenant, "baroncs.co.kr") ||
		tenantHasDomain(tenant, "hallasanup.com") ||
		tenantHasDomain(tenant, "brsw.kr") {
		return true
	}
	name := strings.TrimSpace(tenant.Name)
	return name == "삼안" ||
		name == "한맥기술" ||
		name == "총괄기획&기술개발센터" ||
		name == "한라산업개발" ||
		name == "바론그룹"
}

func isWorksmobileBarongroupChildCompany(tenant domain.Tenant, tenantByID map[string]domain.Tenant) bool {
	if tenant.Type != domain.TenantTypeCompany || tenant.Slug == "baron-group" {
		return false
	}
	parentID := worksmobileTenantParentID(tenant)
	for parentID != "" {
		parent, ok := tenantByID[parentID]
		if !ok {
			return false
		}
		if parent.Slug == "baron-group" {
			return true
		}
		parentID = worksmobileTenantParentID(parent)
	}
	return false
}

func normalizeWorksmobileOrgUnitParent(payload WorksmobileOrgUnitPayload, tenant domain.Tenant, tenantByID map[string]domain.Tenant, rootID string) WorksmobileOrgUnitPayload {
	if tenant.ParentID != nil && *tenant.ParentID == rootID {
		payload.ParentOrgUnitID = ""
	}
	if tenant.ParentID != nil {
		if parent, ok := tenantByID[*tenant.ParentID]; ok {
			if parent.Slug == "baron-group" || !isWorksmobileOrgUnitTenant(parent, tenantByID) {
				payload.ParentOrgUnitID = ""
			}
		}
	}
	return payload
}

func worksmobileUserOutboxPayload(rootID string, payload WorksmobileUserPayload, statuses ...string) domain.JSONMap {
	outboxPayload := domain.JSONMap{
		"request":      payload,
		"tenantRootId": rootID,
		"loginEmail":   payload.Email,
	}
	if password := strings.TrimSpace(payload.PasswordConfig.Password); password != "" {
		outboxPayload["initialPassword"] = password
	}
	if len(statuses) > 0 {
		if status := strings.TrimSpace(statuses[0]); status != "" {
			outboxPayload["baronStatus"] = status
		}
	}
	return outboxPayload
}

func aggregateWorksmobileCredentialBatches(jobs []domain.WorksmobileOutbox) []WorksmobileCredentialBatch {
	byBatchID := map[string]*WorksmobileCredentialBatch{}
	for _, job := range jobs {
		batchID := stringValue(job.Payload["credentialBatchId"])
		if batchID == "" {
			continue
		}
		batch, ok := byBatchID[batchID]
		if !ok {
			createdAt := worksmobileCredentialBatchCreatedAt(job)
			batch = &WorksmobileCredentialBatch{
				BatchID:   batchID,
				Operation: stringValue(job.Payload["credentialOperation"]),
				CreatedAt: createdAt,
				UpdatedAt: job.UpdatedAt,
			}
			byBatchID[batchID] = batch
		}
		batch.UserCount++
		if batch.Operation == "" {
			batch.Operation = stringValue(job.Payload["credentialOperation"])
		}
		jobBatchCreatedAt := worksmobileCredentialBatchCreatedAt(job)
		if jobBatchCreatedAt.Before(batch.CreatedAt) || batch.CreatedAt.IsZero() {
			batch.CreatedAt = jobBatchCreatedAt
		}
		if job.UpdatedAt.After(batch.UpdatedAt) {
			batch.UpdatedAt = job.UpdatedAt
		}
		switch job.Status {
		case domain.WorksmobileOutboxStatusPending:
			batch.PendingCount++
		case domain.WorksmobileOutboxStatusProcessing:
			batch.ProcessingCount++
		case domain.WorksmobileOutboxStatusProcessed:
			batch.ProcessedCount++
		case domain.WorksmobileOutboxStatusFailed:
			batch.FailedCount++
			batch.Failures = append(batch.Failures, WorksmobileCredentialBatchFailure{
				UserID:     job.ResourceID,
				Email:      worksmobileCredentialJobEmail(job),
				Status:     job.Status,
				RetryCount: job.RetryCount,
				LastError:  strings.TrimSpace(job.LastError),
				UpdatedAt:  job.UpdatedAt.Format(time.RFC3339),
			})
		}
		if worksmobilePayloadHasPassword(job.Payload) {
			batch.HasPasswords = true
		}
		if deletedAt := stringValue(job.Payload["credentialDeletedAt"]); deletedAt != "" {
			batch.DeletedAt = deletedAt
		}
	}
	batches := make([]WorksmobileCredentialBatch, 0, len(byBatchID))
	for _, batch := range byBatchID {
		batches = append(batches, *batch)
	}
	sort.Slice(batches, func(i, j int) bool {
		return batches[i].CreatedAt.After(batches[j].CreatedAt)
	})
	return batches
}

func worksmobileCredentialBatchCreatedAt(job domain.WorksmobileOutbox) time.Time {
	if value := stringValue(job.Payload["credentialBatchCreatedAt"]); value != "" {
		if parsed, err := time.Parse(time.RFC3339Nano, value); err == nil {
			return parsed.UTC()
		}
		if parsed, err := time.Parse(time.RFC3339, value); err == nil {
			return parsed.UTC()
		}
	}
	if !job.UpdatedAt.IsZero() && !job.CreatedAt.IsZero() && job.UpdatedAt.After(job.CreatedAt) {
		return job.UpdatedAt.UTC()
	}
	return job.CreatedAt.UTC()
}

func worksmobileCredentialJobEmail(job domain.WorksmobileOutbox) string {
	if email := stringValue(job.Payload["loginEmail"]); email != "" {
		return email
	}
	switch request := job.Payload["request"].(type) {
	case WorksmobileUserPayload:
		return strings.TrimSpace(request.Email)
	case WorksmobilePasswordResetPayload:
		return strings.TrimSpace(request.Email)
	case map[string]any:
		return stringValue(request["email"])
	case domain.JSONMap:
		return stringValue(request["email"])
	default:
		return ""
	}
}

func scrubWorksmobileCredentialPayload(payload domain.JSONMap, deletedAt string) domain.JSONMap {
	nextPayload := make(domain.JSONMap, len(payload)+1)
	for key, value := range payload {
		nextPayload[key] = value
	}
	delete(nextPayload, "initialPassword")
	nextPayload["credentialDeletedAt"] = deletedAt
	nextPayload["request"] = scrubWorksmobileRequestPassword(nextPayload["request"])
	return nextPayload
}

func scrubWorksmobileRequestPassword(request any) any {
	switch v := request.(type) {
	case WorksmobileUserPayload:
		v.PasswordConfig.Password = ""
		return v
	case WorksmobilePasswordResetPayload:
		v.PasswordConfig.Password = ""
		return v
	case map[string]any:
		next := make(map[string]any, len(v))
		for key, value := range v {
			next[key] = value
		}
		next["passwordConfig"] = scrubWorksmobilePasswordConfig(next["passwordConfig"])
		return next
	case domain.JSONMap:
		next := make(domain.JSONMap, len(v))
		for key, value := range v {
			next[key] = value
		}
		next["passwordConfig"] = scrubWorksmobilePasswordConfig(next["passwordConfig"])
		return next
	default:
		return request
	}
}

func scrubWorksmobilePasswordConfig(config any) any {
	switch v := config.(type) {
	case WorksmobilePasswordConfig:
		v.Password = ""
		return v
	case map[string]any:
		next := make(map[string]any, len(v))
		for key, value := range v {
			next[key] = value
		}
		next["password"] = ""
		return next
	case domain.JSONMap:
		next := make(domain.JSONMap, len(v))
		for key, value := range v {
			next[key] = value
		}
		next["password"] = ""
		return next
	default:
		return config
	}
}

func worksmobilePayloadHasPassword(payload domain.JSONMap) bool {
	if stringValue(payload["initialPassword"]) != "" {
		return true
	}
	switch request := payload["request"].(type) {
	case WorksmobileUserPayload:
		return strings.TrimSpace(request.PasswordConfig.Password) != ""
	case WorksmobilePasswordResetPayload:
		return strings.TrimSpace(request.PasswordConfig.Password) != ""
	case map[string]any:
		return worksmobilePasswordConfigHasPassword(request["passwordConfig"])
	case domain.JSONMap:
		return worksmobilePasswordConfigHasPassword(request["passwordConfig"])
	default:
		return false
	}
}

func worksmobilePasswordConfigHasPassword(config any) bool {
	switch v := config.(type) {
	case WorksmobilePasswordConfig:
		return strings.TrimSpace(v.Password) != ""
	case map[string]any:
		return stringValue(v["password"]) != ""
	case domain.JSONMap:
		return stringValue(v["password"]) != ""
	default:
		return false
	}
}

func stringValue(value any) string {
	switch v := value.(type) {
	case string:
		return strings.TrimSpace(v)
	default:
		return ""
	}
}

type worksmobileUserJobSummary struct {
	Status        string
	RetryCount    int
	LastError     string
	LastAttemptAt string
}

func worksmobileUserJobSummaries(jobs []domain.WorksmobileOutbox) map[string]worksmobileUserJobSummary {
	result := map[string]worksmobileUserJobSummary{}
	for _, job := range jobs {
		if job.ResourceType != domain.WorksmobileResourceUser {
			continue
		}
		if job.ResourceID == "" {
			continue
		}
		if _, exists := result[job.ResourceID]; exists {
			continue
		}
		result[job.ResourceID] = worksmobileUserJobSummary{
			Status:        job.Status,
			RetryCount:    job.RetryCount,
			LastError:     strings.TrimSpace(job.LastError),
			LastAttemptAt: job.UpdatedAt.Format(time.RFC3339),
		}
	}
	return result
}

func compareWorksmobileUsers(localUsers []domain.User, remoteUsers []WorksmobileRemoteUser, includeMatched bool, localTenants map[string]domain.Tenant, jobSummaries ...map[string]worksmobileUserJobSummary) []WorksmobileComparisonItem {
	jobSummaryByUserID := map[string]worksmobileUserJobSummary{}
	if len(jobSummaries) > 0 && jobSummaries[0] != nil {
		jobSummaryByUserID = jobSummaries[0]
	}
	remoteByExternalID := map[string]WorksmobileRemoteUser{}
	remoteByEmail := map[string]WorksmobileRemoteUser{}
	for _, remote := range remoteUsers {
		if remote.ExternalID != "" {
			remoteByExternalID[remote.ExternalID] = remote
		}
		if normalizedEmail := strings.ToLower(strings.TrimSpace(remote.Email)); normalizedEmail != "" {
			remoteByEmail[normalizedEmail] = remote
		}
	}
	localByID := map[string]domain.User{}
	matchedRemoteIDs := map[string]bool{}
	excludedLocalIDs := map[string]bool{}
	result := make([]WorksmobileComparisonItem, 0)
	for _, user := range localUsers {
		if user.DeletedAt.Valid || !domain.IsWorksProvisionedUserStatus(user.Status) {
			excludedLocalIDs[user.ID] = true
			if remote, ok := remoteByExternalID[user.ID]; ok {
				matchedRemoteIDs[remote.ID] = true
			} else if remote, ok := remoteByEmail[strings.ToLower(strings.TrimSpace(user.Email))]; ok {
				matchedRemoteIDs[remote.ID] = true
			}
			continue
		}
		localByID[user.ID] = user
		remote, matched := remoteByExternalID[user.ID]
		if !matched {
			remote, matched = remoteByEmail[strings.ToLower(strings.TrimSpace(user.Email))]
		}
		needsUpdate := matched && worksmobileUserNeedsUpdate(user, remote, localTenants)
		if matched && !includeMatched && !needsUpdate {
			matchedRemoteIDs[remote.ID] = true
			continue
		}
		item := WorksmobileComparisonItem{
			ResourceType:        "USER",
			BaronID:             user.ID,
			BaronName:           user.Name,
			BaronEmail:          user.Email,
			BaronPrimaryOrgID:   worksmobileUserPrimaryOrgID(user),
			BaronPrimaryOrgSlug: worksmobileUserPrimaryOrgSlug(user, localTenants),
			BaronPrimaryOrgName: worksmobileUserPrimaryOrgName(user, localTenants),
			Status:              "missing_in_worksmobile",
		}
		if summary, ok := jobSummaryByUserID[user.ID]; ok {
			item.WorksmobileJobStatus = summary.Status
			item.WorksmobileJobRetryCount = summary.RetryCount
			item.WorksmobileLastAttemptAt = summary.LastAttemptAt
			if summary.Status == domain.WorksmobileOutboxStatusFailed {
				item.WorksmobileLastError = summary.LastError
			}
		}
		if matched {
			item.Status = "matched"
			if needsUpdate {
				item.Status = "needs_update"
			}
			item.WorksmobileID = remote.ID
			item.ExternalKey = remote.ExternalID
			item.WorksmobileName = remote.DisplayName
			item.WorksmobileEmail = remote.Email
			item.WorksmobileLevelID = remote.LevelID
			item.WorksmobileLevelName = remote.LevelName
			item.WorksmobileTask = remote.Task
			item.WorksmobileDomainID = remote.DomainID
			item.WorksmobileDomainName = remote.DomainName
			item.WorksmobilePrimaryOrgID = remote.PrimaryOrgUnitID
			item.WorksmobilePrimaryOrgName = remote.PrimaryOrgUnitName
			item.WorksmobilePrimaryOrgPositionID = remote.PrimaryOrgUnitPositionID
			item.WorksmobilePrimaryOrgPositionName = remote.PrimaryOrgUnitPositionName
			item.WorksmobilePrimaryOrgIsManager = remote.PrimaryOrgUnitIsManager
			matchedRemoteIDs[remote.ID] = true
		}
		result = append(result, item)
	}
	for _, remote := range remoteUsers {
		if matchedRemoteIDs[remote.ID] {
			continue
		}
		if excludedLocalIDs[remote.ExternalID] {
			continue
		}
		if remote.ExternalID == "" {
			result = append(result, WorksmobileComparisonItem{
				ResourceType:                      "USER",
				WorksmobileID:                     remote.ID,
				ExternalKey:                       remote.ExternalID,
				WorksmobileName:                   remote.DisplayName,
				WorksmobileEmail:                  remote.Email,
				WorksmobileLevelID:                remote.LevelID,
				WorksmobileLevelName:              remote.LevelName,
				WorksmobileTask:                   remote.Task,
				WorksmobileDomainID:               remote.DomainID,
				WorksmobileDomainName:             remote.DomainName,
				WorksmobilePrimaryOrgID:           remote.PrimaryOrgUnitID,
				WorksmobilePrimaryOrgName:         remote.PrimaryOrgUnitName,
				WorksmobilePrimaryOrgPositionID:   remote.PrimaryOrgUnitPositionID,
				WorksmobilePrimaryOrgPositionName: remote.PrimaryOrgUnitPositionName,
				WorksmobilePrimaryOrgIsManager:    remote.PrimaryOrgUnitIsManager,
				Status:                            "missing_external_key",
			})
			continue
		}
		if _, ok := localByID[remote.ExternalID]; !ok {
			result = append(result, WorksmobileComparisonItem{
				ResourceType:                      "USER",
				WorksmobileID:                     remote.ID,
				ExternalKey:                       remote.ExternalID,
				WorksmobileName:                   remote.DisplayName,
				WorksmobileEmail:                  remote.Email,
				WorksmobileLevelID:                remote.LevelID,
				WorksmobileLevelName:              remote.LevelName,
				WorksmobileTask:                   remote.Task,
				WorksmobileDomainID:               remote.DomainID,
				WorksmobileDomainName:             remote.DomainName,
				WorksmobilePrimaryOrgID:           remote.PrimaryOrgUnitID,
				WorksmobilePrimaryOrgName:         remote.PrimaryOrgUnitName,
				WorksmobilePrimaryOrgPositionID:   remote.PrimaryOrgUnitPositionID,
				WorksmobilePrimaryOrgPositionName: remote.PrimaryOrgUnitPositionName,
				WorksmobilePrimaryOrgIsManager:    remote.PrimaryOrgUnitIsManager,
				Status:                            "missing_in_baron",
			})
		}
	}
	return result
}

func worksmobileUserNeedsUpdate(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant) bool {
	if strings.TrimSpace(remote.ExternalID) != strings.TrimSpace(user.ID) {
		return true
	}
	if strings.TrimSpace(remote.DisplayName) != strings.TrimSpace(user.Name) {
		return true
	}
	if strings.ToLower(strings.TrimSpace(remote.Email)) != strings.ToLower(strings.TrimSpace(user.Email)) {
		return true
	}
	if worksmobileUserPhoneNeedsUpdate(user, remote) {
		return true
	}
	if worksmobileUserEmployeeNumberNeedsUpdate(user, remote) {
		return true
	}
	return false
}

func worksmobileUserPhoneNeedsUpdate(user domain.User, remote WorksmobileRemoteUser) bool {
	localPhone := normalizeWorksmobilePhoneForCompare(user.Phone)
	remotePhone := normalizeWorksmobilePhoneForCompare(remote.CellPhone)
	if localPhone == "" && remotePhone == "" {
		return false
	}
	if localPhone != remotePhone {
		return true
	}
	return localPhone != "" && worksmobilePhoneHasDuplicateKoreanCountryCode(remote.CellPhone)
}

func normalizeWorksmobilePhoneForCompare(value string) string {
	return domain.NormalizePhoneNumber(value)
}

func worksmobilePhoneHasDuplicateKoreanCountryCode(value string) bool {
	digits := strings.Builder{}
	for _, r := range strings.TrimSpace(value) {
		if r >= '0' && r <= '9' {
			digits.WriteRune(r)
		}
	}
	return strings.HasPrefix(digits.String(), "8282")
}

func worksmobileUserEmployeeNumberNeedsUpdate(user domain.User, remote WorksmobileRemoteUser) bool {
	localEmployeeNumber := strings.TrimSpace(metadataEmployeeNumber(user.Metadata))
	remoteEmployeeNumber := strings.TrimSpace(remote.EmployeeNumber)
	if localEmployeeNumber == "" && remoteEmployeeNumber == "" {
		return false
	}
	return localEmployeeNumber != remoteEmployeeNumber
}

func worksmobileUserOrganizationsNeedUpdate(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant) bool {
	if len(remote.Organizations) == 0 || user.TenantID == nil || localTenants == nil {
		return false
	}
	tenantID := strings.TrimSpace(*user.TenantID)
	if tenantID == "" {
		return false
	}
	tenant, ok := localTenants[tenantID]
	if !ok {
		return false
	}
	expected, err := BuildWorksmobileUserPayloadForDomainTenants(user, tenant, localTenants, worksmobileComparisonRootConfig(localTenants))
	if err != nil {
		return false
	}
	return !worksmobileUserOrganizationsEqual(expected.Organizations, remote.Organizations)
}

func worksmobileComparisonRootConfig(localTenants map[string]domain.Tenant) domain.JSONMap {
	for _, tenant := range localTenants {
		if strings.TrimSpace(tenant.Slug) == HanmacFamilyTenantSlug {
			return tenant.Config
		}
	}
	return nil
}

type worksmobileComparableOrgUnit struct {
	organizationPrimary bool
	organizationEmail   string
	unitPrimary         bool
	positionID          string
	comparePosition     bool
	manager             *bool
	compareManager      bool
}

func worksmobileUserOrganizationsEqual(expected []WorksmobileUserOrganization, remote []WorksmobileUserOrganization) bool {
	expectedUnits := flattenExpectedWorksmobileUserOrganizations(expected)
	remoteUnits := flattenRemoteWorksmobileUserOrganizations(remote)
	if len(expectedUnits) != len(remoteUnits) {
		return false
	}
	for key, expectedUnit := range expectedUnits {
		remoteUnit, ok := remoteUnits[key]
		if !ok {
			return false
		}
		if expectedUnit.organizationPrimary != remoteUnit.organizationPrimary {
			return false
		}
		if strings.ToLower(expectedUnit.organizationEmail) != strings.ToLower(remoteUnit.organizationEmail) {
			return false
		}
		if expectedUnit.unitPrimary != remoteUnit.unitPrimary {
			return false
		}
		if expectedUnit.comparePosition && strings.TrimSpace(expectedUnit.positionID) != strings.TrimSpace(remoteUnit.positionID) {
			return false
		}
		if expectedUnit.compareManager && !worksmobileBoolPointersEqual(expectedUnit.manager, remoteUnit.manager) {
			return false
		}
	}
	return true
}

func flattenExpectedWorksmobileUserOrganizations(organizations []WorksmobileUserOrganization) map[string]worksmobileComparableOrgUnit {
	result := map[string]worksmobileComparableOrgUnit{}
	for _, organization := range organizations {
		for _, orgUnit := range organization.OrgUnits {
			key := worksmobileComparableOrgUnitKey(organization.DomainID, orgUnit.OrgUnitID)
			if key == "" {
				continue
			}
			result[key] = worksmobileComparableOrgUnit{
				organizationPrimary: organization.Primary,
				organizationEmail:   strings.TrimSpace(organization.Email),
				unitPrimary:         orgUnit.Primary,
				positionID:          strings.TrimSpace(orgUnit.PositionID),
				comparePosition:     strings.TrimSpace(orgUnit.PositionID) != "",
				manager:             orgUnit.IsManager,
				compareManager:      orgUnit.IsManager != nil,
			}
		}
	}
	return result
}

func flattenRemoteWorksmobileUserOrganizations(organizations []WorksmobileUserOrganization) map[string]worksmobileComparableOrgUnit {
	result := map[string]worksmobileComparableOrgUnit{}
	for _, organization := range organizations {
		for _, orgUnit := range organization.OrgUnits {
			key := worksmobileComparableOrgUnitKey(organization.DomainID, orgUnit.OrgUnitID)
			if key == "" {
				continue
			}
			result[key] = worksmobileComparableOrgUnit{
				organizationPrimary: organization.Primary,
				organizationEmail:   strings.TrimSpace(organization.Email),
				unitPrimary:         orgUnit.Primary,
				positionID:          strings.TrimSpace(orgUnit.PositionID),
				manager:             orgUnit.IsManager,
			}
		}
	}
	return result
}

func worksmobileComparableOrgUnitKey(domainID int64, orgUnitID string) string {
	orgUnitID = strings.TrimSpace(orgUnitID)
	if domainID == 0 || orgUnitID == "" {
		return ""
	}
	return fmt.Sprintf("%d:%s", domainID, orgUnitID)
}

func worksmobileBoolPointersEqual(left, right *bool) bool {
	if left == nil || right == nil {
		return left == nil && right == nil
	}
	return *left == *right
}

func worksmobileUserManagerNeedsUpdate(user domain.User, remote WorksmobileRemoteUser) bool {
	localManagers := worksmobileUserExplicitOrgUnitManagers(user)
	if len(localManagers) == 0 {
		return false
	}
	remoteManagers := remote.OrgUnitManagers
	if len(remoteManagers) == 0 && remote.PrimaryOrgUnitID != "" {
		remoteManagers = map[string]*bool{remote.PrimaryOrgUnitID: remote.PrimaryOrgUnitIsManager}
	}
	for remoteOrgUnitID, remoteManager := range remoteManagers {
		if remoteManager == nil {
			continue
		}
		localManager, ok := localManagers[worksmobileOrgUnitLocalExternalKey(remoteOrgUnitID)]
		if ok && localManager != *remoteManager {
			return true
		}
	}
	return false
}

func worksmobileUserExplicitOrgUnitManagers(user domain.User) map[string]bool {
	managers := map[string]bool{}
	for _, appointment := range worksmobileAppointmentsFromMetadata(user.Metadata) {
		if appointment.TenantID == "" || !appointment.HasManager {
			continue
		}
		managers[appointment.TenantID] = appointment.IsManager
	}
	return managers
}

func worksmobileOrgUnitLocalExternalKey(orgUnitID string) string {
	normalized := strings.TrimSpace(orgUnitID)
	if after, ok := strings.CutPrefix(normalized, "externalKey:"); ok {
		return strings.TrimSpace(after)
	}
	return normalized
}

func worksmobileUserPrimaryOrgID(user domain.User) string {
	if user.TenantID == nil {
		return ""
	}
	return strings.TrimSpace(*user.TenantID)
}

func worksmobileUserPrimaryOrgName(user domain.User, localTenants map[string]domain.Tenant) string {
	tenantID := worksmobileUserPrimaryOrgID(user)
	if tenantID == "" {
		return ""
	}
	if tenant, ok := localTenants[tenantID]; ok {
		return strings.TrimSpace(tenant.Name)
	}
	if user.Tenant != nil && user.Tenant.ID == tenantID {
		return strings.TrimSpace(user.Tenant.Name)
	}
	return ""
}

func worksmobileUserPrimaryOrgSlug(user domain.User, localTenants map[string]domain.Tenant) string {
	tenantID := worksmobileUserPrimaryOrgID(user)
	if tenantID == "" {
		return ""
	}
	if tenant, ok := localTenants[tenantID]; ok {
		return strings.TrimSpace(tenant.Slug)
	}
	if user.Tenant != nil && user.Tenant.ID == tenantID {
		return strings.TrimSpace(user.Tenant.Slug)
	}
	return ""
}

func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []WorksmobileRemoteGroup, includeMatched bool) []WorksmobileComparisonItem {
	remoteByExternalID := map[string][]WorksmobileRemoteGroup{}
	remoteByID := map[string]WorksmobileRemoteGroup{}
	for _, remote := range remoteGroups {
		if remote.ID != "" {
			remoteByID[remote.ID] = remote
		}
		if remote.ExternalID != "" {
			remoteByExternalID[remote.ExternalID] = append(remoteByExternalID[remote.ExternalID], remote)
		}
	}
	tenantByID := worksmobileTenantByID(localTenants)
	localByID := map[string]domain.Tenant{}
	ignoredLocalByID := map[string]bool{}
	matchedRemoteIDs := map[string]bool{}
	result := make([]WorksmobileComparisonItem, 0)
	for _, tenant := range localTenants {
		if !isWorksmobileOrgUnitTenant(tenant, tenantByID) {
			ignoredLocalByID[tenant.ID] = true
			continue
		}
		localByID[tenant.ID] = tenant
		remote, matched := matchingWorksmobileRemoteGroupForTenant(tenant, remoteByExternalID[tenant.ID], tenantByID)
		item := WorksmobileComparisonItem{
			ResourceType:    "GROUP",
			BaronID:         tenant.ID,
			BaronSlug:       tenant.Slug,
			BaronName:       tenant.Name,
			BaronParentID:   worksmobileTenantParentID(tenant),
			BaronParentSlug: worksmobileTenantParentSlug(tenant, tenantByID),
			BaronParentName: worksmobileTenantParentName(tenant, tenantByID),
			Status:          "missing_in_worksmobile",
		}
		if matched {
			item.Status = "matched"
			item.WorksmobileID = remote.ID
			item.ExternalKey = remote.ExternalID
			item.WorksmobileName = remote.DisplayName
			item.WorksmobileEmail = remote.Email
			item.WorksmobileDomainID = remote.DomainID
			item.WorksmobileDomainName = remote.DomainName
			item.WorksmobileParentID = remote.ParentID
			item.WorksmobileParentName = remote.ParentName
			if parent, ok := tenantByID[item.BaronParentID]; ok {
				if parentRemote, ok := matchingWorksmobileRemoteGroupForTenant(parent, remoteByExternalID[item.BaronParentID], tenantByID); ok {
					item.BaronParentWorksmobileID = parentRemote.ID
					item.BaronParentWorksmobileName = parentRemote.DisplayName
					item.BaronParentWorksmobileEmail = parentRemote.Email
				}
			} else if parentRemote, ok := firstWorksmobileRemoteGroup(remoteByExternalID[item.BaronParentID]); ok {
				item.BaronParentWorksmobileID = parentRemote.ID
				item.BaronParentWorksmobileName = parentRemote.DisplayName
				item.BaronParentWorksmobileEmail = parentRemote.Email
			}
			if parentRemote, ok := remoteByID[remote.ParentID]; ok {
				if item.WorksmobileParentName == "" {
					item.WorksmobileParentName = parentRemote.DisplayName
				}
				item.WorksmobileParentEmail = parentRemote.Email
				item.WorksmobileParentExternalKey = parentRemote.ExternalID
			}
			item = fillWorksmobileParentFromBaronParentMatch(item)
			if worksmobileGroupNeedsUpdate(tenant, remote, remoteByID, remoteByExternalID, tenantByID) {
				item.Status = "needs_update"
			}
			matchedRemoteIDs[remote.ID] = true
		}
		if matched && item.Status == "matched" && !includeMatched {
			continue
		}
		result = append(result, item)
	}
	for _, remote := range remoteGroups {
		if matchedRemoteIDs[remote.ID] {
			continue
		}
		if remote.ExternalID == "" {
			result = append(result, WorksmobileComparisonItem{
				ResourceType:          "GROUP",
				WorksmobileID:         remote.ID,
				ExternalKey:           remote.ExternalID,
				WorksmobileName:       remote.DisplayName,
				WorksmobileEmail:      remote.Email,
				WorksmobileDomainID:   remote.DomainID,
				WorksmobileDomainName: remote.DomainName,
				WorksmobileParentID:   remote.ParentID,
				WorksmobileParentName: remote.ParentName,
				Status:                "missing_external_key",
			})
			if parentRemote, ok := remoteByID[remote.ParentID]; ok {
				last := &result[len(result)-1]
				if last.WorksmobileParentName == "" {
					last.WorksmobileParentName = parentRemote.DisplayName
				}
				last.WorksmobileParentEmail = parentRemote.Email
				last.WorksmobileParentExternalKey = parentRemote.ExternalID
			}
			continue
		}
		if ignoredLocalByID[remote.ExternalID] {
			continue
		}
		if _, ok := localByID[remote.ExternalID]; !ok {
			result = append(result, WorksmobileComparisonItem{
				ResourceType:          "GROUP",
				WorksmobileID:         remote.ID,
				ExternalKey:           remote.ExternalID,
				WorksmobileName:       remote.DisplayName,
				WorksmobileEmail:      remote.Email,
				WorksmobileDomainID:   remote.DomainID,
				WorksmobileDomainName: remote.DomainName,
				WorksmobileParentID:   remote.ParentID,
				WorksmobileParentName: remote.ParentName,
				Status:                "missing_in_baron",
			})
			if parentRemote, ok := remoteByID[remote.ParentID]; ok {
				last := &result[len(result)-1]
				if last.WorksmobileParentName == "" {
					last.WorksmobileParentName = parentRemote.DisplayName
				}
				last.WorksmobileParentEmail = parentRemote.Email
				last.WorksmobileParentExternalKey = parentRemote.ExternalID
			}
		}
	}
	return result
}

func matchingWorksmobileRemoteGroupForTenant(tenant domain.Tenant, remotes []WorksmobileRemoteGroup, tenantByID map[string]domain.Tenant) (WorksmobileRemoteGroup, bool) {
	if len(remotes) == 0 {
		return WorksmobileRemoteGroup{}, false
	}
	expectedDomainID, hasExpectedDomainID := expectedWorksmobileDomainIDForTenant(tenant, tenantByID)
	if !hasExpectedDomainID {
		return remotes[0], true
	}
	var unknownDomain WorksmobileRemoteGroup
	hasUnknownDomain := false
	for i := range remotes {
		remote := remotes[i]
		if remote.DomainID == expectedDomainID {
			return remote, true
		}
		if remote.DomainID == 0 && !hasUnknownDomain {
			unknownDomain = remote
			hasUnknownDomain = true
		}
	}
	if hasUnknownDomain {
		return unknownDomain, true
	}
	return WorksmobileRemoteGroup{}, false
}

func firstWorksmobileRemoteGroup(remotes []WorksmobileRemoteGroup) (WorksmobileRemoteGroup, bool) {
	if len(remotes) == 0 {
		return WorksmobileRemoteGroup{}, false
	}
	return remotes[0], true
}

func expectedWorksmobileDomainIDForTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) (int64, bool) {
	domainTenant := worksmobileDomainClassificationTenant(tenant, tenantByID)
	domainID, err := ResolveWorksmobileDomainIDFromTenant(domainTenant, nil)
	if err != nil || domainID <= 0 {
		return 0, false
	}
	return domainID, true
}

func worksmobileGroupNeedsUpdate(tenant domain.Tenant, remote WorksmobileRemoteGroup, remoteByID map[string]WorksmobileRemoteGroup, remoteByExternalID map[string][]WorksmobileRemoteGroup, tenantByID map[string]domain.Tenant) bool {
	if strings.TrimSpace(tenant.Name) != strings.TrimSpace(remote.DisplayName) {
		return true
	}

	expectedParentExternalKey := expectedWorksmobileParentExternalKey(tenant, remoteByExternalID, tenantByID)
	actualParentExternalKey := ""
	if remote.ParentID != "" {
		actualParentExternalKey = strings.TrimSpace(remoteByID[remote.ParentID].ExternalID)
	}
	return expectedParentExternalKey != actualParentExternalKey
}

func expectedWorksmobileParentExternalKey(tenant domain.Tenant, remoteByExternalID map[string][]WorksmobileRemoteGroup, tenantByID map[string]domain.Tenant) string {
	parentID := worksmobileTenantParentID(tenant)
	if parentID == "" {
		return ""
	}
	if parent, ok := tenantByID[parentID]; ok && parent.Slug == "baron-group" {
		return ""
	}
	parent, ok := tenantByID[parentID]
	if !ok {
		return ""
	}
	if _, ok := matchingWorksmobileRemoteGroupForTenant(parent, remoteByExternalID[parentID], tenantByID); !ok {
		return ""
	}
	return parentID
}

func fillWorksmobileParentFromBaronParentMatch(item WorksmobileComparisonItem) WorksmobileComparisonItem {
	if item.WorksmobileParentID == "" || item.WorksmobileParentID != item.BaronParentWorksmobileID {
		return item
	}
	if item.WorksmobileParentName == "" {
		item.WorksmobileParentName = item.BaronParentWorksmobileName
	}
	if item.WorksmobileParentEmail == "" {
		item.WorksmobileParentEmail = item.BaronParentWorksmobileEmail
	}
	if item.WorksmobileParentExternalKey == "" {
		item.WorksmobileParentExternalKey = item.BaronParentID
	}
	return item
}

func worksmobileTenantByID(tenants []domain.Tenant) map[string]domain.Tenant {
	result := make(map[string]domain.Tenant, len(tenants))
	for _, tenant := range tenants {
		result[tenant.ID] = tenant
	}
	return result
}

func worksmobileTenantParentID(tenant domain.Tenant) string {
	if tenant.ParentID == nil {
		return ""
	}
	return strings.TrimSpace(*tenant.ParentID)
}

func worksmobileTenantParentName(tenant domain.Tenant, tenantByID map[string]domain.Tenant) string {
	parentID := worksmobileTenantParentID(tenant)
	if parentID == "" {
		return ""
	}
	return strings.TrimSpace(tenantByID[parentID].Name)
}

func worksmobileTenantParentSlug(tenant domain.Tenant, tenantByID map[string]domain.Tenant) string {
	parentID := worksmobileTenantParentID(tenant)
	if parentID == "" {
		return ""
	}
	return strings.TrimSpace(tenantByID[parentID].Slug)
}

func worksmobileSyncScopeUsers(users []domain.User) []domain.User {
	if len(users) == 0 {
		return users
	}
	filtered := make([]domain.User, 0, len(users))
	for _, user := range users {
		if !domain.IsWorksProvisionedUserStatus(user.Status) {
			continue
		}
		filtered = append(filtered, user)
	}
	return filtered
}
