// Package user 定义 user-service 的用户主数据、展示短号和临时靓号领域模型。
package user

import (
	"errors"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"

	invitedomain "hyapp/services/user-service/internal/domain/invite"
)

// Status 是用户主状态，业务服务只应该依赖这里的稳定语义。
type Status string

const (
	// StatusActive 表示用户可以正常登录和参与业务。
	StatusActive Status = "active"
	// StatusDisabled 表示用户被平台禁用。
	StatusDisabled Status = "disabled"
	// StatusBanned 表示用户被封禁。
	StatusBanned Status = "banned"
)

const (
	// RegisterSourceGameRobot 标记后台为全站机器人池创建的资料号。
	RegisterSourceGameRobot = "game_robot"
	// RegisterSourceQuickAccount 标记后台快捷创建的联调或运营账号，不属于 App 自然新增。
	RegisterSourceQuickAccount = "quick_account"
)

// OnboardingStatus 表达用户是否完成 App 最小注册资料。
type OnboardingStatus string

const (
	// OnboardingStatusProfileRequired 表示用户已创建但还不能进入房间、IM、RTC 或付费链路。
	OnboardingStatusProfileRequired OnboardingStatus = "profile_required"
	// OnboardingStatusCompleted 表示用户名、头像、国家和区域快照已经在注册页原子固化。
	OnboardingStatusCompleted OnboardingStatus = "completed"
)

// DisplayUserIDStatus 表达短号历史记录状态。
type DisplayUserIDStatus string

const (
	// DisplayUserIDStatusActive 表示当前可被普通业务解析的短号归属。
	DisplayUserIDStatusActive DisplayUserIDStatus = "active"
	// DisplayUserIDStatusHeld 表示默认短号仍归用户所有，但被临时靓号覆盖。
	DisplayUserIDStatusHeld DisplayUserIDStatus = "held"
	// DisplayUserIDStatusExpired 表示临时靓号租约已经到期。
	DisplayUserIDStatusExpired DisplayUserIDStatus = "expired"
	// DisplayUserIDStatusReleased 表示短号曾经属于用户，但已经释放。
	DisplayUserIDStatusReleased DisplayUserIDStatus = "released"
	// DisplayUserIDStatusReserved 表示短号被系统预留，不能被普通用户占用。
	DisplayUserIDStatusReserved DisplayUserIDStatus = "reserved"
	// DisplayUserIDStatusBlocked 表示短号被运营阻断，不能分配。
	DisplayUserIDStatusBlocked DisplayUserIDStatus = "blocked"
)

// DisplayUserIDKind 表达当前展示号来源。
type DisplayUserIDKind string

const (
	// DisplayUserIDKindDefault 表示当前展示号来自用户永久默认短号。
	DisplayUserIDKindDefault DisplayUserIDKind = "default"
	// DisplayUserIDKindPretty 表示当前展示号来自有效临时靓号租约。
	DisplayUserIDKindPretty DisplayUserIDKind = "pretty"
)

// PrettyLeaseStatus 表达临时靓号租约生命周期。
type PrettyLeaseStatus string

const (
	// PrettyLeaseStatusActive 表示租约当前覆盖用户展示号。
	PrettyLeaseStatusActive PrettyLeaseStatus = "active"
	// PrettyLeaseStatusExpired 表示租约已按到期流程恢复默认短号。
	PrettyLeaseStatusExpired PrettyLeaseStatus = "expired"
	// PrettyLeaseStatusCancelled 表示租约被业务主动取消。
	PrettyLeaseStatusCancelled PrettyLeaseStatus = "cancelled"
	// PrettyLeaseStatusRefunded 表示租约被退款终止。
	PrettyLeaseStatusRefunded PrettyLeaseStatus = "refunded"
)

// displayUserIDPattern 限制短号为 5 到 11 位非 0 开头数字。
var displayUserIDPattern = regexp.MustCompile(`^[1-9][0-9]{4,10}$`)

const (
	// LevelTrackWealth 是财富等级轨道。
	LevelTrackWealth = "wealth"
	// LevelTrackGame 是游戏等级轨道。
	LevelTrackGame = "game"
	// LevelTrackCharm 是魅力等级轨道。
	LevelTrackCharm = "charm"
)

const (
	// PrettyDisplayIDPoolStatusActive 表示靓号池允许 App 用户申请。
	PrettyDisplayIDPoolStatusActive = "active"
	// PrettyDisplayIDPoolStatusDisabled 表示靓号池暂停申请。
	PrettyDisplayIDPoolStatusDisabled = "disabled"
)

const (
	// PrettyDisplayIDStatusAvailable 表示靓号池号码可被申请。
	PrettyDisplayIDStatusAvailable = "available"
	// PrettyDisplayIDStatusAssigned 表示靓号已被用户占用。
	PrettyDisplayIDStatusAssigned = "assigned"
	// PrettyDisplayIDStatusDisabled 表示靓号被后台下架。
	PrettyDisplayIDStatusDisabled = "disabled"
	// PrettyDisplayIDStatusReserved 表示靓号被系统预留。
	PrettyDisplayIDStatusReserved = "reserved"
	// PrettyDisplayIDStatusReleased 表示后台发放靓号已经释放，仅保留历史。
	PrettyDisplayIDStatusReleased = "released"
	// PrettyDisplayIDStatusDeleted 表示靓号被软删除。
	PrettyDisplayIDStatusDeleted = "deleted"
)

const (
	// PrettyDisplayIDSourcePool 表示靓号来自等级池批量生成。
	PrettyDisplayIDSourcePool = "pool"
	// PrettyDisplayIDSourceAdminGrant 表示靓号来自后台直接发放。
	PrettyDisplayIDSourceAdminGrant = "admin_grant"
)

const (
	// PrettyDisplayIDRuleAA 生成两个相同数字。
	PrettyDisplayIDRuleAA = "aa"
	// PrettyDisplayIDRuleAAA 生成三连号。
	PrettyDisplayIDRuleAAA = "aaa"
	// PrettyDisplayIDRuleAAAA 生成四连号。
	PrettyDisplayIDRuleAAAA = "aaaa"
	// PrettyDisplayIDRuleAAAAA 生成五连号。
	PrettyDisplayIDRuleAAAAA = "aaaaa"
	// PrettyDisplayIDRuleAAAAAA 生成六连号。
	PrettyDisplayIDRuleAAAAAA = "aaaaaa"
	// PrettyDisplayIDRuleAAAAAAA 生成七连号。
	PrettyDisplayIDRuleAAAAAAA = "aaaaaaa"
	// PrettyDisplayIDRuleAABB 生成两组对子。
	PrettyDisplayIDRuleAABB = "aabb"
	// PrettyDisplayIDRuleAABBCC 生成三组对子。
	PrettyDisplayIDRuleAABBCC = "aabbcc"
	// PrettyDisplayIDRuleAABBCCDD 生成四组对子。
	PrettyDisplayIDRuleAABBCCDD = "aabbccdd"
	// PrettyDisplayIDRuleABAB 生成两组交替号。
	PrettyDisplayIDRuleABAB = "abab"
	// PrettyDisplayIDRuleABABAB 生成三组交替号。
	PrettyDisplayIDRuleABABAB = "ababab"
	// PrettyDisplayIDRuleABBA 生成四位回文号。
	PrettyDisplayIDRuleABBA = "abba"
	// PrettyDisplayIDRuleABCBA 生成五位回文号。
	PrettyDisplayIDRuleABCBA = "abcba"
	// PrettyDisplayIDRuleABCCBA 生成六位回文号。
	PrettyDisplayIDRuleABCCBA = "abccba"
	// PrettyDisplayIDRuleAAAB 生成三拖一号码。
	PrettyDisplayIDRuleAAAB = "aaab"
	// PrettyDisplayIDRuleAAAAB 生成四拖一号码。
	PrettyDisplayIDRuleAAAAB = "aaaab"
	// PrettyDisplayIDRuleAAAAAB 生成五拖一号码。
	PrettyDisplayIDRuleAAAAAB = "aaaaab"
	// PrettyDisplayIDRuleAAABBB 生成双豹子号码。
	PrettyDisplayIDRuleAAABBB = "aaabbb"
	// PrettyDisplayIDRuleAAAABBBB 生成双炸弹号码。
	PrettyDisplayIDRuleAAAABBBB = "aaaabbbb"
	// PrettyDisplayIDRuleABC 生成三顺号。
	PrettyDisplayIDRuleABC = "abc"
	// PrettyDisplayIDRuleABCD 生成四顺号。
	PrettyDisplayIDRuleABCD = "abcd"
	// PrettyDisplayIDRuleABCDE 生成五顺号。
	PrettyDisplayIDRuleABCDE = "abcde"
	// PrettyDisplayIDRuleABCDEF 生成六顺号。
	PrettyDisplayIDRuleABCDEF = "abcdef"
	// PrettyDisplayIDRuleCBA 生成三位倒顺号。
	PrettyDisplayIDRuleCBA = "cba"
	// PrettyDisplayIDRuleDCBA 生成四位倒顺号。
	PrettyDisplayIDRuleDCBA = "dcba"
	// PrettyDisplayIDRuleEDCBA 生成五位倒顺号。
	PrettyDisplayIDRuleEDCBA = "edcba"
	// PrettyDisplayIDRuleFEDCBA 生成六位倒顺号。
	PrettyDisplayIDRuleFEDCBA = "fedcba"
	// PrettyDisplayIDRuleABCABC 生成顺子重复号。
	PrettyDisplayIDRuleABCABC = "abcabc"
	// PrettyDisplayIDRuleDateYYYYMMDD 生成生日或纪念日八位日期号。
	PrettyDisplayIDRuleDateYYYYMMDD = "date_yyyymmdd"
	// PrettyDisplayIDRuleDateYYMMDD 生成生日或纪念日六位日期号。
	PrettyDisplayIDRuleDateYYMMDD = "date_yymmdd"
	// PrettyDisplayIDRuleLove 生成 520、521、1314 等寓意号码。
	PrettyDisplayIDRuleLove = "love"
	// PrettyDisplayIDRuleCustomValues 按 rule_config_json.values 生成固定候选。
	PrettyDisplayIDRuleCustomValues = "custom_values"
	// PrettyDisplayIDRuleCustomPattern 按 rule_config_json.pattern 生成自定义字母模式。
	PrettyDisplayIDRuleCustomPattern = "custom_pattern"
	// PrettyDisplayIDBatchStatusCompleted 表示批量生成已经完成。
	PrettyDisplayIDBatchStatusCompleted = "completed"
)

// User 是 user-service 的主数据实体。
// 这里只保存注册时形成的用户资料和来源快照，不承载关系链、房间内临时态或连接态。
type User struct {
	// AppCode 是用户所属 App，所有短号、资料、Host 关系都在该 App 内隔离。
	AppCode string
	// UserID 是系统内部不可变用户 ID。
	UserID int64
	// DefaultDisplayUserID 是用户永久默认短号。
	DefaultDisplayUserID string
	// CurrentDisplayUserID 是当前对外展示短号，可能被有效靓号覆盖。
	CurrentDisplayUserID string
	// CurrentDisplayUserIDKind 表示当前展示号来源。
	CurrentDisplayUserIDKind DisplayUserIDKind
	// CurrentDisplayUserIDExpiresAtMs 是当前靓号过期时间，默认短号为 0。
	CurrentDisplayUserIDExpiresAtMs int64
	// PrettyID 是当前 active 靓号记录 ID，没有靓号时为空。
	PrettyID string
	// PrettyDisplayUserID 是当前 active 靓号内容，没有靓号时为空。
	PrettyDisplayUserID string
	// Username 是注册入口传入的展示名快照，后续资料编辑可在专门用例中覆盖。
	Username string
	// Gender 是客户端上报的性别枚举原值，v1 不在服务端推断性别含义。
	Gender string
	// Country 是客户端选择的国家码快照；值必须来自 countries.country_code。
	Country string
	// RegionID 是按 Country 当前 active 映射计算出的物化区域快照，0 表示无区域。
	RegionID int64
	// CountryID 是按 Country 拼出的国家主数据 ID，0 表示历史值未命中国家表。
	CountryID int64
	// CountryName 是按 Country 拼出的国家英文或拼音名称。
	CountryName string
	// CountryDisplayName 是按 Country 拼出的中文展示名。
	CountryDisplayName string
	// ISONumeric 是按 Country 拼出的三位 numeric/M49 字符串，客户端不能当数字解析。
	ISONumeric string
	// PhoneCountryCode 是按 Country 拼出的 E.164 国家呼叫码，可能为空或被多个国家共享。
	PhoneCountryCode string
	// CountryEnabled 表示当前国家是否仍对 App 注册和改国家开放，历史用户可能为 false。
	CountryEnabled bool
	// RegionCode 是按 RegionID 拼出的区域业务编码。
	RegionCode string
	// RegionName 是按 RegionID 拼出的区域展示名。
	RegionName string
	// InviteCode 是注册时使用的邀请码，空值表示未使用邀请关系。
	InviteCode string
	// InviteOverview 是我的页读取的邀请码和计数 read model，不参与用户主状态决策。
	InviteOverview invitedomain.Overview
	// InviteBinding 是注册完成请求本次邀请归因结果，只服务入口响应，不作为关系事实来源。
	InviteBinding invitedomain.Binding
	// RegisterIP 是注册请求携带或 gateway 观察到的注册 IP 快照。
	RegisterIP string
	// RegisterUserAgent 是 gateway 从 HTTP header 观察到的注册 UA 快照。
	RegisterUserAgent string
	// CountryByIP 是服务端根据入口 IP 或可信边缘地域信息得到的国家快照。
	CountryByIP string
	// RegisterDeviceID 是注册时绑定 refresh session 的设备 ID。
	RegisterDeviceID string
	// RegisterDevice 是客户端上报的设备型号或设备描述。
	RegisterDevice string
	// RegisterOSVersion 是注册客户端系统版本。
	RegisterOSVersion string
	// Avatar 是注册入口传入的头像 HTTP(S) URL。
	Avatar string
	// ProfileBgImg 是用户主页信息卡背景图片 URL，属于用户主资料，不属于钱包装扮或 profile_card 资源。
	ProfileBgImg string
	// ContactInfo 是用户级联系方式，Host、BD、Agency owner 和币商等经营身份共同展示同一份用户主资料。
	ContactInfo string
	// WithdrawUSDTTRC20Address 是用户默认提现 USDT-TRC20 地址；提现订单创建时应把该值快照到 wallet-service。
	WithdrawUSDTTRC20Address string
	// BirthDate 使用 yyyy-mm-dd 字符串表达生日，避免时区把日期偏移成前后一天。
	BirthDate string
	// RegisterAppVersion 是注册客户端版本。
	RegisterAppVersion string
	// RegisterBuildNumber 是注册客户端构建号。
	RegisterBuildNumber string
	// RegisterSource 是注册来源，例如渠道、投放或活动入口。
	RegisterSource string
	// RegisterInstallChannel 是注册安装渠道，例如 play_store 或 app_store。
	RegisterInstallChannel string
	// RegisterCampaign 是注册投放活动标识。
	RegisterCampaign string
	// RegisterPlatform 是客户端平台，当前只接受 android 或 ios。
	RegisterPlatform string
	// Language 是客户端语言偏好。
	Language string
	// Timezone 是客户端 IANA 时区，例如 America/Los_Angeles。
	Timezone string
	// ProfileCompleted 表示用户是否已经完成 App 最小注册资料。
	ProfileCompleted bool
	// ProfileCompletedAtMs 是资料完成时间；0 表示从未完成。
	ProfileCompletedAtMs int64
	// OnboardingStatus 是客户端准入状态，首版只使用 profile_required/completed。
	OnboardingStatus OnboardingStatus
	// Status 控制登录和业务可用性。
	Status Status
	// ContentBlacklisted 只屏蔽游戏列表和 Banner 等运营内容；账号仍可登录，房间和账务状态不受影响。
	ContentBlacklisted bool
	// CreatedAtMs 是用户创建时间。
	CreatedAtMs int64
	// UpdatedAtMs 是用户主记录更新时间。
	UpdatedAtMs int64
}

// ContentBlacklistEntry 是 user-service 持有的运营内容屏蔽当前态。
// 用户资料在列表读取时从 users 当前行投影，避免昵称、头像和展示号被固化成过期快照。
type ContentBlacklistEntry struct {
	UserID               int64
	CurrentDisplayUserID string
	DefaultDisplayUserID string
	PrettyID             string
	PrettyDisplayUserID  string
	Username             string
	Avatar               string
	UserStatus           Status
	OperatorAdminID      int64
	Reason               string
	CreatedAtMs          int64
	UpdatedAtMs          int64
}

// RoomBasicUser 是房间首屏热路径使用的最小用户展示资料，不能混入国家、靓号详情或装扮字段。
type RoomBasicUser struct {
	// UserID 是系统内部不可变用户 ID，调用方用它和麦位/房主/当前用户状态关联。
	UserID int64
	// Username 是用户当前基础昵称；为空时客户端按 display_user_id 或 user_id 兜底。
	Username string
	// Avatar 是用户当前基础头像 URL；装扮头像框不属于这个结构。
	Avatar string
	// DisplayUserID 是房间首屏展示短号；过期靓号在 SQL 内兜底为默认短号。
	DisplayUserID string
}

// ProfileStats 是我的页顶部统计 read model，只保存预聚合计数。
type ProfileStats struct {
	// AppCode 是统计所属 App，关系链和礼物统计都必须在 App 内隔离。
	AppCode string
	// UserID 是统计所属用户。
	UserID int64
	// VisitorsCount 是访问过该用户主页的用户计数投影。
	VisitorsCount int64
	// FollowingCount 是该用户关注的人数计数投影。
	FollowingCount int64
	// FollowersCount 是关注该用户的人数计数投影；它与 FollowingCount 的方向相反。
	FollowersCount int64
	// FriendsCount 是该用户好友人数计数投影。
	FriendsCount int64
	// UpdatedAtMs 是这份计数投影最后更新时间。
	UpdatedAtMs int64
}

const (
	// SocialStatusActive 表示关注或好友关系当前有效。
	SocialStatusActive = "active"
	// SocialStatusCancelled 表示关注关系已取消。
	SocialStatusCancelled = "cancelled"
	// SocialStatusDeleted 表示好友关系已删除。
	SocialStatusDeleted = "deleted"
	// FriendApplicationStatusPending 表示好友申请等待对方处理。
	FriendApplicationStatusPending = "pending"
	// FriendApplicationStatusAccepted 表示好友申请已同意。
	FriendApplicationStatusAccepted = "accepted"
	// ReportTargetUser 表示举报目标是单个用户。
	ReportTargetUser = "user"
	// ReportTargetRoom 表示举报目标是单个房间。
	ReportTargetRoom = "room"
	// ReportStatusSubmitted 表示举报单已提交，等待后台治理流程处理。
	ReportStatusSubmitted = "submitted"
)

// ProfileVisitRecord 是访问某个用户主页的去重记录。
// 新的匿名访问不会再产生记录；VisitorIdentityHidden/AnonymousVisitID 仅用于兼容已经落库的历史匿名事实，
// 真实访客 ID 仍只留在 user-service owner 表中，绝不能进入协议转换层。
type ProfileVisitRecord struct {
	VisitorUserID         int64
	TargetUserID          int64
	VisitCount            int64
	LastVisitedAtMs       int64
	VisitorIdentityHidden bool
	AnonymousVisitID      string
}

// FollowRecord 是单向关注关系。
type FollowRecord struct {
	FollowerUserID int64
	FolloweeUserID int64
	FollowedAtMs   int64
}

// FriendRecord 是双向好友关系在单用户视角下的一条投影。
type FriendRecord struct {
	UserID       int64
	FriendUserID int64
	FriendedAtMs int64
}

// FriendApplication 是好友申请事实。
type FriendApplication struct {
	RequesterUserID int64
	TargetUserID    int64
	Status          string
	CreatedAtMs     int64
	UpdatedAtMs     int64
}

// Report 是 App 用户提交的治理举报事实。
type Report struct {
	// AppCode 是举报所属 App，后台治理只在同 App 范围内处理。
	AppCode string
	// ReportID 是举报单不可变业务 ID。
	ReportID string
	// ReporterUserID 是举报发起人，只能来自已认证 token。
	ReporterUserID int64
	// TargetType 区分 user/room，避免靠空字段猜测举报对象。
	TargetType string
	// UserID 是被举报用户 ID；房间举报时为 0。
	UserID int64
	// RoomID 是被举报房间 ID；用户举报时为空。
	RoomID string
	// ReportType 是客户端选择的稳定举报类型枚举。
	ReportType string
	// Reason 是用户补充的举报理由。
	Reason string
	// ImageURLs 是用户上传后的举报图片 URL 快照。
	ImageURLs []string
	// Status 是举报单当前处理状态。
	Status string
	// RequestID 是提交举报时的 gateway 链路追踪 ID。
	RequestID string
	// CreatedAtMs 是举报创建时间，UTC epoch ms。
	CreatedAtMs int64
	// UpdatedAtMs 是举报最后更新时间，UTC epoch ms。
	UpdatedAtMs int64
}

// BusinessUserLookupItem 是带业务场景权限裁剪后的用户查询结果。
// 它只包含选择目标用户所需字段，不能扩展手机号、登录身份或钱包资产。
type BusinessUserLookupItem struct {
	UserID        int64
	DisplayUserID string
	Username      string
	Avatar        string
	Status        Status
	RegionID      int64
	RegionCode    string
	RegionName    string
}

// Identity 是 user_id 与当前 active display_user_id 的绑定投影。
type Identity struct {
	// AppCode 是短号所属 App。
	AppCode string
	// UserID 是短号归属用户。
	UserID int64
	// DisplayUserID 是当前 active 短号。
	DisplayUserID string
	// DefaultDisplayUserID 是永久默认短号。
	DefaultDisplayUserID string
	// DisplayUserIDKind 表示 DisplayUserID 来源。
	DisplayUserIDKind DisplayUserIDKind
	// DisplayUserIDExpiresAtMs 是 active 靓号过期时间。
	DisplayUserIDExpiresAtMs int64
	// PrettyID 是当前 active 靓号记录 ID，没有靓号时为空。
	PrettyID string
	// PrettyDisplayUserID 是当前 active 靓号内容，没有靓号时为空。
	PrettyDisplayUserID string
	// Status 是短号记录状态。
	Status DisplayUserIDStatus
}

// PrettyDisplayIDPool 保存靓号池等级区间和默认生成规则。
type PrettyDisplayIDPool struct {
	AppCode          string
	PoolID           string
	Name             string
	LevelTrack       string
	MinLevel         int32
	MaxLevel         int32
	RuleType         string
	RuleConfigJSON   string
	Status           string
	SortOrder        int32
	CreatedByAdminID int64
	UpdatedByAdminID int64
	CreatedAtMs      int64
	UpdatedAtMs      int64
}

// PrettyDisplayID 保存一个可申请、已占用或后台发放的靓号。
type PrettyDisplayID struct {
	AppCode          string
	PrettyID         string
	PoolID           string
	Source           string
	DisplayUserID    string
	Status           string
	AssignedUserID   int64
	AssignedLeaseID  string
	AssignedAtMs     int64
	ReleasedAtMs     int64
	ReleaseReason    string
	GeneratedBatchID string
	CreatedByAdminID int64
	CreatedAtMs      int64
	UpdatedAtMs      int64
	Pool             PrettyDisplayIDPool
}

// PrettyDisplayIDGenerationBatch 记录一次后台批量生成结果。
type PrettyDisplayIDGenerationBatch struct {
	AppCode              string
	BatchID              string
	PoolID               string
	RuleType             string
	RuleConfigJSON       string
	RequestedCount       int32
	GeneratedCount       int32
	SkippedConflictCount int32
	Status               string
	OperatorAdminID      int64
	RequestID            string
	CreatedAtMs          int64
	UpdatedAtMs          int64
}

// UserLevelProfile 是 activity-service 给靓号池校验使用的轻量等级投影。
type UserLevelProfile struct {
	UserID      int64
	WealthLevel int32
	GameLevel   int32
	CharmLevel  int32
}

// LevelForTrack 按靓号池保存的轨道取出用户当前等级。
func (p UserLevelProfile) LevelForTrack(track string) (int32, bool) {
	switch track {
	case LevelTrackWealth:
		return p.WealthLevel, true
	case LevelTrackGame:
		return p.GameLevel, true
	case LevelTrackCharm:
		return p.CharmLevel, true
	default:
		return 0, false
	}
}

// PrettyDisplayIDPoolQuery 描述后台靓号池分页查询条件。
type PrettyDisplayIDPoolQuery struct {
	AppCode    string
	Status     string
	LevelTrack string
	Page       int32
	PageSize   int32
}

// PrettyDisplayIDQuery 描述后台靓号列表分页查询条件。
type PrettyDisplayIDQuery struct {
	AppCode        string
	PoolID         string
	Source         string
	Status         string
	Keyword        string
	AssignedUserID int64
	Page           int32
	PageSize       int32
}

// AvailablePrettyDisplayIDQuery 描述 App 用户可申请靓号查询条件。
type AvailablePrettyDisplayIDQuery struct {
	AppCode  string
	UserID   int64
	Page     int32
	PageSize int32
	Levels   UserLevelProfile
}

// PrettyDisplayIDPoolCommand 描述创建或更新靓号池的输入。
type PrettyDisplayIDPoolCommand struct {
	AppCode         string
	PoolID          string
	Name            string
	LevelTrack      string
	MinLevel        int32
	MaxLevel        int32
	RuleType        string
	RuleConfigJSON  string
	Status          string
	SortOrder       int32
	OperatorAdminID int64
	NowMs           int64
}

// PrettyDisplayIDGenerateCommand 描述一次后台批量生成请求。
type PrettyDisplayIDGenerateCommand struct {
	AppCode         string
	BatchID         string
	PoolID          string
	RuleType        string
	RuleConfigJSON  string
	Count           int32
	OperatorAdminID int64
	RequestID       string
	NowMs           int64
}

// PrettyDisplayIDStatusCommand 描述后台切换未占用靓号状态的输入。
type PrettyDisplayIDStatusCommand struct {
	AppCode         string
	PrettyID        string
	Status          string
	Reason          string
	OperatorAdminID int64
	NowMs           int64
}

// PrettyDisplayIDRecycleCommand 描述后台强制回收用户当前靓号的输入。
type PrettyDisplayIDRecycleCommand struct {
	AppCode         string
	PrettyID        string
	Reason          string
	OperatorAdminID int64
	RequestID       string
	NowMs           int64
}

// PrettyDisplayIDPoolApplyCommand 描述 App 靓号池申请事务输入。
type PrettyDisplayIDPoolApplyCommand struct {
	AppCode   string
	UserID    int64
	PrettyID  string
	LeaseID   string
	Levels    UserLevelProfile
	RequestID string
	NowMs     int64
}

// AdminGrantPrettyDisplayIDCommand 描述后台直接发放靓号事务输入。
type AdminGrantPrettyDisplayIDCommand struct {
	AppCode         string
	PrettyID        string
	LeaseID         string
	TargetUserID    int64
	DisplayUserID   string
	DurationMs      int64
	Reason          string
	OperatorAdminID int64
	RequestID       string
	NowMs           int64
}

// DisplayUserIDChangeCommand 描述短号变更的事务输入。
type DisplayUserIDChangeCommand struct {
	// AppCode 是本次短号变更所属 App。
	AppCode string
	// UserID 是要修改默认短号的用户。
	UserID int64
	// NewDisplayUserID 是新的默认短号。
	NewDisplayUserID string
	// Reason 是审计原因。
	Reason string
	// OperatorUserID 是操作者，用户自助修改时等于 UserID。
	OperatorUserID int64
	// RequestID 是入口请求 ID，写入变更日志。
	RequestID string
	// ChangedAtMs 是本次变更时间。
	ChangedAtMs int64
	// CooldownMs 是默认短号修改冷却期。
	CooldownMs int64
}

// ProfileUpdateCommand 描述用户基础资料自助修改。
type ProfileUpdateCommand struct {
	// AppCode 是资料修改所属 App。
	AppCode string
	// UserID 是要修改资料的用户。
	UserID int64
	// Username 非 nil 时覆盖用户名；nil 表示本次不修改。
	Username *string
	// Avatar 非 nil 时覆盖头像地址；nil 表示本次不修改。
	Avatar *string
	// AvatarUploadID 非空时必须与 Avatar 指向同一条 active 上传记录；repository 在资料事务内单次消费。
	AvatarUploadID string
	// Gender 非 nil 时覆盖性别枚举原值；nil 表示本次不修改。
	Gender *string
	// BirthDate 非 nil 时覆盖生日 yyyy-mm-dd；空字符串表示清空生日。
	BirthDate *string
	// UpdatedAtMs 是本次修改时间。
	UpdatedAtMs int64
}

// AvatarMedia 是 user-service 保存的头像原始媒体事实。所有字段都来自 gateway 对实际字节的解析，
// 后续链路不得再根据 URL 后缀猜测格式或动画状态。
type AvatarMedia struct {
	URL         string
	ObjectKey   string
	ContentType string
	Format      string
	SizeBytes   int64
	Width       int32
	Height      int32
	Animated    bool
	FrameCount  int32
	DurationMS  int64
	SHA256      string
	Status      string
}

// AvatarUploadRegistration 是 COS 写入前由 user owner 持久化的授权事实。
// UploadID 同时是资料更新的单次消费凭证，必须按 app_code + user_id 隔离。
type AvatarUploadRegistration struct {
	AppCode            string
	UserID             int64
	UploadID           string
	ClientCommandID    string
	MediaPayloadSHA256 string
	ExpectedObjectKey  string
	ObjectURL          string
	Status             string
	Media              AvatarMedia
	CreatedAtMS        int64
	UpdatedAtMS        int64
}

var (
	// ErrAvatarUploadCommandConflict 表示同一上传命令已绑定另一份媒体载荷。
	ErrAvatarUploadCommandConflict = errors.New("avatar upload command payload conflict")
	// ErrAvatarUploadNotConsumable 表示凭证不存在、归属不符、未完成或已被另一轮资料更新消费。
	ErrAvatarUploadNotConsumable = errors.New("avatar upload is not consumable")
)

// ProfileBackgroundUpdateCommand 描述用户个人信息页背景图的专用修改。
type ProfileBackgroundUpdateCommand struct {
	// AppCode 是资料修改所属 App。
	AppCode string
	// UserID 是要修改背景图的用户，只能来自当前登录态。
	UserID int64
	// ProfileBgImg 是对象存储上传后返回的 HTTP(S) 背景图 URL。
	ProfileBgImg string
	// UpdatedAtMs 是本次修改时间。
	UpdatedAtMs int64
}

// ProfileContactUpdateCommand 描述用户级联系方式的专用修改。
type ProfileContactUpdateCommand struct {
	// AppCode 是资料修改所属 App。
	AppCode string
	// UserID 是要修改联系方式的用户，只能来自当前登录态或后台已解析出的目标用户。
	UserID int64
	// ContactInfo 是用于后台和经营中心展示的联系方式；空字符串表示清空。
	ContactInfo string
	// UpdatedAtMs 是本次修改时间。
	UpdatedAtMs int64
}

// WithdrawAddressUpdateCommand 描述用户默认提现地址的专用修改。
type WithdrawAddressUpdateCommand struct {
	// AppCode 是地址所属 App，提现资料必须和用户主资料保持同租户隔离。
	AppCode string
	// UserID 是要修改默认提现地址的用户，只能来自当前登录态或后台已解析出的目标用户。
	UserID int64
	// USDTTRC20Address 是用户收款用 TRC20 地址；空字符串表示清空默认地址。
	USDTTRC20Address string
	// UpdatedAtMs 是本次修改时间。
	UpdatedAtMs int64
}

// CompleteOnboardingCommand 描述注册页一次性补齐资料的事务输入。
type CompleteOnboardingCommand struct {
	// AppCode 是注册资料固化所属 App。
	AppCode string
	// UserID 是已三方登录创建出的系统用户。
	UserID int64
	// Username 是注册页最终昵称，必填且已校验长度。
	Username string
	// Avatar 是注册页最终头像 URL，必填且已校验为 HTTP(S) 绝对地址。
	Avatar string
	// Gender 是注册页最终性别枚举原值，必填且已校验长度。
	Gender string
	// Country 是 active countries.country_code 中的规范国家码。
	Country string
	// RegionID 是按 Country 当前 active 映射解析出的区域快照，0 表示 GLOBAL 兜底桶。
	RegionID int64
	// InviteCode 是注册页可选的邀请码；非空时 repository 必须解析并绑定关系。
	InviteCode string
	// RequestID 是入口请求 ID，写入邀请关系和 outbox 便于排查。
	RequestID string
	// CompletedAtMs 是资料完成时间，同时作为 updated_at_ms。
	CompletedAtMs int64
}

// BindInviteReferrerCommand 描述资料完成后的邀请码绑定事务。
type BindInviteReferrerCommand struct {
	// AppCode 是邀请关系所属 App，必须和双方用户及邀请码保持一致。
	AppCode string
	// InvitedUserID 是当前登录用户，只能绑定一次邀请人。
	InvitedUserID int64
	// InviteCode 是用户输入的邀请码，服务端在事务内解析归属并绑定。
	InviteCode string
	// RequestID 是入口请求 ID，写入邀请关系和 outbox 便于追踪。
	RequestID string
	// BoundAtMs 是绑定事务时间，同时作为用户快照更新时间。
	BoundAtMs int64
}

// CountryChangeCommand 描述用户国家修改事务。
type CountryChangeCommand struct {
	// AppCode 是国家修改所属 App。
	AppCode string
	// UserID 是要修改国家的用户。
	UserID int64
	// NewCountry 是新的国家或地区。
	NewCountry string
	// NewRegionID 是按 NewCountry 解析出的区域快照，0 表示 GLOBAL 兜底桶。
	NewRegionID int64
	// Source 标记本次修改来自 App 自助、后台覆盖或区域重建任务，供下游 owner service 判定当前读模型来源。
	Source string
	// RequestID 是入口请求 ID，写入国家修改日志。
	RequestID string
	// ChangedAtMs 是本次修改时间。
	ChangedAtMs int64
	// CooldownMs 是可选冷却窗口；为 0 时不限制连续修改，保留字段用于历史兼容和定向测试。
	CooldownMs int64
}

// CountryChangeLog 是用户国家修改审计记录。
type CountryChangeLog struct {
	// AppCode 是审计记录所属 App。
	AppCode string
	// UserID 是修改国家的用户。
	UserID int64
	// OldCountry 是修改前国家，空值表示历史未设置。
	OldCountry string
	// NewCountry 是修改后国家。
	NewCountry string
	// RequestID 是入口请求 ID。
	RequestID string
	// ChangedAtMs 是修改时间。
	ChangedAtMs int64
}

// PushTokenStatus 表达用户设备推送 token 是否仍可用于离线触达。
type PushTokenStatus string

const (
	// PushTokenStatusActive 表示该 token 当前可用于系统/活动消息离线推送。
	PushTokenStatusActive PushTokenStatus = "active"
	// PushTokenStatusInactive 表示该 token 已被用户登出、关闭通知或重新绑定替换。
	PushTokenStatusInactive PushTokenStatus = "inactive"
)

// PushTokenCommand 描述一次设备 push token 绑定。
type PushTokenCommand struct {
	// AppCode 是 token 所属 App，避免不同包之间串推送。
	AppCode string
	// UserID 是当前登录用户，必须由 gateway 鉴权后传入。
	UserID int64
	// DeviceID 是 App 安装级稳定 ID。
	DeviceID string
	// PushToken 是系统推送服务返回的 token。
	PushToken string
	// Provider 是推送提供方，例如 fcm 或 apns。
	Provider string
	// Platform 是客户端平台，当前只接受 android/ios。
	Platform string
	// AppVersion 是上报 token 时的 App 版本快照。
	AppVersion string
	// Language 是用户当前客户端语言偏好。
	Language string
	// Timezone 是用户当前 IANA 时区。
	Timezone string
	// RequestID 是入口请求 ID，便于排查重复绑定。
	RequestID string
	// UpdatedAtMs 是绑定事务时间。
	UpdatedAtMs int64
}

// DeletePushTokenCommand 描述一次设备 push token 失效。
type DeletePushTokenCommand struct {
	AppCode     string
	UserID      int64
	DeviceID    string
	PushToken   string
	RequestID   string
	UpdatedAtMs int64
}

// PrettyDisplayUserIDLease 是临时靓号覆盖关系的事实记录。
type PrettyDisplayUserIDLease struct {
	// AppCode 是租约所属 App。
	AppCode string
	// LeaseID 是临时靓号租约主键。
	LeaseID string
	// DisplayUserID 是租约覆盖的靓号。
	DisplayUserID string
	// UserID 是租约所属用户。
	UserID int64
	// Status 是租约生命周期状态。
	Status PrettyLeaseStatus
	// StartsAtMs 是租约开始时间。
	StartsAtMs int64
	// ExpiresAtMs 是租约过期时间。
	ExpiresAtMs int64
	// PaymentReceiptID 是外部支付回执。
	PaymentReceiptID string
	// Source 标识租约来源，例如 payment。
	Source string
	// CreatedAtMs 是租约创建时间。
	CreatedAtMs int64
	// UpdatedAtMs 是租约更新时间。
	UpdatedAtMs int64
}

// PrettyDisplayUserIDCommand 描述申请临时靓号的事务输入。
type PrettyDisplayUserIDCommand struct {
	// AppCode 是靓号申请所属 App。
	AppCode string
	// LeaseID 是本次申请生成的租约 ID。
	LeaseID string
	// UserID 是申请用户。
	UserID int64
	// PrettyDisplayID 是申请覆盖的靓号。
	PrettyDisplayID string
	// LeaseDurationMs 是租期长度。
	LeaseDurationMs int64
	// PaymentReceiptID 是已完成支付的账务回执。
	PaymentReceiptID string
	// Source 是申请来源。
	Source string
	// RequestID 是入口请求 ID。
	RequestID string
	// OperatorUserID 是操作者。
	OperatorUserID int64
	// NowMs 是事务时间。
	NowMs int64
}

// ExpirePrettyDisplayUserIDCommand 描述靓号过期恢复的事务输入。
type ExpirePrettyDisplayUserIDCommand struct {
	// AppCode 是过期恢复所属 App。
	AppCode string
	// UserID 是需要恢复默认短号的用户。
	UserID int64
	// LeaseID 可选；传入时必须匹配当前 active lease。
	LeaseID string
	// RequestID 是触发过期的请求或任务 ID。
	RequestID string
	// NowMs 是过期事务时间。
	NowMs int64
}

// CanLogin 判断用户状态是否允许签发新会话。
func (u User) CanLogin() bool {
	// active 只代表用户主状态可用；全站机器人虽然复用用户资料和短号展示，但它不是可登录主体，
	// 所有密码、三方、refresh 和 access 续签入口都必须在签发 session 前复用这条判断统一阻断。
	return u.Status == StatusActive && !IsGameRobotRegisterSource(u.RegisterSource)
}

// IsGameRobotRegisterSource 判断注册来源是否是全站机器人池账号。
func IsGameRobotRegisterSource(source string) bool {
	return strings.EqualFold(strings.TrimSpace(source), RegisterSourceGameRobot)
}

// ExcludedFromRegistrationStats 判断账号来源是否应该排除在自然新增、注册 cohort 和留存统计之外。
func ExcludedFromRegistrationStats(source string) bool {
	normalized := strings.ToLower(strings.TrimSpace(source))
	return normalized == RegisterSourceGameRobot || normalized == RegisterSourceQuickAccount
}

// DisplayUserIDExpired 判断当前展示号快照是否已经因为靓号到期而失效。
func (u User) DisplayUserIDExpired(nowMs int64) bool {
	// 只有 pretty 展示号有过期语义，默认短号永不过期。
	return u.CurrentDisplayUserIDKind == DisplayUserIDKindPretty && u.CurrentDisplayUserIDExpiresAtMs > 0 && u.CurrentDisplayUserIDExpiresAtMs <= nowMs
}

// EffectiveIdentity 把用户当前展示号快照转换成对外身份投影。
func (u User) EffectiveIdentity() Identity {
	// Identity 是对外短号投影，不暴露 users 表内部状态之外的资料字段。
	return Identity{
		AppCode:                  u.AppCode,
		UserID:                   u.UserID,
		DisplayUserID:            u.CurrentDisplayUserID,
		DefaultDisplayUserID:     u.DefaultDisplayUserID,
		DisplayUserIDKind:        u.CurrentDisplayUserIDKind,
		DisplayUserIDExpiresAtMs: u.CurrentDisplayUserIDExpiresAtMs,
		PrettyID:                 u.PrettyID,
		PrettyDisplayUserID:      u.PrettyDisplayUserID,
		Status:                   DisplayUserIDStatusActive,
	}
}

// ValidDisplayUserID 校验短号格式。
func ValidDisplayUserID(value string) bool {
	// 格式校验只判断形态，唯一性和可用性由 repository 事务决定。
	return displayUserIDPattern.MatchString(value)
}

// ValidResolvableDisplayUserID 校验客户端或后台传入的“可解析展示号”。
func ValidResolvableDisplayUserID(value string) bool {
	if ValidDisplayUserID(value) {
		// 默认短号仍然走原有纯数字规则，保证注册、换号和历史默认号解析行为不变。
		return true
	}
	// 解析和登录入口必须允许后台发放的靓号；写入默认短号仍然只能调用 ValidDisplayUserID。
	return ValidAdminPrettyDisplayUserID(value)
}

// ValidAdminPrettyDisplayUserID 校验后台直接发放的靓号内容。
func ValidAdminPrettyDisplayUserID(value string) bool {
	// 靓号是运营展示文本而非登录凭据，必须接受 Unicode 字体、符号和 emoji；仍拒绝空值、首尾空白及控制字符，避免换行等不可见控制码污染列表、日志和协议载荷。
	if value == "" || strings.TrimSpace(value) != value {
		return false
	}
	if utf8.RuneCountInString(value) > 64 {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) {
			return false
		}
	}
	return true
}

// ValidPrettyDisplayIDPoolTrack 校验靓号池等级轨道。
func ValidPrettyDisplayIDPoolTrack(track string) bool {
	return track == LevelTrackWealth || track == LevelTrackGame || track == LevelTrackCharm
}

// ValidPrettyDisplayIDPoolStatus 校验靓号池状态。
func ValidPrettyDisplayIDPoolStatus(status string) bool {
	return status == PrettyDisplayIDPoolStatusActive || status == PrettyDisplayIDPoolStatusDisabled
}

// ValidPrettyDisplayIDRuleType 校验后台批量生成支持的靓号规则。
func ValidPrettyDisplayIDRuleType(ruleType string) bool {
	// 规则类型只表达候选生成方式；具体数量、冲突过滤和 rule_config_json 解析由 service/repository 继续处理。
	switch ruleType {
	case PrettyDisplayIDRuleAA,
		PrettyDisplayIDRuleAAA,
		PrettyDisplayIDRuleAAAA,
		PrettyDisplayIDRuleAAAAA,
		PrettyDisplayIDRuleAAAAAA,
		PrettyDisplayIDRuleAAAAAAA,
		PrettyDisplayIDRuleAABB,
		PrettyDisplayIDRuleAABBCC,
		PrettyDisplayIDRuleAABBCCDD,
		PrettyDisplayIDRuleABAB,
		PrettyDisplayIDRuleABABAB,
		PrettyDisplayIDRuleABBA,
		PrettyDisplayIDRuleABCBA,
		PrettyDisplayIDRuleABCCBA,
		PrettyDisplayIDRuleAAAB,
		PrettyDisplayIDRuleAAAAB,
		PrettyDisplayIDRuleAAAAAB,
		PrettyDisplayIDRuleAAABBB,
		PrettyDisplayIDRuleAAAABBBB,
		PrettyDisplayIDRuleABC,
		PrettyDisplayIDRuleABCD,
		PrettyDisplayIDRuleABCDE,
		PrettyDisplayIDRuleABCDEF,
		PrettyDisplayIDRuleCBA,
		PrettyDisplayIDRuleDCBA,
		PrettyDisplayIDRuleEDCBA,
		PrettyDisplayIDRuleFEDCBA,
		PrettyDisplayIDRuleABCABC,
		PrettyDisplayIDRuleDateYYYYMMDD,
		PrettyDisplayIDRuleDateYYMMDD,
		PrettyDisplayIDRuleLove,
		PrettyDisplayIDRuleCustomValues,
		PrettyDisplayIDRuleCustomPattern:
		return true
	default:
		return false
	}
}

// ValidPrettyDisplayIDStatusChange 校验后台允许切换的未占用靓号状态。
func ValidPrettyDisplayIDStatusChange(status string) bool {
	return status == PrettyDisplayIDStatusAvailable || status == PrettyDisplayIDStatusDisabled || status == PrettyDisplayIDStatusReserved
}
