package user

import (
	"context"
	"strings"

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

const (
	defaultUserIDPageSize = 500
	maxUserIDPageSize     = 5000
)

// GetUser 查询单个用户主状态。
func (s *Service) GetUser(ctx context.Context, userID int64) (userdomain.User, error) {
	if userID <= 0 {
		// service 层统一做参数校验，transport 只负责协议适配。
		return userdomain.User{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}

	if s.userRepository == nil {
		// repository 缺失代表应用装配不完整。
		return userdomain.User{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}

	user, err := s.userRepository.GetUser(ctx, userID)
	if err != nil {
		return userdomain.User{}, err
	}

	// 查询用户时顺带执行靓号懒过期，返回给上游的是当前有效展示号。
	user, err = s.refreshExpiredUser(ctx, user, "")
	if err != nil {
		return userdomain.User{}, err
	}
	if s.contentBlacklistRepository == nil {
		// 纯 service 单测允许只注入最小 UserRepository；生产装配会自动注入同库 blacklist repository。
		return user, nil
	}
	user.ContentBlacklisted, err = s.contentBlacklistRepository.IsUserContentBlacklisted(ctx, userID)
	if err != nil {
		// 屏蔽状态读取失败不能按“未屏蔽”降级，否则数据库抖动会把运营限制静默放开。
		return userdomain.User{}, err
	}
	return user, nil
}

// GetInviteAttribution 按被邀请用户读取邀请关系；没有关系时返回 Found=false，调用方按普通非邀请充值处理。
func (s *Service) GetInviteAttribution(ctx context.Context, invitedUserID int64) (invitedomain.Attribution, error) {
	if invitedUserID <= 0 {
		return invitedomain.Attribution{}, xerr.New(xerr.InvalidArgument, "invited_user_id is required")
	}
	if s.userRepository == nil {
		return invitedomain.Attribution{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.GetInviteAttribution(ctx, invitedUserID)
}

// GetMyProfileStats 读取我的页顶部统计计数；统计事实必须由专门 read model 维护。
func (s *Service) GetMyProfileStats(ctx context.Context, userID int64) (userdomain.ProfileStats, error) {
	if userID <= 0 {
		return userdomain.ProfileStats{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if s.userRepository == nil {
		return userdomain.ProfileStats{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.GetMyProfileStats(ctx, userID)
}

// BatchGetUsers 批量查询用户主状态。
func (s *Service) BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error) {
	if len(userIDs) == 0 {
		// 空批量请求返回空 map，避免调用方处理 nil。
		return map[int64]userdomain.User{}, nil
	}

	for _, userID := range userIDs {
		if userID <= 0 {
			// 任一非法 ID 都让整个批量请求失败，避免部分成功掩盖输入错误。
			return nil, xerr.New(xerr.InvalidArgument, "user_ids contains invalid value")
		}
	}

	if s.userRepository == nil {
		// 批量查询同样不能绕过 repository。
		return nil, xerr.New(xerr.Unavailable, "user repository is not configured")
	}

	users, err := s.userRepository.BatchGetUsers(ctx, userIDs)
	if err != nil {
		return nil, err
	}

	for userID, user := range users {
		// 批量返回前逐个刷新过期靓号，保证身份投影一致。
		refreshed, err := s.refreshExpiredUser(ctx, user, "")
		if err != nil {
			return nil, err
		}
		users[userID] = refreshed
	}

	return users, nil
}

// BatchGetRoomBasicUsers 批量查询房间首屏最小用户资料。
func (s *Service) BatchGetRoomBasicUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.RoomBasicUser, error) {
	if len(userIDs) == 0 {
		// 空批量请求返回空 map，gateway 可以直接输出空 profiles。
		return map[int64]userdomain.RoomBasicUser{}, nil
	}
	for _, userID := range userIDs {
		if userID <= 0 {
			// room-service 只接受正 user_id；这里继续对调用方输入做边界收敛。
			return nil, xerr.New(xerr.InvalidArgument, "user_ids contains invalid value")
		}
	}
	if s.userRepository == nil {
		// 房间热路径也不能绕过 owner repository；无存储时明确返回不可用。
		return nil, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.BatchGetRoomBasicUsers(ctx, userIDs)
}

// ListUserIDs returns target user IDs for low-frequency backend fanout tasks.
func (s *Service) ListUserIDs(ctx context.Context, filter userdomain.UserIDPageFilter) ([]int64, int64, bool, error) {
	if s.userRepository == nil {
		return nil, 0, false, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	filter.TargetScope = strings.TrimSpace(filter.TargetScope)
	filter.Country = userdomain.NormalizeCountryCode(filter.Country)
	filter.PageSize = normalizeUserIDPageSize(filter.PageSize)
	if filter.CursorUserID < 0 {
		return nil, 0, false, xerr.New(xerr.InvalidArgument, "cursor_user_id is invalid")
	}
	switch filter.TargetScope {
	case userdomain.UserIDTargetAllActiveUsers, userdomain.UserIDTargetAllRegistered:
	case userdomain.UserIDTargetRegion:
		if filter.RegionID <= 0 {
			return nil, 0, false, xerr.New(xerr.InvalidArgument, "region_id is required")
		}
	case userdomain.UserIDTargetCountry:
		if !userdomain.ValidCountryCode(filter.Country) {
			return nil, 0, false, xerr.New(xerr.InvalidArgument, "country is invalid")
		}
	default:
		return nil, 0, false, xerr.New(xerr.InvalidArgument, "target_scope is invalid")
	}

	userIDs, err := s.userRepository.ListUserIDs(ctx, filter)
	if err != nil {
		return nil, 0, false, err
	}
	nextCursor := filter.CursorUserID
	if len(userIDs) > 0 {
		nextCursor = userIDs[len(userIDs)-1]
	}
	return userIDs, nextCursor, len(userIDs) < filter.PageSize, nil
}

func normalizeUserIDPageSize(value int) int {
	if value <= 0 {
		return defaultUserIDPageSize
	}
	if value > maxUserIDPageSize {
		return maxUserIDPageSize
	}
	return value
}

// CreateUser 创建用户主记录，并同时分配 active display_user_id。
// 该方法供注册和三方首次登录后续接入，避免出现没有短号的用户。
func (s *Service) CreateUser(ctx context.Context) (userdomain.User, userdomain.Identity, error) {
	if s.userRepository == nil {
		// 没有持久化能力时不能创建用户。
		return userdomain.User{}, userdomain.Identity{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}

	for attempt := 0; attempt < s.allocateMaxAttempts; attempt++ {
		// user_id 和 display_user_id 都在每次尝试中重新生成，降低冲突概率。
		userID := s.idGenerator.NewInt64()
		nowMs := s.now().UnixMilli()
		displayUserID := s.displayUserIDAllocator.NextDisplayUserID(userID, attempt)
		if !userdomain.ValidDisplayUserID(displayUserID) {
			// allocator 理论上应生成合法短号；非法候选直接跳过本次尝试。
			continue
		}

		createdUser := userdomain.User{
			AppCode:                  appcode.FromContext(ctx),
			UserID:                   userID,
			DefaultDisplayUserID:     displayUserID,
			CurrentDisplayUserID:     displayUserID,
			CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault,
			Status:                   userdomain.StatusActive,
			CreatedAtMs:              nowMs,
			UpdatedAtMs:              nowMs,
		}
		identity := userdomain.Identity{
			AppCode:              appcode.FromContext(ctx),
			UserID:               userID,
			DisplayUserID:        displayUserID,
			DefaultDisplayUserID: displayUserID,
			DisplayUserIDKind:    userdomain.DisplayUserIDKindDefault,
			Status:               userdomain.DisplayUserIDStatusActive,
		}

		err := s.userRepository.CreateUserWithIdentity(ctx, createdUser, identity)
		if err == nil {
			// repository 事务成功后，用户和默认短号一定同时存在。
			return createdUser, identity, nil
		}
		if !xerr.IsCode(err, xerr.DisplayUserIDExists) {
			// 非短号冲突错误不能重试，否则可能掩盖真实存储异常。
			return userdomain.User{}, userdomain.Identity{}, err
		}
	}

	// 有限重试耗尽后返回稳定 reason，调用方可以提示稍后重试。
	return userdomain.User{}, userdomain.Identity{}, xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id allocation failed")
}
