package grpc

import (
	"context"

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/pkg/xerr"
	cpdomain "hyapp/services/user-service/internal/domain/cp"
)

// ListCPApplications 返回当前用户收到或发出的 CP/兄弟/姐妹申请。
func (s *Server) ListCPApplications(ctx context.Context, req *userv1.ListCPApplicationsRequest) (*userv1.ListCPApplicationsResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	// gRPC 层只透传分页、方向和状态，pending 过期固化由 service/repository 统一处理。
	applications, total, serverTimeMS, err := s.cpSvc.ListApplications(ctx, req.GetUserId(), req.GetDirection(), req.GetStatus(), req.GetPage(), req.GetPageSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &userv1.ListCPApplicationsResponse{
		Applications: make([]*userv1.CPApplication, 0, len(applications)),
		Total:        total,
		ServerTimeMs: serverTimeMS,
	}
	for _, application := range applications {
		resp.Applications = append(resp.Applications, toProtoCPApplication(application))
	}
	return resp, nil
}

// AcceptCPApplication 同意申请并返回建立后的关系。
func (s *Server) AcceptCPApplication(ctx context.Context, req *userv1.AcceptCPApplicationRequest) (*userv1.AcceptCPApplicationResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	application, relationship, err := s.cpSvc.AcceptApplication(ctx, req.GetUserId(), req.GetApplicationId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.AcceptCPApplicationResponse{
		Application:  toProtoCPApplication(application),
		Relationship: toProtoCPRelationship(relationship),
	}, nil
}

// RejectCPApplication 拒绝申请；拒绝原因只进入 outbox payload，不改变关系主事实。
func (s *Server) RejectCPApplication(ctx context.Context, req *userv1.RejectCPApplicationRequest) (*userv1.RejectCPApplicationResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	application, err := s.cpSvc.RejectApplication(ctx, req.GetUserId(), req.GetApplicationId(), req.GetReason())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.RejectCPApplicationResponse{Application: toProtoCPApplication(application)}, nil
}

// ListCPRelationships 返回用户 active 关系；当前每个用户最多同时拥有一条关系。
func (s *Server) ListCPRelationships(ctx context.Context, req *userv1.ListCPRelationshipsRequest) (*userv1.ListCPRelationshipsResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	relationships, total, err := s.cpSvc.ListRelationships(ctx, req.GetUserId(), req.GetRelationType(), req.GetPage(), req.GetPageSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &userv1.ListCPRelationshipsResponse{
		Relationships: make([]*userv1.CPRelationship, 0, len(relationships)),
		Total:         total,
	}
	for _, relationship := range relationships {
		resp.Relationships = append(resp.Relationships, toProtoCPRelationship(relationship))
	}
	return resp, nil
}

// ListCPIntimacyLeaderboard 返回 cron 写入 Redis 的 CP 亲密值排行榜。
func (s *Server) ListCPIntimacyLeaderboard(ctx context.Context, req *userv1.ListCPIntimacyLeaderboardRequest) (*userv1.ListCPIntimacyLeaderboardResponse, error) {
	// app_code 必须从 meta 进入 context，Redis key 按 app 隔离；gateway 不传时会落到默认 app。
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	// service 层负责关系类型归一、分页夹紧和 Redis 读取；transport 只做 proto 边界转换。
	page, err := s.cpSvc.ListIntimacyLeaderboard(ctx, req.GetRelationType(), req.GetPage(), req.GetPageSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &userv1.ListCPIntimacyLeaderboardResponse{
		Items:        make([]*userv1.CPIntimacyLeaderboardItem, 0, len(page.Items)),
		Total:        page.Total,
		Page:         page.Page,
		PageSize:     page.PageSize,
		ServerTimeMs: page.ServerTimeMS,
	}
	for _, item := range page.Items {
		// item 内已经包含 cron 固化的双方资料和头像框快照，这里不能再回表或跨服务补字段。
		resp.Items = append(resp.Items, toProtoCPIntimacyLeaderboardItem(item))
	}
	return resp, nil
}

// ListCPFormationGiftFeed 返回组 CP 成立时 wallet 结算金币价值严格超过固定门槛的历史成立事件。
func (s *Server) ListCPFormationGiftFeed(ctx context.Context, req *userv1.ListCPFormationGiftFeedRequest) (*userv1.ListCPFormationGiftFeedResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	// limit 只控制返回条数；金币门槛、active/accepted 和 relation_type=cp 均由 owner 仓储固定，调用方不能篡改。
	items, err := s.cpSvc.ListFormationGiftFeed(ctx, req.GetLimit())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &userv1.ListCPFormationGiftFeedResponse{
		Items: make([]*userv1.CPFormationGiftFeedItem, 0, len(items)),
	}
	for _, item := range items {
		resp.Items = append(resp.Items, toProtoCPFormationGiftFeedItem(item))
	}
	return resp, nil
}

// PrepareBreakCPRelationship 创建解除关系占位；gateway 随后按返回费用调用 wallet-service 扣金币。
func (s *Server) PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	breakup, relationship, err := s.cpSvc.PrepareBreakRelationship(ctx, req.GetUserId(), req.GetRelationshipId(), req.GetCommandId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.PrepareBreakCPRelationshipResponse{
		Relationship:        toProtoCPRelationship(relationship),
		CommandId:           breakup.CommandID,
		Status:              breakup.Status,
		BreakupCostCoins:    breakup.BreakupCostCoins,
		WalletTransactionId: breakup.WalletTransactionID,
		CoinBalanceAfter:    breakup.CoinBalanceAfter,
	}, nil
}

// ConfirmBreakCPRelationship 在钱包扣费成功后确认解除，关系状态和 outbox 在同一事务提交。
func (s *Server) ConfirmBreakCPRelationship(ctx context.Context, req *userv1.ConfirmBreakCPRelationshipRequest) (*userv1.ConfirmBreakCPRelationshipResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	breakup, relationship, err := s.cpSvc.ConfirmBreakRelationship(ctx, req.GetUserId(), req.GetRelationshipId(), req.GetCommandId(), req.GetWalletTransactionId(), req.GetPaidCoinAmount(), req.GetCoinBalanceAfter())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.ConfirmBreakCPRelationshipResponse{
		Relationship:        toProtoCPRelationship(relationship),
		BreakupCostCoins:    breakup.BreakupCostCoins,
		WalletTransactionId: breakup.WalletTransactionID,
		CoinBalanceAfter:    breakup.CoinBalanceAfter,
	}, nil
}

// CancelBreakCPRelationship 取消还未扣费成功的解除占位，避免钱包失败后关系被其他请求长期阻塞。
func (s *Server) CancelBreakCPRelationship(ctx context.Context, req *userv1.CancelBreakCPRelationshipRequest) (*userv1.CancelBreakCPRelationshipResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	breakup, err := s.cpSvc.CancelBreakRelationship(ctx, req.GetUserId(), req.GetRelationshipId(), req.GetCommandId(), req.GetReason())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CancelBreakCPRelationshipResponse{
		CommandId: breakup.CommandID,
		Status:    breakup.Status,
	}, nil
}

// ConsumeRoomGiftCPEvent 由 user-service 内部 MQ worker 调用，把 RoomGiftSent 转为 CP 申请或亲密值增长。
func (s *Server) ConsumeRoomGiftCPEvent(ctx context.Context, req *userv1.ConsumeRoomGiftCPEventRequest) (*userv1.ConsumeRoomGiftCPEventResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	event := req.GetEvent()
	result, err := s.cpSvc.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{
		EventID:          event.GetEventId(),
		RoomID:           event.GetRoomId(),
		RoomVersion:      event.GetRoomVersion(),
		OccurredAtMS:     event.GetOccurredAtMs(),
		SenderUserID:     event.GetSenderUserId(),
		TargetUserID:     event.GetTargetUserId(),
		GiftID:           event.GetGiftId(),
		GiftCount:        event.GetGiftCount(),
		GiftValue:        event.GetGiftValue(),
		CoinSpent:        event.GetCoinSpent(),
		BillingReceiptID: event.GetBillingReceiptId(),
		VisibleRegionID:  event.GetVisibleRegionId(),
		CommandID:        event.GetCommandId(),
		GiftTypeCode:     event.GetGiftTypeCode(),
		CPRelationType:   event.GetCpRelationType(),
		GiftName:         event.GetGiftName(),
		GiftIconURL:      event.GetGiftIconUrl(),
		GiftAnimationURL: event.GetGiftAnimationUrl(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.ConsumeRoomGiftCPEventResponse{
		Consumed:     result.Consumed,
		Application:  toProtoCPApplication(result.Application),
		Relationship: toProtoCPRelationship(result.Relationship),
	}, nil
}

// ListCPWeeklyRankEntries 给 activity-service 读取周榜结算快照；该 RPC 不暴露给 App，避免 H5 直接扫 MySQL 周榜事实。
func (s *Server) ListCPWeeklyRankEntries(ctx context.Context, req *userv1.ListCPWeeklyRankEntriesRequest) (*userv1.ListCPWeeklyRankEntriesResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	entries, serverTimeMS, err := s.cpSvc.ListWeeklyRankEntries(ctx, req.GetRelationType(), req.GetStartMs(), req.GetEndMs(), req.GetLimit())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &userv1.ListCPWeeklyRankEntriesResponse{
		Entries:      make([]*userv1.CPWeeklyRankEntry, 0, len(entries)),
		ServerTimeMs: serverTimeMS,
	}
	for _, entry := range entries {
		resp.Entries = append(resp.Entries, toProtoCPWeeklyRankEntry(entry))
	}
	return resp, nil
}

func toProtoCPApplication(application cpdomain.Application) *userv1.CPApplication {
	if application.ApplicationID == "" {
		return nil
	}
	return &userv1.CPApplication{
		ApplicationId: application.ApplicationID,
		RelationType:  application.RelationType,
		Status:        application.Status,
		Requester:     toProtoCPUserProfile(application.Requester),
		Target:        toProtoCPUserProfile(application.Target),
		RoomId:        application.RoomID,
		RoomRegionId:  application.RoomRegionID,
		Gift:          toProtoCPGiftSnapshot(application.Gift),
		CreatedAtMs:   application.CreatedAtMS,
		UpdatedAtMs:   application.UpdatedAtMS,
		ExpiresAtMs:   application.ExpiresAtMS,
		DecidedAtMs:   application.DecidedAtMS,
	}
}

func toProtoCPRelationship(relationship cpdomain.Relationship) *userv1.CPRelationship {
	if relationship.RelationshipID == "" {
		return nil
	}
	return &userv1.CPRelationship{
		RelationshipId:        relationship.RelationshipID,
		RelationType:          relationship.RelationType,
		Status:                relationship.Status,
		Me:                    toProtoCPUserProfile(relationship.Me),
		Partner:               toProtoCPUserProfile(relationship.Partner),
		IntimacyValue:         relationship.IntimacyValue,
		Level:                 relationship.Level,
		FormedAtMs:            relationship.FormedAtMS,
		UpdatedAtMs:           relationship.UpdatedAtMS,
		CurrentLevelThreshold: relationship.CurrentLevelThreshold,
		NextLevelThreshold:    relationship.NextLevelThreshold,
		NeededForNextLevel:    relationship.NeededForNextLevel,
		LevelProgressPercent:  relationship.LevelProgressPercent,
		MaxLevel:              relationship.MaxLevel,
		BreakupCostCoins:      relationship.BreakupCostCoins,
		EndedAtMs:             relationship.EndedAtMS,
	}
}

func toProtoCPFormationGiftFeedItem(item cpdomain.FormationGiftFeedItem) *userv1.CPFormationGiftFeedItem {
	if item.FormationID == "" {
		return nil
	}
	return &userv1.CPFormationGiftFeedItem{
		FormationId:   item.FormationID,
		GiftCoinValue: item.GiftCoinValue,
		Requester:     toProtoCPUserProfile(item.Requester),
		Target:        toProtoCPUserProfile(item.Target),
		FormedAtMs:    item.FormedAtMS,
	}
}

func toProtoCPUserProfile(profile cpdomain.UserProfile) *userv1.CPUserProfile {
	if profile.UserID <= 0 {
		return nil
	}
	return &userv1.CPUserProfile{
		UserId:        profile.UserID,
		DisplayUserId: profile.DisplayUserID,
		Username:      profile.Username,
		Avatar:        profile.Avatar,
	}
}

func toProtoCPWeeklyRankEntry(item cpdomain.WeeklyRankEntry) *userv1.CPWeeklyRankEntry {
	if item.RelationshipID == "" {
		return nil
	}
	return &userv1.CPWeeklyRankEntry{
		Rank:            item.Rank,
		RelationshipId:  item.RelationshipID,
		RelationType:    item.RelationType,
		Score:           item.Score,
		UserA:           toProtoCPIntimacyLeaderboardUser(item.UserA),
		UserB:           toProtoCPIntimacyLeaderboardUser(item.UserB),
		FormedAtMs:      item.FormedAtMS,
		UpdatedAtMs:     item.UpdatedAtMS,
		FirstScoredAtMs: item.FirstScoredAtMS,
		LastScoredAtMs:  item.LastScoredAtMS,
	}
}

func toProtoCPIntimacyLeaderboardItem(item cpdomain.IntimacyLeaderboardEntry) *userv1.CPIntimacyLeaderboardItem {
	if item.RelationshipID == "" {
		return nil
	}
	return &userv1.CPIntimacyLeaderboardItem{
		Rank:           item.Rank,
		RelationshipId: item.RelationshipID,
		RelationType:   item.RelationType,
		IntimacyValue:  item.IntimacyValue,
		Level:          item.Level,
		UserA:          toProtoCPIntimacyLeaderboardUser(item.UserA),
		UserB:          toProtoCPIntimacyLeaderboardUser(item.UserB),
		FormedAtMs:     item.FormedAtMS,
		UpdatedAtMs:    item.UpdatedAtMS,
	}
}

func toProtoCPIntimacyLeaderboardUser(user cpdomain.IntimacyLeaderboardUser) *userv1.CPIntimacyLeaderboardUser {
	if user.UserID <= 0 {
		return nil
	}
	return &userv1.CPIntimacyLeaderboardUser{
		UserId:        user.UserID,
		DisplayUserId: user.DisplayUserID,
		Username:      user.Username,
		Avatar:        user.Avatar,
		AvatarFrame:   toProtoCPAvatarFrame(user.AvatarFrame),
	}
}

func toProtoCPAvatarFrame(frame *cpdomain.AvatarFrameSnapshot) *userv1.CPAvatarFrameSnapshot {
	if frame == nil || frame.ResourceID <= 0 {
		return nil
	}
	return &userv1.CPAvatarFrameSnapshot{
		ResourceId:   frame.ResourceID,
		ResourceCode: frame.ResourceCode,
		Name:         frame.Name,
		AssetUrl:     frame.AssetURL,
		PreviewUrl:   frame.PreviewURL,
		AnimationUrl: frame.AnimationURL,
		MetadataJson: frame.MetadataJSON,
	}
}

func toProtoCPGiftSnapshot(gift cpdomain.GiftSnapshot) *userv1.CPGiftSnapshot {
	if gift.GiftID == "" && gift.GiftName == "" {
		return nil
	}
	return &userv1.CPGiftSnapshot{
		GiftId:           gift.GiftID,
		GiftName:         gift.GiftName,
		GiftIconUrl:      gift.GiftIconURL,
		GiftAnimationUrl: gift.GiftAnimationURL,
		GiftCount:        gift.GiftCount,
		GiftValue:        gift.GiftValue,
		CoinSpent:        gift.CoinSpent,
		BillingReceiptId: gift.BillingReceiptID,
	}
}
