package user

import (
	"context"
	"strings"

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

const (
	defaultSocialPageSize = 20
	maxSocialPageSize     = 100

	// 这两个 code 必须与 wallet-service 的可配置 VIP benefit code 保持一致；
	// user-service 只消费最终 allowed 结果，不复制等级、体验卡和用户开关规则。
	hideProfileDataBenefitCode       = "hide_profile_data"
	anonymousProfileVisitBenefitCode = "anonymous_profile_visit"
)

// ProfileVisitResult 是一次资料访问的隐私收敛结果。
// TargetStatsHidden=true 时 TargetStats 必须保持零值，transport 也必须省略该对象，形成双层防泄漏。
type ProfileVisitResult struct {
	Recorded          bool
	TargetStats       userdomain.ProfileStats
	TargetStatsHidden bool
}

// RecordProfileVisit 记录访问关系；自己访问自己不进入访问计数。
func (s *Service) RecordProfileVisit(ctx context.Context, requestID string, visitorUserID int64, targetUserID int64) (ProfileVisitResult, error) {
	if visitorUserID <= 0 || targetUserID <= 0 {
		return ProfileVisitResult{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if visitorUserID == targetUserID {
		return ProfileVisitResult{}, xerr.New(xerr.InvalidArgument, "cannot visit self")
	}
	if s.userRepository == nil {
		return ProfileVisitResult{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}

	// 两项隐私都必须在访问发生时实时判断：目标用户的数据隐藏影响本次响应；访客匿名一旦生效，
	// 本次访问就不能新增或刷新任何访客事实。匿名权益查询失败时中止请求，绝不能降级公开写入泄漏身份。
	anonymousVisit, err := s.anonymousVisitAllowed(ctx, requestID, visitorUserID)
	if err != nil {
		return ProfileVisitResult{}, err
	}
	// 匿名门禁先成功，才读取目标用户的当前隐藏设置；这样 wallet 整体不可用时不做无意义第二次 RPC，
	// 正常链路下 target_stats 的判定也更接近最终响应时点。
	targetStatsHidden := s.targetStatsMustBeHidden(ctx, requestID, targetUserID)
	if anonymousVisit {
		// 匿名访问虽然不留足迹，仍必须确认目标用户真实存在；否则伪造 user_id 会被错误返回为成功。
		if _, err := s.userRepository.GetUser(ctx, targetUserID); err != nil {
			return ProfileVisitResult{}, err
		}
		result := ProfileVisitResult{Recorded: false, TargetStatsHidden: targetStatsHidden}
		if !targetStatsHidden {
			// 目标资料统计来自既有聚合表，只读查询不会改变 visitors_count，也不会触碰公开或历史匿名记录。
			stats, err := s.userRepository.GetMyProfileStats(ctx, targetUserID)
			if err != nil {
				return ProfileVisitResult{}, err
			}
			result.TargetStats = stats
		}
		return result, nil
	}

	// 未启用匿名权益时保持公开访问的原子写入：足迹聚合和 visitors_count 在同一事务内更新。
	recorded, stats, err := s.userRepository.RecordProfileVisit(ctx, visitorUserID, targetUserID, s.now().UnixMilli())
	if err != nil {
		return ProfileVisitResult{}, err
	}
	result := ProfileVisitResult{Recorded: recorded, TargetStatsHidden: targetStatsHidden}
	if !targetStatsHidden {
		result.TargetStats = stats
	}
	return result, nil
}

// targetStatsMustBeHidden 收敛目标资料统计的可见性。
// checker 缺失或 RPC 失败都隐藏 stats：这是只读保护，不会因此授予目标用户任何可执行付费能力。
func (s *Service) targetStatsMustBeHidden(ctx context.Context, requestID string, userID int64) bool {
	if s.privacyBenefitChecker == nil {
		return true
	}
	allowed, err := s.privacyBenefitChecker.CheckVipBenefit(ctx, requestID, userID, hideProfileDataBenefitCode)
	return err != nil || allowed
}

// anonymousVisitAllowed 判断本次足迹能否匿名。
// 依赖失败时不能回退公开写入，也不能把失败当 allowed 授予付费权益，因此明确返回 UNAVAILABLE 且不落任何访问事实。
func (s *Service) anonymousVisitAllowed(ctx context.Context, requestID string, userID int64) (bool, error) {
	if s.privacyBenefitChecker == nil {
		return false, xerr.New(xerr.Unavailable, "privacy benefit checker is not configured")
	}
	allowed, err := s.privacyBenefitChecker.CheckVipBenefit(ctx, requestID, userID, anonymousProfileVisitBenefitCode)
	if err != nil {
		return false, xerr.New(xerr.Unavailable, "anonymous profile visit benefit is unavailable")
	}
	return allowed, nil
}

func (s *Service) ListProfileVisitors(ctx context.Context, userID int64, page int32, pageSize int32) ([]userdomain.ProfileVisitRecord, int64, error) {
	if userID <= 0 {
		return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if s.userRepository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	page, pageSize = normalizeSocialPage(page, pageSize)
	return s.userRepository.ListProfileVisitors(ctx, userID, page, pageSize)
}

func (s *Service) FollowUser(ctx context.Context, followerUserID int64, followeeUserID int64) (userdomain.ProfileStats, error) {
	if followerUserID <= 0 || followeeUserID <= 0 {
		return userdomain.ProfileStats{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if followerUserID == followeeUserID {
		return userdomain.ProfileStats{}, xerr.New(xerr.InvalidArgument, "cannot follow self")
	}
	if s.userRepository == nil {
		return userdomain.ProfileStats{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.FollowUser(ctx, followerUserID, followeeUserID, s.now().UnixMilli())
}

func (s *Service) UnfollowUser(ctx context.Context, followerUserID int64, followeeUserID int64) (userdomain.ProfileStats, error) {
	if followerUserID <= 0 || followeeUserID <= 0 {
		return userdomain.ProfileStats{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if followerUserID == followeeUserID {
		return userdomain.ProfileStats{}, xerr.New(xerr.InvalidArgument, "cannot unfollow self")
	}
	if s.userRepository == nil {
		return userdomain.ProfileStats{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.UnfollowUser(ctx, followerUserID, followeeUserID, s.now().UnixMilli())
}

func (s *Service) ListFollowing(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FollowRecord, int64, error) {
	if userID <= 0 {
		return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if s.userRepository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	page, pageSize = normalizeSocialPage(page, pageSize)
	return s.userRepository.ListFollowing(ctx, userID, page, pageSize, cursorUpdatedAtMS, cursorUserID)
}

func (s *Service) ApplyFriend(ctx context.Context, requesterUserID int64, targetUserID int64) (userdomain.FriendApplication, bool, error) {
	if requesterUserID <= 0 || targetUserID <= 0 {
		return userdomain.FriendApplication{}, false, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if requesterUserID == targetUserID {
		return userdomain.FriendApplication{}, false, xerr.New(xerr.InvalidArgument, "cannot add self as friend")
	}
	if s.userRepository == nil {
		return userdomain.FriendApplication{}, false, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.ApplyFriend(ctx, requesterUserID, targetUserID, s.now().UnixMilli())
}

func (s *Service) AcceptFriendApplication(ctx context.Context, accepterUserID int64, requesterUserID int64) (userdomain.FriendRecord, error) {
	if accepterUserID <= 0 || requesterUserID <= 0 {
		return userdomain.FriendRecord{}, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if accepterUserID == requesterUserID {
		return userdomain.FriendRecord{}, xerr.New(xerr.InvalidArgument, "cannot accept self friend request")
	}
	if s.userRepository == nil {
		return userdomain.FriendRecord{}, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.AcceptFriendApplication(ctx, accepterUserID, requesterUserID, s.now().UnixMilli())
}

func (s *Service) DeleteFriend(ctx context.Context, userID int64, friendUserID int64) (bool, error) {
	if userID <= 0 || friendUserID <= 0 {
		return false, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if userID == friendUserID {
		return false, xerr.New(xerr.InvalidArgument, "cannot delete self friend")
	}
	if s.userRepository == nil {
		return false, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	return s.userRepository.DeleteFriend(ctx, userID, friendUserID, s.now().UnixMilli())
}

func (s *Service) ListFriends(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FriendRecord, int64, error) {
	if userID <= 0 {
		return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	if s.userRepository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	page, pageSize = normalizeSocialPage(page, pageSize)
	return s.userRepository.ListFriends(ctx, userID, page, pageSize, cursorUpdatedAtMS, cursorUserID)
}

func (s *Service) ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error) {
	if userID <= 0 {
		return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	direction = strings.ToLower(strings.TrimSpace(direction))
	if direction == "" {
		direction = "incoming"
	}
	if direction != "incoming" && direction != "outgoing" {
		return nil, 0, xerr.New(xerr.InvalidArgument, "direction is invalid")
	}
	if s.userRepository == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "user repository is not configured")
	}
	page, pageSize = normalizeSocialPage(page, pageSize)
	return s.userRepository.ListFriendApplications(ctx, userID, direction, page, pageSize)
}

func normalizeSocialPage(page int32, pageSize int32) (int32, int32) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = defaultSocialPageSize
	}
	if pageSize > maxSocialPageSize {
		pageSize = maxSocialPageSize
	}
	return page, pageSize
}
