package user

import (
	"context"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

// ApplyPrettyDisplayUserID 申请临时靓号并让它在租期内覆盖当前展示号。
func (s *Service) ApplyPrettyDisplayUserID(ctx context.Context, userID int64, prettyDisplayUserID string, leaseDurationMs int64, paymentReceiptID string, requestID string) (userdomain.Identity, string, error) {
	if userID <= 0 {
		// 靓号租约必须绑定明确用户。
		return userdomain.Identity{}, "", xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if !userdomain.ValidDisplayUserID(prettyDisplayUserID) {
		// 靓号也复用 display_user_id 格式规则。
		return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDInvalid, "display_user_id format is invalid")
	}
	if leaseDurationMs <= 0 {
		// 没有正租期无法计算过期恢复时间。
		return userdomain.Identity{}, "", xerr.New(xerr.InvalidArgument, "lease_duration_ms is required")
	}
	if paymentReceiptID == "" {
		// v1 要求先完成支付再申请靓号，避免 user-service 自己处理账务。
		return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPaymentRequired, "payment receipt is required")
	}
	if s.identityRepository == nil {
		// 靓号涉及多表事务，必须由 identity repository 完成。
		return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "identity repository is not configured")
	}

	// LeaseID 在服务层生成，repository 只负责原子保存和冲突判断。
	nowMs := s.now().UnixMilli()
	return s.identityRepository.ApplyPrettyDisplayUserID(ctx, userdomain.PrettyDisplayUserIDCommand{
		AppCode:          appcode.FromContext(ctx),
		LeaseID:          idgen.New("pdu_lease"),
		UserID:           userID,
		PrettyDisplayID:  prettyDisplayUserID,
		LeaseDurationMs:  leaseDurationMs,
		PaymentReceiptID: paymentReceiptID,
		Source:           "payment",
		RequestID:        requestID,
		OperatorUserID:   userID,
		NowMs:            nowMs,
	})
}

// ExpirePrettyDisplayUserID 主动触发靓号过期恢复；定时任务和懒过期应复用同一事务。
func (s *Service) ExpirePrettyDisplayUserID(ctx context.Context, userID int64, leaseID string, requestID string) (userdomain.Identity, error) {
	if userID <= 0 {
		// 过期恢复需要明确用户，lease_id 只是可选校验条件。
		return userdomain.Identity{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if s.identityRepository == nil {
		// 没有 identity repository 无法恢复默认短号。
		return userdomain.Identity{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}

	return s.identityRepository.ExpirePrettyDisplayUserID(ctx, userdomain.ExpirePrettyDisplayUserIDCommand{
		AppCode:   appcode.FromContext(ctx),
		UserID:    userID,
		LeaseID:   leaseID,
		RequestID: requestID,
		NowMs:     s.now().UnixMilli(),
	})
}

func (s *Service) refreshExpiredUser(ctx context.Context, user userdomain.User, requestID string) (userdomain.User, error) {
	nowMs := s.now().UnixMilli()
	if !user.DisplayUserIDExpired(nowMs) {
		// 当前展示号仍有效时直接返回用户快照。
		return user, nil
	}

	if s.identityRepository == nil {
		// 发现过期但无法恢复时不能继续返回过期靓号。
		return userdomain.User{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}

	// 过期恢复必须走 identity repository 事务，同步更新 users 和短号记录。
	if _, err := s.identityRepository.ExpirePrettyDisplayUserID(ctx, userdomain.ExpirePrettyDisplayUserIDCommand{
		AppCode:   appcode.FromContext(ctx),
		UserID:    user.UserID,
		RequestID: requestID,
		NowMs:     nowMs,
	}); err != nil {
		return userdomain.User{}, err
	}

	if s.userRepository == nil {
		// 恢复后需要重新读取 users 快照，确保返回的展示号来自持久化结果。
		return userdomain.User{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}

	return s.userRepository.GetUser(ctx, user.UserID)
}

// ListAvailablePrettyDisplayIDs 返回当前用户等级允许申请的靓号池号码。
func (s *Service) ListAvailablePrettyDisplayIDs(ctx context.Context, userID int64, page int32, pageSize int32, requestID string) ([]userdomain.PrettyDisplayID, int64, error) {
	if userID <= 0 {
		// 可申请列表必须绑定当前登录用户，避免客户端自报等级或轨道。
		return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if s.identityRepository == nil {
		// 靓号池和短号唯一性由 identity repository 统一持久化。
		return nil, 0, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	if s.levelProfileReader == nil {
		// 没有等级投影时不能返回可申请列表，否则会绕过等级区间限制。
		return nil, 0, xerr.New(xerr.Unavailable, "level profile reader is not configured")
	}

	levels, err := s.levelProfileReader.GetUserLevelProfile(ctx, appcode.FromContext(ctx), userID, requestID)
	if err != nil {
		return nil, 0, err
	}
	return s.identityRepository.ListAvailablePrettyDisplayIDs(ctx, userdomain.AvailablePrettyDisplayIDQuery{
		AppCode:  appcode.FromContext(ctx),
		UserID:   userID,
		Page:     normalizePage(page),
		PageSize: normalizePageSize(pageSize),
		Levels:   levels,
	})
}

// ApplyPrettyDisplayIDFromPool 申请靓号池号码，成功后长期覆盖当前展示号。
func (s *Service) ApplyPrettyDisplayIDFromPool(ctx context.Context, userID int64, prettyID string, requestID string) (userdomain.Identity, string, error) {
	prettyID = strings.TrimSpace(prettyID)
	if userID <= 0 || prettyID == "" {
		// 靓号池申请只接受 pretty_id，不接受客户端自报号码、等级或租期。
		return userdomain.Identity{}, "", xerr.New(xerr.InvalidArgument, "pretty_id is required")
	}
	if s.identityRepository == nil {
		return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	if s.levelProfileReader == nil {
		return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "level profile reader is not configured")
	}

	levels, err := s.levelProfileReader.GetUserLevelProfile(ctx, appcode.FromContext(ctx), userID, requestID)
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	leaseID := idgen.New("pdu_lease")
	identity, err := s.identityRepository.ApplyPrettyDisplayIDFromPool(ctx, userdomain.PrettyDisplayIDPoolApplyCommand{
		AppCode:   appcode.FromContext(ctx),
		UserID:    userID,
		PrettyID:  prettyID,
		LeaseID:   leaseID,
		Levels:    levels,
		RequestID: requestID,
		NowMs:     s.now().UnixMilli(),
	})
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	return identity, leaseID, nil
}

// ListPrettyDisplayIDPools 返回后台靓号池分页列表。
func (s *Service) ListPrettyDisplayIDPools(ctx context.Context, status string, levelTrack string, page int32, pageSize int32) ([]userdomain.PrettyDisplayIDPool, int64, error) {
	if s.identityRepository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 后台列表只做参数归一化，不在服务层补默认筛选；空 status/level_track 表示查询全部。
	return s.identityRepository.ListPrettyDisplayIDPools(ctx, userdomain.PrettyDisplayIDPoolQuery{
		AppCode:    appcode.FromContext(ctx),
		Status:     strings.TrimSpace(status),
		LevelTrack: strings.TrimSpace(levelTrack),
		Page:       normalizePage(page),
		PageSize:   normalizePageSize(pageSize),
	})
}

// CreatePrettyDisplayIDPool 创建后台靓号池配置。
func (s *Service) CreatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) {
	if s.identityRepository == nil {
		return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 池 ID、应用和时间由服务端生成，后台只提交业务配置和操作者，避免跨应用或伪造创建时间。
	command.AppCode = appcode.FromContext(ctx)
	command.PoolID = idgen.New("pretty_pool")
	command.NowMs = s.now().UnixMilli()
	normalizePrettyDisplayIDPoolCommand(&command)
	// 创建和更新共用同一个校验函数，保证等级区间、轨道、状态和规则类型行为一致。
	if err := validatePrettyDisplayIDPoolCommand(command); err != nil {
		return userdomain.PrettyDisplayIDPool{}, err
	}
	return s.identityRepository.CreatePrettyDisplayIDPool(ctx, command)
}

// UpdatePrettyDisplayIDPool 更新后台靓号池配置。
func (s *Service) UpdatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) {
	if s.identityRepository == nil {
		return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 更新时 pool_id 来自路径，其他字段来自 body；统一 trim 后再校验，避免空格绕过必填判断。
	command.AppCode = appcode.FromContext(ctx)
	command.PoolID = strings.TrimSpace(command.PoolID)
	command.NowMs = s.now().UnixMilli()
	normalizePrettyDisplayIDPoolCommand(&command)
	if command.PoolID == "" {
		return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.InvalidArgument, "pool_id is required")
	}
	if err := validatePrettyDisplayIDPoolCommand(command); err != nil {
		return userdomain.PrettyDisplayIDPool{}, err
	}
	return s.identityRepository.UpdatePrettyDisplayIDPool(ctx, command)
}

// GeneratePrettyDisplayIDs 按靓号池规则批量生成号码。
func (s *Service) GeneratePrettyDisplayIDs(ctx context.Context, command userdomain.PrettyDisplayIDGenerateCommand) (userdomain.PrettyDisplayIDGenerationBatch, error) {
	if s.identityRepository == nil {
		return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 批量生成由服务端生成 batch_id 和当前时间，客户端只能选择池、规则和数量，审计操作者来自后台登录态。
	command.AppCode = appcode.FromContext(ctx)
	command.PoolID = strings.TrimSpace(command.PoolID)
	command.RuleType = strings.TrimSpace(command.RuleType)
	command.RuleConfigJSON = strings.TrimSpace(command.RuleConfigJSON)
	command.BatchID = idgen.New("pretty_batch")
	command.NowMs = s.now().UnixMilli()
	if command.PoolID == "" || command.Count <= 0 || command.OperatorAdminID <= 0 {
		return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.InvalidArgument, "generate pretty display ids argument is invalid")
	}
	if command.RuleType == "" {
		// 空规则按历史默认 AABBCC 处理，旧后台和旧池配置不需要补字段也能继续生成。
		command.RuleType = userdomain.PrettyDisplayIDRuleAABBCC
	}
	if !userdomain.ValidPrettyDisplayIDRuleType(command.RuleType) {
		return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.InvalidArgument, "rule_type is invalid")
	}
	return s.identityRepository.GeneratePrettyDisplayIDs(ctx, command)
}

// ListPrettyDisplayIDs 返回后台靓号分页列表。
func (s *Service) ListPrettyDisplayIDs(ctx context.Context, query userdomain.PrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) {
	if s.identityRepository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 查询条件只做 trim 和分页收敛，来源、状态、池 ID 的合法性由 repository SQL 作为精确筛选处理。
	query.AppCode = appcode.FromContext(ctx)
	query.Page = normalizePage(query.Page)
	query.PageSize = normalizePageSize(query.PageSize)
	query.PoolID = strings.TrimSpace(query.PoolID)
	query.Source = strings.TrimSpace(query.Source)
	query.Status = strings.TrimSpace(query.Status)
	query.Keyword = strings.TrimSpace(query.Keyword)
	return s.identityRepository.ListPrettyDisplayIDs(ctx, query)
}

// SetPrettyDisplayIDStatus 切换未占用靓号的状态。
func (s *Service) SetPrettyDisplayIDStatus(ctx context.Context, command userdomain.PrettyDisplayIDStatusCommand) (userdomain.PrettyDisplayID, error) {
	if s.identityRepository == nil {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 状态修改不接收 display_user_id，必须以 pretty_id 定位池表记录，防止同内容历史记录被误改。
	command.AppCode = appcode.FromContext(ctx)
	command.PrettyID = strings.TrimSpace(command.PrettyID)
	command.Status = strings.TrimSpace(command.Status)
	command.Reason = strings.TrimSpace(command.Reason)
	command.NowMs = s.now().UnixMilli()
	if command.PrettyID == "" || command.OperatorAdminID <= 0 || !userdomain.ValidPrettyDisplayIDStatusChange(command.Status) {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.InvalidArgument, "pretty display id status argument is invalid")
	}
	return s.identityRepository.SetPrettyDisplayIDStatus(ctx, command)
}

// RecyclePrettyDisplayID 后台强制回收用户正在占用的靓号，并恢复该用户默认短号。
func (s *Service) RecyclePrettyDisplayID(ctx context.Context, command userdomain.PrettyDisplayIDRecycleCommand) (userdomain.PrettyDisplayID, error) {
	if s.identityRepository == nil {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 回收是后台写操作，只接受 pretty_id 定位当前占用记录，用户、lease 和来源都在 repository 事务内重新读取校验。
	command.AppCode = appcode.FromContext(ctx)
	command.PrettyID = strings.TrimSpace(command.PrettyID)
	command.Reason = strings.TrimSpace(command.Reason)
	command.NowMs = s.now().UnixMilli()
	if command.PrettyID == "" || command.OperatorAdminID <= 0 {
		return userdomain.PrettyDisplayID{}, xerr.New(xerr.InvalidArgument, "pretty display id recycle argument is invalid")
	}
	return s.identityRepository.RecyclePrettyDisplayID(ctx, command)
}

// AdminGrantPrettyDisplayID 后台直接发放靓号。
func (s *Service) AdminGrantPrettyDisplayID(ctx context.Context, command userdomain.AdminGrantPrettyDisplayIDCommand) (userdomain.Identity, string, error) {
	if s.identityRepository == nil {
		return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "identity repository is not configured")
	}
	// 后台发放没有池限制和等级限制，但 pretty_id、lease_id、应用和时间仍由服务端生成，保证审计链完整。
	command.AppCode = appcode.FromContext(ctx)
	command.PrettyID = idgen.New("pretty")
	command.LeaseID = idgen.New("pdu_lease")
	command.DisplayUserID = strings.TrimSpace(command.DisplayUserID)
	command.Reason = strings.TrimSpace(command.Reason)
	command.NowMs = s.now().UnixMilli()
	// 靓号内容允许 Unicode 字母、数字和组合标记；target/admin 必须来自可信登录态或明确目标用户。
	if command.TargetUserID <= 0 || command.OperatorAdminID <= 0 || !userdomain.ValidAdminPrettyDisplayUserID(command.DisplayUserID) {
		return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDInvalid, "pretty display_user_id format is invalid")
	}
	identity, leaseID, err := s.identityRepository.AdminGrantPrettyDisplayID(ctx, command)
	if err != nil {
		return userdomain.Identity{}, "", err
	}
	return identity, leaseID, nil
}

func validatePrettyDisplayIDPoolCommand(command userdomain.PrettyDisplayIDPoolCommand) error {
	if command.Name == "" || command.MinLevel < 0 || command.MaxLevel < command.MinLevel || command.OperatorAdminID <= 0 {
		return xerr.New(xerr.InvalidArgument, "pretty display id pool argument is invalid")
	}
	if !userdomain.ValidPrettyDisplayIDPoolTrack(command.LevelTrack) || !userdomain.ValidPrettyDisplayIDPoolStatus(command.Status) {
		return xerr.New(xerr.InvalidArgument, "pretty display id pool argument is invalid")
	}
	if command.RuleType == "" {
		command.RuleType = userdomain.PrettyDisplayIDRuleAABBCC
	}
	if !userdomain.ValidPrettyDisplayIDRuleType(command.RuleType) {
		return xerr.New(xerr.InvalidArgument, "rule_type is invalid")
	}
	return nil
}

func normalizePrettyDisplayIDPoolCommand(command *userdomain.PrettyDisplayIDPoolCommand) {
	command.Name = strings.TrimSpace(command.Name)
	command.LevelTrack = strings.TrimSpace(command.LevelTrack)
	command.RuleType = strings.TrimSpace(command.RuleType)
	command.RuleConfigJSON = strings.TrimSpace(command.RuleConfigJSON)
	command.Status = strings.TrimSpace(command.Status)
	if command.RuleType == "" {
		command.RuleType = userdomain.PrettyDisplayIDRuleAABBCC
	}
}

func normalizePage(page int32) int32 {
	if page <= 0 {
		return 1
	}
	return page
}

func normalizePageSize(pageSize int32) int32 {
	if pageSize <= 0 {
		return 20
	}
	if pageSize > 100 {
		return 100
	}
	return pageSize
}
