package identity

import (
	"context"
	"database/sql"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
	"hyapp/services/user-service/internal/storage/mysql/shared"
	userstorage "hyapp/services/user-service/internal/storage/mysql/user"
)

type prettyAssignment struct {
	// PrettyID 只在靓号池申请和后台发放时存在；旧付费临时靓号没有池表记录，返回身份时只返回展示号。
	PrettyID         string
	LeaseID          string
	DisplayUserID    string
	ExpiresAtMs      int64
	PaymentReceiptID string
	// Source 决定释放后的池表状态：pool 号码回到 available，admin_grant 号码进入 released 留审计。
	Source         string
	ChangeType     string
	Reason         string
	OperatorUserID int64
	RequestID      string
	NowMs          int64
}

type prettyReleaseCommand struct {
	// LeaseID 为空时按用户当前 active lease 释放；非空时必须匹配当前 active lease，防止旧请求误释放新靓号。
	LeaseID string
	// Reason 写入 lease、展示号、靓号池记录和变更日志，用于区分到期、替换和后台回收。
	Reason string
	// ChangeType 是 display_user_id_change_logs 的业务类型。
	ChangeType string
	// LeaseStatus 表示租约释放后的生命周期状态。
	LeaseStatus userdomain.PrettyLeaseStatus
	// DisplayStatus 表示当前展示号占用释放后的状态，到期用 expired，后台回收用 released。
	DisplayStatus userdomain.DisplayUserIDStatus
	// OperatorAdminID 只有后台回收这类管理动作存在；到期任务为 0。
	OperatorAdminID int64
	RequestID       string
	NowMs           int64
}

// ApplyPrettyDisplayUserID 兼容旧付费靓号接口，仍要求租期和支付回执，但允许覆盖旧靓号。
func (r *Repository) ApplyPrettyDisplayUserID(ctx context.Context, command userdomain.PrettyDisplayUserIDCommand) (userdomain.Identity, string, error) {
	// 旧接口按展示号申请，没有 pretty_id；先懒过期同名到期靓号，避免已过期记录继续占用唯一性。
	if err := r.expirePrettyByDisplayUserID(ctx, command.PrettyDisplayID, command.NowMs, command.RequestID); err != nil {
		return userdomain.Identity{}, "", err
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	defer tx.Rollback()

	user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID)
	if err == sql.ErrNoRows {
		return userdomain.Identity{}, "", xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	// 用户锁在全局唯一性校验前拿到，保证同一用户的展示号替换和其他申请不会交叉写入。
	if err := r.assertDisplayUserIDGloballyAvailable(ctx, tx, command.PrettyDisplayID); err != nil {
		if xerr.IsCode(err, xerr.DisplayUserIDExists) {
			return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display_user_id is not available")
		}
		return userdomain.Identity{}, "", err
	}

	identity, err := r.replacePrettyInTx(ctx, tx, user, prettyAssignment{
		LeaseID:          command.LeaseID,
		DisplayUserID:    command.PrettyDisplayID,
		ExpiresAtMs:      command.NowMs + command.LeaseDurationMs,
		PaymentReceiptID: command.PaymentReceiptID,
		Source:           command.Source,
		ChangeType:       "pretty_apply",
		Reason:           "pretty_apply",
		OperatorUserID:   command.OperatorUserID,
		RequestID:        command.RequestID,
		NowMs:            command.NowMs,
	})
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.Identity{}, "", err
	}

	return identity, command.LeaseID, nil
}

// ListAvailablePrettyDisplayIDs 返回用户当前等级可申请的靓号池号码。
func (r *Repository) ListAvailablePrettyDisplayIDs(ctx context.Context, query userdomain.AvailablePrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) {
	appCode := appcode.Normalize(query.AppCode)
	page, pageSize := normalizeIdentityPage(query.Page, query.PageSize)
	offset := (page - 1) * pageSize
	// 可申请列表不接受客户端自报轨道等级；服务层先读取用户真实等级，这里按财富、游戏、魅力三条轨道做 OR 匹配。
	levelWhere := `
		AND (
		  (pool.level_track = ? AND pool.min_level <= ? AND pool.max_level >= ?)
		  OR (pool.level_track = ? AND pool.min_level <= ? AND pool.max_level >= ?)
		  OR (pool.level_track = ? AND pool.min_level <= ? AND pool.max_level >= ?)
		)`
	levelArgs := []any{
		userdomain.LevelTrackWealth, query.Levels.WealthLevel, query.Levels.WealthLevel,
		userdomain.LevelTrackGame, query.Levels.GameLevel, query.Levels.GameLevel,
		userdomain.LevelTrackCharm, query.Levels.CharmLevel, query.Levels.CharmLevel,
	}

	var total int64
	countArgs := append([]any{appCode, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDSourcePool, userdomain.PrettyDisplayIDPoolStatusActive}, levelArgs...)
	if err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM pretty_display_ids pdi
		JOIN pretty_display_id_pools pool
		  ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id
		WHERE pdi.app_code = ?
		  AND pdi.status = ?
		  AND pdi.source = ?
		  AND pool.status = ?
		`+levelWhere, countArgs...).Scan(&total); err != nil {
		return nil, 0, err
	}

	selectArgs := append(countArgs, int(pageSize), int(offset))
	rows, err := r.db.QueryContext(ctx, `
		SELECT `+prettyDisplayIDSelectColumns()+`
		FROM pretty_display_ids pdi
		JOIN pretty_display_id_pools pool
		  ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id
		WHERE pdi.app_code = ?
		  AND pdi.status = ?
		  AND pdi.source = ?
		  AND pool.status = ?
		`+levelWhere+`
		ORDER BY pool.sort_order DESC, pdi.created_at_ms ASC, pdi.pretty_id ASC
		LIMIT ? OFFSET ?
	`, selectArgs...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items, err := scanPrettyDisplayIDRows(rows)
	if err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

// ApplyPrettyDisplayIDFromPool 申请靓号池号码，号码本身、用户当前等级和用户展示号在同一事务中校验。
func (r *Repository) ApplyPrettyDisplayIDFromPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolApplyCommand) (userdomain.Identity, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.Identity{}, err
	}
	defer tx.Rollback()

	appCode := appcode.Normalize(command.AppCode)
	// 先锁定 pretty_display_ids 行，保证同一个 pretty_id 只能被一个申请事务从 available 改为 assigned。
	item, err := queryPrettyDisplayIDForUpdate(ctx, tx, appCode, command.PrettyID)
	if err == sql.ErrNoRows {
		return userdomain.Identity{}, xerr.New(xerr.NotFound, "pretty display id not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}
	if item.Source != userdomain.PrettyDisplayIDSourcePool || item.Status != userdomain.PrettyDisplayIDStatusAvailable {
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id is not available")
	}

	// 池配置也加锁读取，避免管理员同时禁用池或调整等级区间时出现旧规则通过的申请。
	pool, err := queryPrettyDisplayIDPoolForUpdate(ctx, tx, appCode, item.PoolID)
	if err == sql.ErrNoRows {
		return userdomain.Identity{}, xerr.New(xerr.NotFound, "pretty display id pool not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}
	if pool.Status != userdomain.PrettyDisplayIDPoolStatusActive {
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id pool is disabled")
	}
	// 只校验该池声明的等级轨道；用户在其他轨道等级足够也不能跨池申请。
	level, ok := command.Levels.LevelForTrack(pool.LevelTrack)
	if !ok || level < pool.MinLevel || level > pool.MaxLevel {
		return userdomain.Identity{}, xerr.New(xerr.PermissionDenied, "user level is not allowed for pretty display id pool")
	}

	// 用户行最后锁定，后续替换展示号时以 users 当前快照为准释放旧靓号或挂起默认短号。
	user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID)
	if err == sql.ErrNoRows {
		return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}

	// 池申请默认长期持有，expires_at_ms=0；替换当前靓号和写入租约先完成，再把池号码标记为 assigned。
	identity, err := r.replacePrettyInTx(ctx, tx, user, prettyAssignment{
		PrettyID:       item.PrettyID,
		LeaseID:        command.LeaseID,
		DisplayUserID:  item.DisplayUserID,
		ExpiresAtMs:    0,
		Source:         userdomain.PrettyDisplayIDSourcePool,
		ChangeType:     "pretty_pool_apply",
		Reason:         "pretty_pool_apply",
		OperatorUserID: command.UserID,
		RequestID:      command.RequestID,
		NowMs:          command.NowMs,
	})
	if err != nil {
		return userdomain.Identity{}, err
	}

	// WHERE status=available 是最后一道并发保护，确保只有锁住并仍可用的池号会被分配。
	_, err = tx.ExecContext(ctx, `
		UPDATE pretty_display_ids
		SET status = ?, assigned_user_id = ?, assigned_lease_id = ?, assigned_at_ms = ?,
		    released_at_ms = 0, release_reason = '', updated_at_ms = ?
		WHERE app_code = ? AND pretty_id = ? AND status = ?
	`, userdomain.PrettyDisplayIDStatusAssigned, command.UserID, command.LeaseID, command.NowMs, command.NowMs, appCode, command.PrettyID, userdomain.PrettyDisplayIDStatusAvailable)
	if err != nil {
		return userdomain.Identity{}, shared.MapPrettyDuplicateError(err)
	}
	if err := tx.Commit(); err != nil {
		return userdomain.Identity{}, err
	}
	return identity, nil
}

// ExpirePrettyDisplayUserID 把到期靓号恢复成默认短号；该方法是定时和懒过期的共同事务。
func (r *Repository) ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.Identity{}, err
	}
	defer tx.Rollback()

	user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID)
	if err == sql.ErrNoRows {
		return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, err
	}

	identity, err := r.expirePrettyInTx(ctx, tx, user, command.LeaseID, command.NowMs, command.RequestID)
	if err != nil {
		return userdomain.Identity{}, err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.Identity{}, err
	}

	return identity, nil
}

func (r *Repository) expirePrettyByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64, requestID string) error {
	// 申请同名靓号前只处理已经到期的占用；未到期的冲突仍交给唯一性校验返回不可用。
	var userID int64
	err := r.db.QueryRowContext(ctx, `
		SELECT user_id
		FROM users
		WHERE app_code = ?
		  AND current_display_user_id_kind = ?
		  AND current_display_user_id_expires_at_ms IS NOT NULL
		  AND current_display_user_id_expires_at_ms > 0
		  AND current_display_user_id_expires_at_ms <= ?
		  AND (current_display_user_id = ? OR default_display_user_id = ?)
		LIMIT 1
	`, appcode.FromContext(ctx), string(userdomain.DisplayUserIDKindPretty), nowMs, displayUserID, displayUserID).Scan(&userID)
	if err == sql.ErrNoRows {
		return nil
	}
	if err != nil {
		return err
	}

	_, err = r.ExpirePrettyDisplayUserID(ctx, userdomain.ExpirePrettyDisplayUserIDCommand{
		UserID:    userID,
		RequestID: requestID,
		NowMs:     nowMs,
	})
	return err
}

// ExpirePrettyByDisplayUserID 给认证存储复用短号懒过期事务。
func ExpirePrettyByDisplayUserID(ctx context.Context, db *sql.DB, displayUserID string, nowMs int64, requestID string) error {
	return New(db).expirePrettyByDisplayUserID(ctx, displayUserID, nowMs, requestID)
}

func (r *Repository) expirePrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, leaseID string, nowMs int64, requestID string) (userdomain.Identity, error) {
	if user.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty {
		// 当前已经是默认短号时，过期恢复是幂等操作，直接返回现有身份。
		return user.EffectiveIdentity(), nil
	}
	if user.CurrentDisplayUserIDExpiresAtMs == 0 || user.CurrentDisplayUserIDExpiresAtMs > nowMs {
		// expires_at_ms=0 表示长期靓号，正未来时间表示尚未到期，这两类都不能被过期任务释放。
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not expired")
	}

	return r.releaseCurrentPrettyInTx(ctx, tx, user, prettyReleaseCommand{
		LeaseID:       leaseID,
		Reason:        "pretty_expire",
		ChangeType:    "pretty_expire",
		LeaseStatus:   userdomain.PrettyLeaseStatusExpired,
		DisplayStatus: userdomain.DisplayUserIDStatusExpired,
		RequestID:     requestID,
		NowMs:         nowMs,
	})
}

// RecyclePrettyDisplayID 强制回收后台列表中仍处于 assigned 的靓号，并把用户恢复到默认短号。
func (r *Repository) RecyclePrettyDisplayID(ctx context.Context, command userdomain.PrettyDisplayIDRecycleCommand) (userdomain.PrettyDisplayID, error) {
	appCode := appcode.Normalize(command.AppCode)
	reason := strings.TrimSpace(command.Reason)
	if reason == "" {
		// 默认原因必须和到期恢复区分，方便审计和后续排查运营手动动作。
		reason = "admin_recycle_pretty"
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.PrettyDisplayID{}, err
	}
	defer tx.Rollback()

	item, err := queryPrettyDisplayIDForUpdate(ctx, tx, appCode, strings.TrimSpace(command.PrettyID))
	if err == sql.ErrNoRows {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.NotFound, "pretty display id not found")
	}
	if err != nil {
		return userdomain.PrettyDisplayID{}, err
	}
	if item.Status != userdomain.PrettyDisplayIDStatusAssigned || item.AssignedUserID <= 0 || item.AssignedLeaseID == "" {
		// 只有真实占用中的号码才能回收；可用、禁用、预留和已释放状态仍走普通状态管理。
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id is not assigned")
	}

	user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", item.AssignedUserID)
	if err == sql.ErrNoRows {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.NotFound, "assigned user not found")
	}
	if err != nil {
		return userdomain.PrettyDisplayID{}, err
	}
	if user.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty || user.CurrentDisplayUserID != item.DisplayUserID {
		// 列表记录和 users 当前展示号不一致时不能盲目清表，交给后续数据修复处理，避免回收错用户的新靓号。
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display id lease is not active")
	}

	if _, err := r.releaseCurrentPrettyInTx(ctx, tx, user, prettyReleaseCommand{
		LeaseID:         item.AssignedLeaseID,
		Reason:          reason,
		ChangeType:      "admin_recycle_pretty",
		LeaseStatus:     userdomain.PrettyLeaseStatusCancelled,
		DisplayStatus:   userdomain.DisplayUserIDStatusReleased,
		OperatorAdminID: command.OperatorAdminID,
		RequestID:       command.RequestID,
		NowMs:           command.NowMs,
	}); err != nil {
		return userdomain.PrettyDisplayID{}, err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.PrettyDisplayID{}, err
	}

	return queryPrettyDisplayID(ctx, r.db, appCode, item.PrettyID)
}

func (r *Repository) releaseCurrentPrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, command prettyReleaseCommand) (userdomain.Identity, error) {
	appCode := appcode.FromContext(ctx)
	// 先锁 active lease，再按调用方传入的 lease_id 做精确校验；后台回收必须命中列表行里的 lease，过期任务可以空 lease 幂等恢复。
	activeLeaseID, _, err := lockActivePrettyLease(ctx, tx, appCode, user.UserID)
	if err != nil {
		return userdomain.Identity{}, err
	}
	if command.LeaseID != "" && activeLeaseID != command.LeaseID {
		return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not active")
	}

	if command.LeaseID != "" {
		_, err = tx.ExecContext(ctx, `
			UPDATE pretty_display_user_id_leases
			SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ?
			WHERE app_code = ? AND lease_id = ? AND status = ?
		`, string(command.LeaseStatus), command.NowMs, command.Reason, command.NowMs, appCode, command.LeaseID, string(userdomain.PrettyLeaseStatusActive))
	} else {
		_, err = tx.ExecContext(ctx, `
			UPDATE pretty_display_user_id_leases
			SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ?
			WHERE app_code = ? AND user_id = ? AND status = ?
		`, string(command.LeaseStatus), command.NowMs, command.Reason, command.NowMs, appCode, user.UserID, string(userdomain.PrettyLeaseStatusActive))
	}
	if err != nil {
		return userdomain.Identity{}, err
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE user_display_user_ids
		SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
	`, string(command.DisplayStatus), command.NowMs, command.Reason, command.NowMs, appCode, user.UserID, user.CurrentDisplayUserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive))
	if err != nil {
		return userdomain.Identity{}, err
	}
	if activeLeaseID != "" {
		// 池号和后台发放号释放后的状态不同，统一通过 lease 反查池表行处理。
		if err := releasePrettyDisplayIDByLeaseInTx(ctx, tx, appCode, activeLeaseID, command.NowMs, command.Reason); err != nil {
			return userdomain.Identity{}, err
		}
	}

	// 默认短号在用户使用靓号期间保持 held；释放时只把原默认号重新激活，不重新生成短号。
	_, err = tx.ExecContext(ctx, `
		UPDATE user_display_user_ids
		SET status = ?, activated_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
	`, string(userdomain.DisplayUserIDStatusActive), command.NowMs, command.NowMs, appCode, user.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusHeld))
	if err != nil {
		return userdomain.Identity{}, err
	}

	_, err = tx.ExecContext(ctx, `
		UPDATE users
		SET current_display_user_id = default_display_user_id, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = NULL, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, string(userdomain.DisplayUserIDKindDefault), command.NowMs, appCode, user.UserID)
	if err != nil {
		return userdomain.Identity{}, err
	}

	if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{
		AppCode:     appCode,
		UserID:      user.UserID,
		OldID:       user.CurrentDisplayUserID,
		NewID:       user.DefaultDisplayUserID,
		OldKind:     userdomain.DisplayUserIDKindPretty,
		NewKind:     userdomain.DisplayUserIDKindDefault,
		ChangeType:  command.ChangeType,
		LeaseID:     activeLeaseID,
		Reason:      command.Reason,
		OperatorID:  command.OperatorAdminID,
		RequestID:   command.RequestID,
		CreatedAtMs: command.NowMs,
	}); err != nil {
		return userdomain.Identity{}, err
	}

	return userdomain.Identity{
		AppCode:              appCode,
		UserID:               user.UserID,
		DisplayUserID:        user.DefaultDisplayUserID,
		DefaultDisplayUserID: user.DefaultDisplayUserID,
		DisplayUserIDKind:    userdomain.DisplayUserIDKindDefault,
		Status:               userdomain.DisplayUserIDStatusActive,
	}, nil
}

func (r *Repository) replacePrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, assignment prettyAssignment) (userdomain.Identity, error) {
	appCode := appcode.FromContext(ctx)
	if user.DisplayUserIDExpired(assignment.NowMs) {
		// 替换前先在同一事务内清理已过期靓号，把 users 快照恢复到默认号，后续日志的 old_id 才准确。
		identity, err := r.expirePrettyInTx(ctx, tx, user, "", assignment.NowMs, assignment.RequestID)
		if err != nil {
			return userdomain.Identity{}, err
		}
		user.CurrentDisplayUserID = identity.DisplayUserID
		user.CurrentDisplayUserIDKind = identity.DisplayUserIDKind
		user.CurrentDisplayUserIDExpiresAtMs = 0
		user.PrettyID = ""
		user.PrettyDisplayUserID = ""
	}

	if user.CurrentDisplayUserIDKind == userdomain.DisplayUserIDKindPretty {
		// 用户当前已有 active 靓号时，新靓号直接覆盖旧靓号，旧 lease 必须先取消并记录释放原因。
		activeLeaseID, _, err := lockActivePrettyLease(ctx, tx, appCode, user.UserID)
		if err != nil {
			return userdomain.Identity{}, err
		}
		if activeLeaseID == "" {
			return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not active")
		}
		_, err = tx.ExecContext(ctx, `
			UPDATE pretty_display_user_id_leases
			SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ?
			WHERE app_code = ? AND lease_id = ? AND status = ?
		`, string(userdomain.PrettyLeaseStatusCancelled), assignment.NowMs, "replaced_by_new_pretty", assignment.NowMs, appCode, activeLeaseID, string(userdomain.PrettyLeaseStatusActive))
		if err != nil {
			return userdomain.Identity{}, err
		}
		// active 的 user_display_user_ids 代表当前展示号占用，覆盖时释放该占用，避免同一用户出现两个 active 靓号。
		_, err = tx.ExecContext(ctx, `
			UPDATE user_display_user_ids
			SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ?
			WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
		`, string(userdomain.DisplayUserIDStatusReleased), assignment.NowMs, "replaced_by_new_pretty", assignment.NowMs, appCode, user.UserID, user.CurrentDisplayUserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive))
		if err != nil {
			return userdomain.Identity{}, err
		}
		// 旧靓号对应的池表记录按来源释放：池号回可申请，后台发放号保留 released 审计态。
		if err := releasePrettyDisplayIDByLeaseInTx(ctx, tx, appCode, activeLeaseID, assignment.NowMs, "replaced_by_new_pretty"); err != nil {
			return userdomain.Identity{}, err
		}
	} else {
		// 用户从默认短号切到靓号时，不删除默认短号，只把它挂起，后续过期或释放可以原样恢复。
		_, err := tx.ExecContext(ctx, `
			UPDATE user_display_user_ids
			SET status = ?, updated_at_ms = ?
			WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ?
		`, string(userdomain.DisplayUserIDStatusHeld), assignment.NowMs, appCode, user.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive))
		if err != nil {
			return userdomain.Identity{}, err
		}
	}

	// 租约是靓号生命周期的主记录；长期靓号 expires_at_ms=0，限时靓号写入明确到期时间。
	_, err := tx.ExecContext(ctx, `
		INSERT INTO pretty_display_user_id_leases (
		  app_code, lease_id, display_user_id, user_id, status, starts_at_ms, expires_at_ms,
		  released_at_ms, release_reason, payment_receipt_id, source, created_at_ms, updated_at_ms
		)
		VALUES (?, ?, ?, ?, ?, ?, ?, 0, '', ?, ?, ?, ?)
	`, appCode, assignment.LeaseID, assignment.DisplayUserID, user.UserID, string(userdomain.PrettyLeaseStatusActive), assignment.NowMs, assignment.ExpiresAtMs, shared.NullableString(assignment.PaymentReceiptID), assignment.Source, assignment.NowMs, assignment.NowMs)
	if err != nil {
		return userdomain.Identity{}, shared.MapPrettyDuplicateError(err)
	}

	// 当前展示号占用单独写入 user_display_user_ids，和 users 快照互相校验，便于全局唯一性检查。
	_, err = tx.ExecContext(ctx, `
		INSERT INTO user_display_user_ids (
		  app_code, display_user_id, user_id, display_user_id_kind, status,
		  assigned_at_ms, activated_at_ms, expires_at_ms, created_at_ms, updated_at_ms
		)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appCode, assignment.DisplayUserID, user.UserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive), assignment.NowMs, assignment.NowMs, assignment.ExpiresAtMs, assignment.NowMs, assignment.NowMs)
	if err != nil {
		return userdomain.Identity{}, shared.MapPrettyDuplicateError(err)
	}

	// users 表保存高频读取快照，资料接口不用每次 join 租约表也能返回当前展示号和到期时间。
	_, err = tx.ExecContext(ctx, `
		UPDATE users
		SET current_display_user_id = ?, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = ?, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ?
	`, assignment.DisplayUserID, string(userdomain.DisplayUserIDKindPretty), assignment.ExpiresAtMs, assignment.NowMs, appCode, user.UserID)
	if err != nil {
		return userdomain.Identity{}, shared.MapPrettyDuplicateError(err)
	}

	// 展示号变更日志记录 old/new、操作者、请求 ID，覆盖、池申请、后台发放和旧付费接口都复用同一审计结构。
	if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{
		AppCode:     appCode,
		UserID:      user.UserID,
		OldID:       user.CurrentDisplayUserID,
		NewID:       assignment.DisplayUserID,
		OldKind:     user.CurrentDisplayUserIDKind,
		NewKind:     userdomain.DisplayUserIDKindPretty,
		ChangeType:  assignment.ChangeType,
		LeaseID:     assignment.LeaseID,
		Reason:      assignment.Reason,
		OperatorID:  assignment.OperatorUserID,
		RequestID:   assignment.RequestID,
		CreatedAtMs: assignment.NowMs,
	}); err != nil {
		return userdomain.Identity{}, err
	}

	return userdomain.Identity{
		AppCode:                  appCode,
		UserID:                   user.UserID,
		DisplayUserID:            assignment.DisplayUserID,
		DefaultDisplayUserID:     user.DefaultDisplayUserID,
		DisplayUserIDKind:        userdomain.DisplayUserIDKindPretty,
		DisplayUserIDExpiresAtMs: assignment.ExpiresAtMs,
		PrettyID:                 assignment.PrettyID,
		PrettyDisplayUserID:      assignment.DisplayUserID,
		Status:                   userdomain.DisplayUserIDStatusActive,
	}, nil
}

func lockActivePrettyLease(ctx context.Context, tx *sql.Tx, appCode string, userID int64) (string, string, error) {
	var leaseID string
	var displayUserID string
	err := tx.QueryRowContext(ctx, `
		SELECT lease_id, display_user_id
		FROM pretty_display_user_id_leases
		WHERE app_code = ? AND user_id = ? AND status = ?
		FOR UPDATE
	`, appcode.Normalize(appCode), userID, string(userdomain.PrettyLeaseStatusActive)).Scan(&leaseID, &displayUserID)
	if err == sql.ErrNoRows {
		return "", "", nil
	}
	if err != nil {
		return "", "", err
	}
	return leaseID, displayUserID, nil
}

func releasePrettyDisplayIDByLeaseInTx(ctx context.Context, tx *sql.Tx, appCode string, leaseID string, nowMs int64, reason string) error {
	// source=pool 的号码释放后重新进入池；后台手动发放的自由靓号没有池归属，释放后进入 released 防止自动复用。
	_, err := tx.ExecContext(ctx, `
		UPDATE pretty_display_ids
		SET status = CASE
		      WHEN source = ? THEN ?
		      ELSE ?
		    END,
		    assigned_user_id = 0,
		    assigned_lease_id = '',
		    assigned_at_ms = 0,
		    released_at_ms = ?,
		    release_reason = ?,
		    updated_at_ms = ?
		WHERE app_code = ? AND assigned_lease_id = ? AND status = ?
	`, userdomain.PrettyDisplayIDSourcePool, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusReleased, nowMs, reason, nowMs, appcode.Normalize(appCode), leaseID, userdomain.PrettyDisplayIDStatusAssigned)
	return err
}

func (r *Repository) assertDisplayUserIDGloballyAvailable(ctx context.Context, tx *sql.Tx, displayUserID string) error {
	// users 表同时覆盖真实 user_id、默认短号和当前展示号，避免管理员发放成任何用户已使用的 ID。
	var exists int
	err := tx.QueryRowContext(ctx, `
		SELECT 1
		FROM users
		WHERE app_code = ?
		  AND (CAST(user_id AS CHAR) = ? OR default_display_user_id = ? OR current_display_user_id = ?)
		LIMIT 1
	`, appcode.FromContext(ctx), displayUserID, displayUserID, displayUserID).Scan(&exists)
	if err != nil && err != sql.ErrNoRows {
		return err
	}
	if exists == 1 {
		return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists")
	}

	// user_display_user_ids 覆盖 active/held/reserved/blocked 历史占用，默认短号 held 时也不能被靓号抢占。
	err = tx.QueryRowContext(ctx, `
		SELECT 1
		FROM user_display_user_ids
		WHERE app_code = ? AND display_user_id = ? AND status IN (?, ?, ?, ?)
		LIMIT 1
	`, appcode.FromContext(ctx), displayUserID, string(userdomain.DisplayUserIDStatusActive), string(userdomain.DisplayUserIDStatusHeld), string(userdomain.DisplayUserIDStatusReserved), string(userdomain.DisplayUserIDStatusBlocked)).Scan(&exists)
	if err != nil && err != sql.ErrNoRows {
		return err
	}
	if exists == 1 {
		return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists")
	}

	// pretty_display_ids 覆盖池中未发放、已发放、禁用和预留号码，防止批量生成和后台发放互相撞号。
	err = tx.QueryRowContext(ctx, `
		SELECT 1
		FROM pretty_display_ids
		WHERE app_code = ? AND display_user_id = ? AND status IN (?, ?, ?, ?)
		LIMIT 1
	`, appcode.FromContext(ctx), displayUserID, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusAssigned, userdomain.PrettyDisplayIDStatusDisabled, userdomain.PrettyDisplayIDStatusReserved).Scan(&exists)
	if err != nil && err != sql.ErrNoRows {
		return err
	}
	if exists == 1 {
		return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists")
	}
	return nil
}

// ListPrettyDisplayIDPools 分页读取后台靓号池配置。
func (r *Repository) ListPrettyDisplayIDPools(ctx context.Context, query userdomain.PrettyDisplayIDPoolQuery) ([]userdomain.PrettyDisplayIDPool, int64, error) {
	appCode := appcode.Normalize(query.AppCode)
	page, pageSize := normalizeIdentityPage(query.Page, query.PageSize)
	where := []string{"app_code = ?"}
	args := []any{appCode}
	if query.Status != "" {
		where = append(where, "status = ?")
		args = append(args, query.Status)
	}
	if query.LevelTrack != "" {
		where = append(where, "level_track = ?")
		args = append(args, query.LevelTrack)
	}
	whereSQL := "WHERE " + strings.Join(where, " AND ")

	var total int64
	if err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM pretty_display_id_pools "+whereSQL, args...).Scan(&total); err != nil {
		return nil, 0, err
	}
	rows, err := r.db.QueryContext(ctx, `
		SELECT app_code, pool_id, name, level_track, min_level, max_level, rule_type,
		       COALESCE(CAST(rule_config_json AS CHAR), ''), status, sort_order,
		       created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms
		FROM pretty_display_id_pools
		`+whereSQL+`
		ORDER BY sort_order DESC, created_at_ms DESC, pool_id ASC
		LIMIT ? OFFSET ?
	`, append(args, int(pageSize), int((page-1)*pageSize))...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	pools := make([]userdomain.PrettyDisplayIDPool, 0)
	for rows.Next() {
		pool, err := scanPrettyDisplayIDPool(rows)
		if err != nil {
			return nil, 0, err
		}
		pools = append(pools, pool)
	}
	return pools, total, rows.Err()
}

func (r *Repository) CreatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) {
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO pretty_display_id_pools (
		  app_code, pool_id, name, level_track, min_level, max_level, rule_type,
		  rule_config_json, status, sort_order, created_by_admin_id, updated_by_admin_id,
		  created_at_ms, updated_at_ms
		)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(command.AppCode), command.PoolID, command.Name, command.LevelTrack, command.MinLevel, command.MaxLevel, command.RuleType, nullableJSON(command.RuleConfigJSON), command.Status, command.SortOrder, command.OperatorAdminID, command.OperatorAdminID, command.NowMs, command.NowMs)
	if err != nil {
		return userdomain.PrettyDisplayIDPool{}, err
	}
	return queryPrettyDisplayIDPool(ctx, r.db, appcode.Normalize(command.AppCode), command.PoolID)
}

func (r *Repository) UpdatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) {
	result, err := r.db.ExecContext(ctx, `
		UPDATE pretty_display_id_pools
		SET name = ?, level_track = ?, min_level = ?, max_level = ?, rule_type = ?,
		    rule_config_json = ?, status = ?, sort_order = ?, updated_by_admin_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND pool_id = ?
	`, command.Name, command.LevelTrack, command.MinLevel, command.MaxLevel, command.RuleType, nullableJSON(command.RuleConfigJSON), command.Status, command.SortOrder, command.OperatorAdminID, command.NowMs, appcode.Normalize(command.AppCode), command.PoolID)
	if err != nil {
		return userdomain.PrettyDisplayIDPool{}, err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.NotFound, "pretty display id pool not found")
	}
	return queryPrettyDisplayIDPool(ctx, r.db, appcode.Normalize(command.AppCode), command.PoolID)
}

func (r *Repository) GeneratePrettyDisplayIDs(ctx context.Context, command userdomain.PrettyDisplayIDGenerateCommand) (userdomain.PrettyDisplayIDGenerationBatch, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.PrettyDisplayIDGenerationBatch{}, err
	}
	defer tx.Rollback()

	appCode := appcode.Normalize(command.AppCode)
	pool, err := queryPrettyDisplayIDPoolForUpdate(ctx, tx, appCode, command.PoolID)
	if err == sql.ErrNoRows {
		return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.NotFound, "pretty display id pool not found")
	}
	if err != nil {
		return userdomain.PrettyDisplayIDGenerationBatch{}, err
	}
	// 生成必须锁池配置，确保管理员同时修改规则或禁用池时，本批次仍以一个确定的池状态落库。
	ruleType := command.RuleType
	if ruleType == "" {
		ruleType = pool.RuleType
	}

	generated := int32(0)
	skipped := int32(0)
	if command.RuleConfigJSON == "" {
		// 生成弹窗没有单独填写规则配置时，沿用靓号池保存的配置，保证“按池生成”不会丢掉排除数字等配置。
		command.RuleConfigJSON = pool.RuleConfigJSON
	}
	candidates, err := generatePrettyDisplayIDCandidates(ruleType, command.RuleConfigJSON)
	if err != nil {
		return userdomain.PrettyDisplayIDGenerationBatch{}, err
	}

	// 候选按规则确定顺序扫描，遇到已有用户 ID、默认短号、当前靓号或池内号码就跳过，不随机重试。
	for _, candidate := range candidates {
		if generated >= command.Count {
			break
		}
		if err := r.assertDisplayUserIDGloballyAvailable(ctx, tx, candidate); err != nil {
			if xerr.IsCode(err, xerr.DisplayUserIDExists) {
				skipped++
				continue
			}
			return userdomain.PrettyDisplayIDGenerationBatch{}, err
		}
		_, err := tx.ExecContext(ctx, `
			INSERT INTO pretty_display_ids (
			  app_code, pretty_id, pool_id, source, display_user_id, status,
			  generated_batch_id, created_by_admin_id, created_at_ms, updated_at_ms
			)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		`, appCode, idgen.New("pretty"), command.PoolID, userdomain.PrettyDisplayIDSourcePool, candidate, userdomain.PrettyDisplayIDStatusAvailable, command.BatchID, command.OperatorAdminID, command.NowMs, command.NowMs)
		if err != nil {
			// 唯一键冲突和显式查重一样计入 skipped，保证并发生成时不会因为个别撞号中断整个批次。
			if xerr.IsCode(shared.MapPrettyDuplicateError(err), xerr.DisplayUserIDPrettyNotAvailable) {
				skipped++
				continue
			}
			return userdomain.PrettyDisplayIDGenerationBatch{}, err
		}
		generated++
	}
	if generated < command.Count {
		// 候选空间被耗尽时也要记录缺口，便于后台看到“请求 1000 个但实际生成不足”的原因。
		shortfall := command.Count - generated
		if shortfall > skipped {
			skipped = shortfall
		}
	}

	// 批次记录和号码记录同事务提交，后台列表能用 batch_id 追溯一次生成实际落了哪些号码。
	_, err = tx.ExecContext(ctx, `
		INSERT INTO pretty_display_id_generation_batches (
		  app_code, batch_id, pool_id, rule_type, rule_config_json, requested_count,
		  generated_count, skipped_conflict_count, status, operator_admin_id,
		  request_id, created_at_ms, updated_at_ms
		)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appCode, command.BatchID, command.PoolID, ruleType, nullableJSON(command.RuleConfigJSON), command.Count, generated, skipped, userdomain.PrettyDisplayIDBatchStatusCompleted, command.OperatorAdminID, command.RequestID, command.NowMs, command.NowMs)
	if err != nil {
		return userdomain.PrettyDisplayIDGenerationBatch{}, err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.PrettyDisplayIDGenerationBatch{}, err
	}
	return userdomain.PrettyDisplayIDGenerationBatch{
		AppCode:              appCode,
		BatchID:              command.BatchID,
		PoolID:               command.PoolID,
		RuleType:             ruleType,
		RuleConfigJSON:       command.RuleConfigJSON,
		RequestedCount:       command.Count,
		GeneratedCount:       generated,
		SkippedConflictCount: skipped,
		Status:               userdomain.PrettyDisplayIDBatchStatusCompleted,
		OperatorAdminID:      command.OperatorAdminID,
		RequestID:            command.RequestID,
		CreatedAtMs:          command.NowMs,
		UpdatedAtMs:          command.NowMs,
	}, nil
}

func (r *Repository) ListPrettyDisplayIDs(ctx context.Context, query userdomain.PrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) {
	appCode := appcode.Normalize(query.AppCode)
	page, pageSize := normalizeIdentityPage(query.Page, query.PageSize)
	where := []string{"pdi.app_code = ?"}
	args := []any{appCode}
	if query.PoolID != "" {
		where = append(where, "pdi.pool_id = ?")
		args = append(args, query.PoolID)
	}
	if query.Source != "" {
		where = append(where, "pdi.source = ?")
		args = append(args, query.Source)
	}
	if query.Status != "" {
		where = append(where, "pdi.status = ?")
		args = append(args, query.Status)
	}
	if query.AssignedUserID > 0 {
		where = append(where, "pdi.assigned_user_id = ?")
		args = append(args, query.AssignedUserID)
	}
	if query.Keyword != "" {
		where = append(where, "(pdi.pretty_id LIKE ? OR pdi.display_user_id LIKE ?)")
		keyword := "%" + query.Keyword + "%"
		args = append(args, keyword, keyword)
	}
	whereSQL := "WHERE " + strings.Join(where, " AND ")

	var total int64
	if err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM pretty_display_ids pdi
		LEFT JOIN pretty_display_id_pools pool
		  ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id
		`+whereSQL, args...).Scan(&total); err != nil {
		return nil, 0, err
	}
	rows, err := r.db.QueryContext(ctx, `
		SELECT `+prettyDisplayIDSelectColumns()+`
		FROM pretty_display_ids pdi
		LEFT JOIN pretty_display_id_pools pool
		  ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id
		`+whereSQL+`
		ORDER BY pdi.created_at_ms DESC, pdi.pretty_id DESC
		LIMIT ? OFFSET ?
	`, append(args, int(pageSize), int((page-1)*pageSize))...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()
	items, err := scanPrettyDisplayIDRows(rows)
	if err != nil {
		return nil, 0, err
	}
	return items, total, rows.Err()
}

func (r *Repository) SetPrettyDisplayIDStatus(ctx context.Context, command userdomain.PrettyDisplayIDStatusCommand) (userdomain.PrettyDisplayID, error) {
	// 状态切换只允许未占用号码，assigned 号码必须通过覆盖、过期或释放链路改变，避免后台直接改坏用户当前展示号。
	result, err := r.db.ExecContext(ctx, `
		UPDATE pretty_display_ids
		SET status = ?, release_reason = ?, updated_at_ms = ?
		WHERE app_code = ? AND pretty_id = ?
		  AND assigned_user_id = 0
		  AND assigned_lease_id = ''
		  AND status IN (?, ?, ?)
	`, command.Status, command.Reason, command.NowMs, appcode.Normalize(command.AppCode), command.PrettyID, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusDisabled, userdomain.PrettyDisplayIDStatusReserved)
	if err != nil {
		return userdomain.PrettyDisplayID{}, err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id status can not be changed")
	}
	return queryPrettyDisplayID(ctx, r.db, appcode.Normalize(command.AppCode), command.PrettyID)
}

func (r *Repository) AdminGrantPrettyDisplayID(ctx context.Context, command userdomain.AdminGrantPrettyDisplayIDCommand) (userdomain.Identity, string, error) {
	// 后台发放可以输入任意合规内容；如果同名旧限时靓号已到期，先懒过期释放后再做全局唯一性校验。
	if err := r.expirePrettyByDisplayUserID(ctx, command.DisplayUserID, command.NowMs, command.RequestID); err != nil {
		return userdomain.Identity{}, "", err
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	defer tx.Rollback()

	// 后台发放不经过号码池，但仍要和用户真实 ID、默认短号、当前展示号、池号和保留号做全局冲突检查。
	if err := r.assertDisplayUserIDGloballyAvailable(ctx, tx, command.DisplayUserID); err != nil {
		return userdomain.Identity{}, "", err
	}
	// 锁目标用户后再插入租约和更新 users，确保同一用户同时被发放或申请池号时只有一个事务成功替换。
	user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID)
	if err == sql.ErrNoRows {
		return userdomain.Identity{}, "", xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return userdomain.Identity{}, "", err
	}

	expiresAtMs := int64(0)
	if command.DurationMs > 0 {
		// duration_ms<=0 在服务层表示长期；只有正数才转换成明确过期时间，供懒过期和定时恢复使用。
		expiresAtMs = command.NowMs + command.DurationMs
	}
	// 自由输入的后台靓号也先写入 pretty_display_ids，并直接标记 assigned，便于列表、审计和后续释放统一处理。
	_, err = tx.ExecContext(ctx, `
		INSERT INTO pretty_display_ids (
		  app_code, pretty_id, pool_id, source, display_user_id, status,
		  assigned_user_id, assigned_lease_id, assigned_at_ms, created_by_admin_id,
		  created_at_ms, updated_at_ms
		)
		VALUES (?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, appcode.Normalize(command.AppCode), command.PrettyID, userdomain.PrettyDisplayIDSourceAdminGrant, command.DisplayUserID, userdomain.PrettyDisplayIDStatusAssigned, command.TargetUserID, command.LeaseID, command.NowMs, command.OperatorAdminID, command.NowMs, command.NowMs)
	if err != nil {
		return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err)
	}

	// 真正覆盖用户当前展示号仍走统一替换事务，确保旧池号释放、旧后台号 released 和默认号 held 行为完全一致。
	identity, err := r.replacePrettyInTx(ctx, tx, user, prettyAssignment{
		PrettyID:       command.PrettyID,
		LeaseID:        command.LeaseID,
		DisplayUserID:  command.DisplayUserID,
		ExpiresAtMs:    expiresAtMs,
		Source:         userdomain.PrettyDisplayIDSourceAdminGrant,
		ChangeType:     "pretty_admin_grant",
		Reason:         command.Reason,
		OperatorUserID: command.OperatorAdminID,
		RequestID:      command.RequestID,
		NowMs:          command.NowMs,
	})
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	if err := tx.Commit(); err != nil {
		return userdomain.Identity{}, "", err
	}
	return identity, command.LeaseID, nil
}

func normalizeIdentityPage(page int32, pageSize int32) (int32, int32) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 20
	}
	if pageSize > 100 {
		pageSize = 100
	}
	return page, pageSize
}

func nullableJSON(value string) any {
	if strings.TrimSpace(value) == "" {
		return nil
	}
	return value
}

type rowScanner interface {
	Scan(dest ...any) error
}

func scanPrettyDisplayIDPool(scanner rowScanner) (userdomain.PrettyDisplayIDPool, error) {
	var pool userdomain.PrettyDisplayIDPool
	err := scanner.Scan(
		&pool.AppCode,
		&pool.PoolID,
		&pool.Name,
		&pool.LevelTrack,
		&pool.MinLevel,
		&pool.MaxLevel,
		&pool.RuleType,
		&pool.RuleConfigJSON,
		&pool.Status,
		&pool.SortOrder,
		&pool.CreatedByAdminID,
		&pool.UpdatedByAdminID,
		&pool.CreatedAtMs,
		&pool.UpdatedAtMs,
	)
	return pool, err
}

func queryPrettyDisplayIDPool(ctx context.Context, q shared.Queryer, appCode string, poolID string) (userdomain.PrettyDisplayIDPool, error) {
	return scanPrettyDisplayIDPool(q.QueryRowContext(ctx, `
		SELECT app_code, pool_id, name, level_track, min_level, max_level, rule_type,
		       COALESCE(CAST(rule_config_json AS CHAR), ''), status, sort_order,
		       created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms
		FROM pretty_display_id_pools
		WHERE app_code = ? AND pool_id = ?
	`, appCode, poolID))
}

func queryPrettyDisplayIDPoolForUpdate(ctx context.Context, tx *sql.Tx, appCode string, poolID string) (userdomain.PrettyDisplayIDPool, error) {
	return scanPrettyDisplayIDPool(tx.QueryRowContext(ctx, `
		SELECT app_code, pool_id, name, level_track, min_level, max_level, rule_type,
		       COALESCE(CAST(rule_config_json AS CHAR), ''), status, sort_order,
		       created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms
		FROM pretty_display_id_pools
		WHERE app_code = ? AND pool_id = ?
		FOR UPDATE
	`, appCode, poolID))
}

func prettyDisplayIDSelectColumns() string {
	return `
		pdi.app_code, pdi.pretty_id, pdi.pool_id, pdi.source, pdi.display_user_id,
		pdi.status, pdi.assigned_user_id, pdi.assigned_lease_id, pdi.assigned_at_ms,
		pdi.released_at_ms, pdi.release_reason, pdi.generated_batch_id,
		pdi.created_by_admin_id, pdi.created_at_ms, pdi.updated_at_ms,
		COALESCE(pool.app_code, ''), COALESCE(pool.pool_id, ''), COALESCE(pool.name, ''),
		COALESCE(pool.level_track, ''), COALESCE(pool.min_level, 0), COALESCE(pool.max_level, 0),
		COALESCE(pool.rule_type, ''), COALESCE(CAST(pool.rule_config_json AS CHAR), ''),
		COALESCE(pool.status, ''), COALESCE(pool.sort_order, 0),
		COALESCE(pool.created_by_admin_id, 0), COALESCE(pool.updated_by_admin_id, 0),
		COALESCE(pool.created_at_ms, 0), COALESCE(pool.updated_at_ms, 0)`
}

func scanPrettyDisplayIDRows(rows *sql.Rows) ([]userdomain.PrettyDisplayID, error) {
	items := make([]userdomain.PrettyDisplayID, 0)
	for rows.Next() {
		item, err := scanPrettyDisplayID(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func scanPrettyDisplayID(scanner rowScanner) (userdomain.PrettyDisplayID, error) {
	var item userdomain.PrettyDisplayID
	err := scanner.Scan(
		&item.AppCode,
		&item.PrettyID,
		&item.PoolID,
		&item.Source,
		&item.DisplayUserID,
		&item.Status,
		&item.AssignedUserID,
		&item.AssignedLeaseID,
		&item.AssignedAtMs,
		&item.ReleasedAtMs,
		&item.ReleaseReason,
		&item.GeneratedBatchID,
		&item.CreatedByAdminID,
		&item.CreatedAtMs,
		&item.UpdatedAtMs,
		&item.Pool.AppCode,
		&item.Pool.PoolID,
		&item.Pool.Name,
		&item.Pool.LevelTrack,
		&item.Pool.MinLevel,
		&item.Pool.MaxLevel,
		&item.Pool.RuleType,
		&item.Pool.RuleConfigJSON,
		&item.Pool.Status,
		&item.Pool.SortOrder,
		&item.Pool.CreatedByAdminID,
		&item.Pool.UpdatedByAdminID,
		&item.Pool.CreatedAtMs,
		&item.Pool.UpdatedAtMs,
	)
	return item, err
}

func queryPrettyDisplayID(ctx context.Context, q shared.Queryer, appCode string, prettyID string) (userdomain.PrettyDisplayID, error) {
	return scanPrettyDisplayID(q.QueryRowContext(ctx, `
		SELECT `+prettyDisplayIDSelectColumns()+`
		FROM pretty_display_ids pdi
		LEFT JOIN pretty_display_id_pools pool
		  ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id
		WHERE pdi.app_code = ? AND pdi.pretty_id = ?
	`, appCode, prettyID))
}

func queryPrettyDisplayIDForUpdate(ctx context.Context, tx *sql.Tx, appCode string, prettyID string) (userdomain.PrettyDisplayID, error) {
	return scanPrettyDisplayID(tx.QueryRowContext(ctx, `
		SELECT `+prettyDisplayIDSelectColumns()+`
		FROM pretty_display_ids pdi
		LEFT JOIN pretty_display_id_pools pool
		  ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id
		WHERE pdi.app_code = ? AND pdi.pretty_id = ?
		FOR UPDATE
	`, appCode, prettyID))
}
