// Package walletclient 适配 wallet-service 资源与 VIP 权益 RPC，供 user-service 构建展示读模型并执行隐私门禁。
package walletclient

import (
	"context"
	"strings"

	"google.golang.org/grpc"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	cpdomain "hyapp/services/user-service/internal/domain/cp"
	userservice "hyapp/services/user-service/internal/service/user"
)

const avatarFrameResourceType = "avatar_frame"

// Client 只暴露 user-service 构建 CP 读模型和执行资料隐私需要的钱包读取能力。
type Client struct {
	client walletv1.WalletServiceClient
}

// CheckVipBenefit 读取 wallet-service 已结合 App 配置、有效 VIP、体验卡排除项及用户开关计算出的最终结果。
// user-service 不缓存隐私权益，保证资料统计隐藏和匿名访问每次都按访问发生时的事实固化。
func (c *Client) CheckVipBenefit(ctx context.Context, requestID string, userID int64, benefitCode string) (bool, error) {
	if c == nil || c.client == nil {
		return false, grpc.ErrClientConnClosing
	}
	requestID = strings.TrimSpace(requestID)
	if requestID == "" {
		// 非 gateway 的内部调用缺 request_id 时生成兜底链路 ID；HTTP 请求始终复用 gateway 已生成的 request_id。
		requestID = idgen.New("user_privacy")
	}
	resp, err := c.client.CheckVipBenefit(ctx, &walletv1.CheckVipBenefitRequest{
		RequestId:   requestID,
		AppCode:     appcode.FromContext(ctx),
		UserId:      userID,
		BenefitCode: benefitCode,
	})
	if err != nil {
		return false, err
	}
	return resp.GetAllowed(), nil
}

// CheckVIPBenefitDecision 返回动态头像门禁需要的完整判定结果。
// program_type 必须来自 wallet 的 App 配置，调用方不能按 app_code 或固定 VIP 数字推断。
func (c *Client) CheckVIPBenefitDecision(ctx context.Context, requestID string, userID int64, benefitCode string) (userservice.VIPBenefitDecision, error) {
	if c == nil || c.client == nil {
		return userservice.VIPBenefitDecision{}, grpc.ErrClientConnClosing
	}
	requestID = strings.TrimSpace(requestID)
	if requestID == "" {
		requestID = idgen.New("user_avatar")
	}
	resp, err := c.client.CheckVipBenefit(ctx, &walletv1.CheckVipBenefitRequest{
		RequestId: requestID, AppCode: appcode.FromContext(ctx), UserId: userID, BenefitCode: strings.TrimSpace(benefitCode),
	})
	if err != nil {
		return userservice.VIPBenefitDecision{}, err
	}
	if resp == nil || resp.GetState() == nil || resp.GetState().GetProgramConfig() == nil {
		return userservice.VIPBenefitDecision{}, grpc.ErrClientConnClosing
	}
	return userservice.VIPBenefitDecision{
		ProgramType:           strings.TrimSpace(resp.GetState().GetProgramConfig().GetProgramType()),
		Allowed:               resp.GetAllowed(),
		RequiredLevel:         resp.GetRequiredLevel(),
		CurrentEffectiveLevel: resp.GetCurrentEffectiveLevel(),
	}, nil
}

// NewGRPC 基于共享 gRPC 连接创建 wallet-service 资源读取客户端。
func NewGRPC(conn grpc.ClientConnInterface) *Client {
	return &Client{client: walletv1.NewWalletServiceClient(conn)}
}

// BatchGetAvatarFrames 返回用户当前佩戴头像框快照；没有佩戴的用户不会出现在结果 map 中。
func (c *Client) BatchGetAvatarFrames(ctx context.Context, appCode string, userIDs []int64) (map[int64]cpdomain.AvatarFrameSnapshot, error) {
	if c == nil || c.client == nil || len(userIDs) == 0 {
		// 没有用户或依赖未装配时返回空 map，调用方会保持 avatar_frame=nil；这里不制造默认头像框。
		return map[int64]cpdomain.AvatarFrameSnapshot{}, nil
	}
	// wallet-service 是资源佩戴事实的 owner；user-service 只按用户批量读取 avatar_frame 类型，避免读取整包资源列表。
	resp, err := c.client.BatchGetUserEquippedResources(ctx, &walletv1.BatchGetUserEquippedResourcesRequest{
		RequestId:     idgen.New("cp_frame"),
		AppCode:       appCode,
		UserIds:       userIDs,
		ResourceTypes: []string{avatarFrameResourceType},
	})
	if err != nil {
		return nil, err
	}
	frames := make(map[int64]cpdomain.AvatarFrameSnapshot, len(resp.GetUsers()))
	for _, user := range resp.GetUsers() {
		for _, entitlement := range user.GetResources() {
			resource := entitlement.GetResource()
			if resource == nil || resource.GetResourceType() != avatarFrameResourceType || resource.GetResourceId() <= 0 {
				// 返回包里可能混入空资源或未来扩展资源类型；榜单只认有效 avatar_frame，其他内容不能进入 CP 快照。
				continue
			}
			// 一个用户同一类型理论上只应佩戴一个资源；命中第一条有效头像框后 break，避免后续异常重复覆盖。
			frames[user.GetUserId()] = cpdomain.AvatarFrameSnapshot{
				ResourceID:    resource.GetResourceId(),
				ResourceCode:  resource.GetResourceCode(),
				Name:          resource.GetName(),
				AssetURL:      resource.GetAssetUrl(),
				PreviewURL:    resource.GetPreviewUrl(),
				AnimationURL:  resource.GetAnimationUrl(),
				MetadataJSON:  resource.GetMetadataJson(),
				EntitlementID: entitlement.GetEntitlementId(),
			}
			break
		}
	}
	return frames, nil
}
