package grpc

import (
	"context"
	"time"

	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
)

// ListVipPackages 返回当前 VIP 和可购买包列表。
func (s *Server) ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	state, levels, err := s.svc.ListVipPackages(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListVipPackagesResponse{
		CurrentVip:    vipToProto(state.EffectiveVip),
		Packages:      make([]*walletv1.VipLevel, 0, len(levels)),
		State:         vipStateToProto(state),
		ProgramConfig: vipProgramConfigToProto(state.ProgramConfig),
	}
	for _, level := range levels {
		resp.Packages = append(resp.Packages, vipLevelToProto(level))
	}
	return resp, nil
}

// GetMyVip 返回当前登录用户 VIP 状态。
func (s *Server) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	state, err := s.svc.GetVipState(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	// 旧 vip 字段保留为最终生效投影，付费会员原始事实由 state.paid_vip 明确返回。
	return &walletv1.GetMyVipResponse{Vip: vipToProto(state.EffectiveVip), State: vipStateToProto(state)}, nil
}

// BatchGetUserEffectiveVipLevels 返回一页用户在当前 App 下的最终有效等级。
// paid/trial 合并和过期判断全部留在 wallet，gateway 只能原样聚合该投影。
func (s *Server) BatchGetUserEffectiveVipLevels(ctx context.Context, req *walletv1.BatchGetUserEffectiveVipLevelsRequest) (*walletv1.BatchGetUserEffectiveVipLevelsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	items, evaluatedAtMS, err := s.svc.BatchGetUserEffectiveVipLevels(ctx, req.GetUserIds())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	response := &walletv1.BatchGetUserEffectiveVipLevelsResponse{
		Users:         make([]*walletv1.UserEffectiveVipLevel, 0, len(items)),
		EvaluatedAtMs: evaluatedAtMS,
	}
	for _, item := range items {
		response.Users = append(response.Users, &walletv1.UserEffectiveVipLevel{
			UserId:      item.UserID,
			Level:       item.Level,
			ExpiresAtMs: item.ExpiresAtMS,
			Source:      item.Source,
		})
	}
	return response, nil
}

// GetVipProgramConfig 返回按 App 隔离的状态机配置和最高等级权益目录。
func (s *Server) GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	config, benefits, err := s.svc.GetVipProgramConfig(ctx)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GetVipProgramConfigResponse{
		Config:       vipProgramConfigToProto(config),
		Benefits:     vipBenefitsToProto(benefits),
		ServerTimeMs: time.Now().UnixMilli(),
	}, nil
}

// CheckVipBenefit 供权益 owner 精确判断单项能力，不允许调用方退化成等级比较。
func (s *Server) CheckVipBenefit(ctx context.Context, req *walletv1.CheckVipBenefitRequest) (*walletv1.CheckVipBenefitResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	var result ledger.CheckVipBenefitResult
	var err error
	if req.GetResourceId() > 0 || req.GetResourceType() != "" {
		// 带资源坐标的调用走向下继承判定；不带资源的通知、隐私、返币等功能权益
		// 继续使用当前等级完整矩阵，不能因为装扮继承而扩大功能权限。
		result, err = s.svc.CheckVipResourceBenefit(ctx, req.GetUserId(), req.GetBenefitCode(), req.GetResourceId(), req.GetResourceType())
	} else {
		result, err = s.svc.CheckVipBenefit(ctx, req.GetUserId(), req.GetBenefitCode())
	}
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.CheckVipBenefitResponse{
		Allowed:               result.Allowed,
		Benefit:               vipBenefitToProto(result.Benefit),
		State:                 vipStateToProto(result.State),
		DenialReason:          result.DenialReason,
		EvaluatedAtMs:         result.EvaluatedAtMS,
		RequiredLevel:         result.RequiredLevel,
		CurrentEffectiveLevel: result.CurrentEffectiveLevel,
	}, nil
}

// PurchaseVip 购买、续期或升级 VIP。
func (s *Server) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.PurchaseVip(ctx, ledger.PurchaseVipCommand{
		AppCode:   req.GetAppCode(),
		CommandID: req.GetCommandId(),
		UserID:    req.GetUserId(),
		Level:     req.GetLevel(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.PurchaseVipResponse{
		OrderId:          receipt.OrderID,
		TransactionId:    receipt.TransactionID,
		Vip:              vipToProto(receipt.Vip),
		CoinSpent:        receipt.CoinSpent,
		CoinBalanceAfter: receipt.CoinBalanceAfter,
		RewardItems:      vipRewardItemsToProto(receipt.RewardItems),
		State:            vipStateToProto(receipt.State),
		CoinBalance:      balanceToProto(receipt.CoinBalance),
	}, nil
}

// GrantVip 统一处理活动和后台赠送 VIP。
func (s *Server) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.GrantVip(ctx, ledger.GrantVipCommand{
		AppCode:        req.GetAppCode(),
		CommandID:      req.GetCommandId(),
		TargetUserID:   req.GetTargetUserId(),
		Level:          req.GetLevel(),
		GrantSource:    req.GetGrantSource(),
		OperatorUserID: req.GetOperatorUserId(),
		Reason:         req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GrantVipResponse{
		TransactionId: receipt.TransactionID,
		Vip:           vipToProto(receipt.Vip),
		RewardItems:   vipRewardItemsToProto(receipt.RewardItems),
		ServerTimeMs:  time.Now().UnixMilli(),
		State:         vipStateToProto(receipt.State),
	}, nil
}

// GrantVipTrialCard 发卡但不自动佩戴，resource_id 省略时由钱包按 level 解析同 App 预置资源。
func (s *Server) GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	resourceID := int64(0)
	if req.ResourceId != nil {
		resourceID = req.GetResourceId()
	}
	receipt, err := s.svc.GrantVipTrialCard(ctx, ledger.GrantVipTrialCardCommand{
		AppCode:        req.GetAppCode(),
		CommandID:      req.GetCommandId(),
		TargetUserID:   req.GetTargetUserId(),
		Level:          req.GetLevel(),
		DurationMS:     req.GetDurationMs(),
		ResourceID:     resourceID,
		GrantSource:    req.GetGrantSource(),
		OperatorUserID: req.GetOperatorUserId(),
		Reason:         req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GrantVipTrialCardResponse{
		TrialCard:    vipTrialCardToProto(receipt.TrialCard),
		State:        vipStateToProto(receipt.State),
		ServerTimeMs: receipt.ServerTimeMS,
	}, nil
}

// EquipVipTrialCard 精确佩戴一个 entitlement；同类型旧装备仅解除指针，库存与到期时间保持不变。
func (s *Server) EquipVipTrialCard(ctx context.Context, req *walletv1.EquipVipTrialCardRequest) (*walletv1.EquipVipTrialCardResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	card, state, err := s.svc.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{
		AppCode:       req.GetAppCode(),
		RequestID:     req.GetRequestId(),
		UserID:        req.GetUserId(),
		EntitlementID: req.GetEntitlementId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.EquipVipTrialCardResponse{
		TrialCard:    vipTrialCardToProto(card),
		State:        vipStateToProto(state),
		ServerTimeMs: time.Now().UnixMilli(),
	}, nil
}

// UnequipVipTrialCard 卸下当前体验卡并返回回落后的最终 VIP 状态。
func (s *Server) UnequipVipTrialCard(ctx context.Context, req *walletv1.UnequipVipTrialCardRequest) (*walletv1.UnequipVipTrialCardResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	unequipped, state, err := s.svc.UnequipVipTrialCard(ctx, ledger.UnequipVipTrialCardCommand{
		AppCode:   req.GetAppCode(),
		RequestID: req.GetRequestId(),
		UserID:    req.GetUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.UnequipVipTrialCardResponse{
		Unequipped:   unequipped,
		State:        vipStateToProto(state),
		ServerTimeMs: time.Now().UnixMilli(),
	}, nil
}

// ListAdminVipLevels 返回后台 VIP 配置。
func (s *Server) ListAdminVipLevels(ctx context.Context, req *walletv1.ListAdminVipLevelsRequest) (*walletv1.ListAdminVipLevelsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	config, levels, err := s.svc.ListAdminVipLevels(ctx)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli(), ProgramConfig: vipProgramConfigToProto(config)}
	for _, level := range levels {
		resp.Levels = append(resp.Levels, vipLevelToProto(level))
	}
	return resp, nil
}

// UpdateAdminVipLevels 保存后台 VIP 配置。
func (s *Server) UpdateAdminVipLevels(ctx context.Context, req *walletv1.UpdateAdminVipLevelsRequest) (*walletv1.UpdateAdminVipLevelsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	commands := make([]ledger.AdminVipLevelCommand, 0, len(req.GetLevels()))
	for _, item := range req.GetLevels() {
		if item == nil {
			continue
		}
		commands = append(commands, ledger.AdminVipLevelCommand{
			Level:                      item.GetLevel(),
			Name:                       item.GetName(),
			Status:                     item.GetStatus(),
			PriceCoin:                  item.GetPriceCoin(),
			DurationMS:                 item.GetDurationMs(),
			RewardResourceGroupID:      item.GetRewardResourceGroupId(),
			RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(),
			SortOrder:                  item.GetSortOrder(),
			Benefits:                   vipBenefitsFromProto(item.GetBenefits()),
		})
	}
	config, levels, err := s.svc.UpdateAdminVipLevels(ctx, commands, req.GetOperatorUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.UpdateAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli(), ProgramConfig: vipProgramConfigToProto(config)}
	for _, level := range levels {
		resp.Levels = append(resp.Levels, vipLevelToProto(level))
	}
	return resp, nil
}

// UpdateAdminVipProgramConfig 保存 App 级策略，审计操作者只接受请求外层的服务端身份字段。
func (s *Server) UpdateAdminVipProgramConfig(ctx context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	config := vipProgramConfigFromProto(req.GetConfig())
	config.AppCode = req.GetAppCode()
	updated, err := s.svc.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{
		Config:         config,
		OperatorUserID: req.GetOperatorUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.UpdateAdminVipProgramConfigResponse{Config: vipProgramConfigToProto(updated), ServerTimeMs: time.Now().UnixMilli()}, nil
}

// UpdateMyVipSettings 保留 proto optional 的 partial update 语义；未提交字段不能被零值 false 覆盖。
func (s *Server) UpdateMyVipSettings(ctx context.Context, req *walletv1.UpdateMyVipSettingsRequest) (*walletv1.UpdateMyVipSettingsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	var roomEntryNoticeEnabled *bool
	if req.RoomEntryNoticeEnabled != nil {
		value := req.GetRoomEntryNoticeEnabled()
		roomEntryNoticeEnabled = &value
	}
	var onlineGlobalNoticeEnabled *bool
	if req.OnlineGlobalNoticeEnabled != nil {
		value := req.GetOnlineGlobalNoticeEnabled()
		onlineGlobalNoticeEnabled = &value
	}
	var hideProfileDataEnabled *bool
	if req.HideProfileDataEnabled != nil {
		value := req.GetHideProfileDataEnabled()
		hideProfileDataEnabled = &value
	}
	var anonymousProfileVisitEnabled *bool
	if req.AnonymousProfileVisitEnabled != nil {
		value := req.GetAnonymousProfileVisitEnabled()
		anonymousProfileVisitEnabled = &value
	}
	var leaderboardInvisibleEnabled *bool
	if req.LeaderboardInvisibleEnabled != nil {
		value := req.GetLeaderboardInvisibleEnabled()
		leaderboardInvisibleEnabled = &value
	}
	settings, err := s.svc.UpdateVipUserSettings(ctx, ledger.UpdateVipUserSettingsCommand{
		AppCode: req.GetAppCode(), UserID: req.GetUserId(),
		RoomEntryNoticeEnabled: roomEntryNoticeEnabled, OnlineGlobalNoticeEnabled: onlineGlobalNoticeEnabled,
		HideProfileDataEnabled: hideProfileDataEnabled, AnonymousProfileVisitEnabled: anonymousProfileVisitEnabled,
		LeaderboardInvisibleEnabled: leaderboardInvisibleEnabled,
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.UpdateMyVipSettingsResponse{
		Settings: vipUserSettingsToProto(settings), ServerTimeMs: time.Now().UTC().UnixMilli(),
	}, nil
}

// GetMyCurrentVipDailyCoinRebate 返回当前 UTC 日可领取/已领取事实；未生成以 found=false 表达。
func (s *Server) GetMyCurrentVipDailyCoinRebate(ctx context.Context, req *walletv1.GetMyCurrentVipDailyCoinRebateRequest) (*walletv1.GetMyCurrentVipDailyCoinRebateResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	rebate, found, err := s.svc.GetMyCurrentVipDailyCoinRebate(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	response := &walletv1.GetMyCurrentVipDailyCoinRebateResponse{Found: found, ServerTimeMs: time.Now().UTC().UnixMilli()}
	if found {
		response.Rebate = vipDailyCoinRebateToProto(rebate)
	}
	return response, nil
}

// ListMyVipDailyCoinRebateStatuses 同时支持历史分页和通知按 rebate_ids 精确恢复；repository 始终附加 user_id 条件。
func (s *Server) ListMyVipDailyCoinRebateStatuses(ctx context.Context, req *walletv1.ListMyVipDailyCoinRebateStatusesRequest) (*walletv1.ListMyVipDailyCoinRebateStatusesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	items, total, err := s.svc.ListMyVipDailyCoinRebateStatuses(ctx, ledger.ListVipDailyCoinRebateStatusesQuery{
		AppCode: req.GetAppCode(), UserID: req.GetUserId(), Page: int(req.GetPage()),
		PageSize: int(req.GetPageSize()), RebateIDs: append([]string(nil), req.GetRebateIds()...),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	response := &walletv1.ListMyVipDailyCoinRebateStatusesResponse{
		Rebates: make([]*walletv1.VipDailyCoinRebate, 0, len(items)), Total: total,
		ServerTimeMs: time.Now().UTC().UnixMilli(),
	}
	for _, item := range items {
		response.Rebates = append(response.Rebates, vipDailyCoinRebateToProto(item))
	}
	return response, nil
}

// ClaimVipDailyCoinRebate 不接收金额；wallet owner 从锁内返现行读取并返回稳定账后余额。
func (s *Server) ClaimVipDailyCoinRebate(ctx context.Context, req *walletv1.ClaimVipDailyCoinRebateRequest) (*walletv1.ClaimVipDailyCoinRebateResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.ClaimVipDailyCoinRebate(ctx, ledger.ClaimVipDailyCoinRebateCommand{
		AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), RebateID: req.GetRebateId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.ClaimVipDailyCoinRebateResponse{
		Rebate: vipDailyCoinRebateToProto(receipt.Rebate), TransactionId: receipt.TransactionID,
		CoinBalance: balanceToProto(receipt.CoinBalance), ServerTimeMs: time.Now().UTC().UnixMilli(),
	}, nil
}

func vipToProto(vip ledger.UserVip) *walletv1.UserVip {
	return &walletv1.UserVip{
		UserId:        vip.UserID,
		Level:         vip.Level,
		Name:          vip.Name,
		Active:        vip.Active,
		StartedAtMs:   vip.StartedAtMS,
		ExpiresAtMs:   vip.ExpiresAtMS,
		UpdatedAtMs:   vip.UpdatedAtMS,
		ProgramType:   vip.ProgramType,
		ConfigVersion: vip.ConfigVersion,
	}
}

func vipDailyCoinRebateRunToProto(run ledger.VipDailyCoinRebateRun) *walletv1.VipDailyCoinRebateRun {
	return &walletv1.VipDailyCoinRebateRun{
		RunId: run.RunID, TaskDay: run.TaskDay, DayStartMs: run.DayStartMS, DayEndMs: run.DayEndMS,
		ConfigVersion: run.ConfigVersion, LevelAmountsJson: run.LevelAmountsJSON, Status: run.Status,
		LastUserId: run.LastUserID, ScannedCount: run.ScannedCount, CreatedCount: run.CreatedCount,
		CreatedAtMs: run.CreatedAtMS, UpdatedAtMs: run.UpdatedAtMS, CompletedAtMs: run.CompletedAtMS,
	}
}

func vipDailyCoinRebateToProto(rebate ledger.VipDailyCoinRebate) *walletv1.VipDailyCoinRebate {
	if rebate.RebateID == "" {
		return nil
	}
	return &walletv1.VipDailyCoinRebate{
		RebateId: rebate.RebateID, UserId: rebate.UserID, TaskDay: rebate.TaskDay,
		VipLevel: rebate.VipLevel, VipName: rebate.VipName, CoinAmount: rebate.CoinAmount,
		Status: rebate.Status, AvailableAtMs: rebate.AvailableAtMS, ExpiresAtMs: rebate.ExpiresAtMS,
		ConfigVersion: rebate.ConfigVersion, ClaimedAtMs: rebate.ClaimedAtMS,
		WalletTransactionId: rebate.WalletTransactionID, CreatedAtMs: rebate.CreatedAtMS, UpdatedAtMs: rebate.UpdatedAtMS,
	}
}

func vipUserSettingsToProto(settings ledger.VipUserSettings) *walletv1.VipUserSettings {
	return &walletv1.VipUserSettings{
		AppCode: settings.AppCode, UserId: settings.UserID,
		RoomEntryNoticeEnabled:       settings.RoomEntryNoticeEnabled,
		OnlineGlobalNoticeEnabled:    settings.OnlineGlobalNoticeEnabled,
		HideProfileDataEnabled:       settings.HideProfileDataEnabled,
		AnonymousProfileVisitEnabled: settings.AnonymousProfileVisitEnabled,
		LeaderboardInvisibleEnabled:  settings.LeaderboardInvisibleEnabled,
		UpdatedAtMs:                  settings.UpdatedAtMS,
	}
}

func vipLevelToProto(level ledger.VipLevel) *walletv1.VipLevel {
	return &walletv1.VipLevel{
		Level:                      level.Level,
		Name:                       level.Name,
		Status:                     level.Status,
		PriceCoin:                  level.PriceCoin,
		DurationMs:                 level.DurationMS,
		RewardResourceGroupId:      level.RewardResourceGroupID,
		RewardItems:                vipRewardItemsToProto(level.RewardItems),
		CanPurchase:                level.CanPurchase,
		SortOrder:                  level.SortOrder,
		RechargeGateRequired:       level.RechargeGateRequired,
		RequiredRechargeCoinAmount: level.RequiredRechargeCoinAmount,
		UserRechargeCoinAmount:     level.UserRechargeCoinAmount,
		PurchaseLockedReason:       level.PurchaseLockedReason,
		CreatedAtMs:                level.CreatedAtMS,
		UpdatedAtMs:                level.UpdatedAtMS,
		Benefits:                   vipBenefitsToProto(level.Benefits),
		ConfigVersion:              level.ConfigVersion,
	}
}

func vipProgramConfigToProto(config ledger.VipProgramConfig) *walletv1.VipProgramConfig {
	return &walletv1.VipProgramConfig{
		AppCode:                  config.AppCode,
		ProgramType:              config.ProgramType,
		LevelCount:               config.LevelCount,
		SameLevelExpiryPolicy:    config.SameLevelExpiryPolicy,
		UpgradeExpiryPolicy:      config.UpgradeExpiryPolicy,
		DowngradePurchasePolicy:  config.DowngradePurchasePolicy,
		BenefitInheritancePolicy: config.BenefitInheritancePolicy,
		GrantMode:                config.GrantMode,
		TrialCardEnabled:         config.TrialCardEnabled,
		Status:                   config.Status,
		ConfigVersion:            config.ConfigVersion,
		UpdatedByAdminId:         config.UpdatedByAdminID,
		CreatedAtMs:              config.CreatedAtMS,
		UpdatedAtMs:              config.UpdatedAtMS,
	}
}

func vipProgramConfigFromProto(config *walletv1.VipProgramConfig) ledger.VipProgramConfig {
	if config == nil {
		return ledger.VipProgramConfig{}
	}
	return ledger.VipProgramConfig{
		AppCode:                  config.GetAppCode(),
		ProgramType:              config.GetProgramType(),
		LevelCount:               config.GetLevelCount(),
		SameLevelExpiryPolicy:    config.GetSameLevelExpiryPolicy(),
		UpgradeExpiryPolicy:      config.GetUpgradeExpiryPolicy(),
		DowngradePurchasePolicy:  config.GetDowngradePurchasePolicy(),
		BenefitInheritancePolicy: config.GetBenefitInheritancePolicy(),
		GrantMode:                config.GetGrantMode(),
		TrialCardEnabled:         config.GetTrialCardEnabled(),
		Status:                   config.GetStatus(),
	}
}

func vipBenefitToProto(benefit ledger.VipBenefit) *walletv1.VipBenefit {
	if benefit.BenefitCode == "" {
		return nil
	}
	return &walletv1.VipBenefit{
		BenefitCode:    benefit.BenefitCode,
		Name:           benefit.Name,
		BenefitType:    benefit.BenefitType,
		UnlockLevel:    benefit.UnlockLevel,
		Status:         benefit.Status,
		TrialEnabled:   benefit.TrialEnabled,
		ResourceId:     benefit.ResourceID,
		ResourceType:   benefit.ResourceType,
		ExecutionScope: benefit.ExecutionScope,
		AutoEquip:      benefit.AutoEquip,
		SortOrder:      benefit.SortOrder,
		MetadataJson:   benefit.MetadataJSON,
		CreatedAtMs:    benefit.CreatedAtMS,
		UpdatedAtMs:    benefit.UpdatedAtMS,
		Presentation:   vipBenefitPresentationToProto(benefit.Presentation),
		Resource:       vipBenefitResourceToProto(benefit.Resource),
	}
}

// vipBenefitResourceToProto 只转换 repository 已完成 active/type 校验的资源投影。
// nil 必须继续保持 nil，使 Flutter 能区分“功能权益”与“装扮尚未完成真实资源配置”。
func vipBenefitResourceToProto(resource *ledger.VipBenefitResource) *walletv1.Resource {
	if resource == nil || resource.ResourceID <= 0 {
		return nil
	}
	return &walletv1.Resource{
		AppCode: resource.AppCode, ResourceId: resource.ResourceID, ResourceCode: resource.ResourceCode,
		ResourceType: resource.ResourceType, Name: resource.Name, Status: resource.Status,
		Grantable: resource.Grantable, ManagerGrantEnabled: resource.ManagerGrantEnabled,
		GrantStrategy: resource.GrantStrategy, WalletAssetType: resource.WalletAssetType,
		WalletAssetAmount: resource.WalletAssetAmount, PriceType: resource.PriceType,
		CoinPrice: resource.CoinPrice, GiftPointAmount: resource.GiftPointAmount,
		UsageScopes: resource.UsageScopes, AssetUrl: resource.AssetURL, PreviewUrl: resource.PreviewURL,
		AnimationUrl: resource.AnimationURL, MetadataJson: resource.MetadataJSON, SortOrder: resource.SortOrder,
		CreatedByUserId: resource.CreatedByUserID, UpdatedByUserId: resource.UpdatedByUserID,
		CreatedAtMs: resource.CreatedAtMS, UpdatedAtMs: resource.UpdatedAtMS,
	}
}

func vipBenefitsToProto(benefits []ledger.VipBenefit) []*walletv1.VipBenefit {
	items := make([]*walletv1.VipBenefit, 0, len(benefits))
	for _, benefit := range benefits {
		if item := vipBenefitToProto(benefit); item != nil {
			items = append(items, item)
		}
	}
	return items
}

func vipBenefitsFromProto(benefits []*walletv1.VipBenefit) []ledger.VipBenefit {
	items := make([]ledger.VipBenefit, 0, len(benefits))
	for _, benefit := range benefits {
		if benefit == nil {
			continue
		}
		items = append(items, ledger.VipBenefit{
			BenefitCode:    benefit.GetBenefitCode(),
			Name:           benefit.GetName(),
			BenefitType:    benefit.GetBenefitType(),
			UnlockLevel:    benefit.GetUnlockLevel(),
			Status:         benefit.GetStatus(),
			TrialEnabled:   benefit.GetTrialEnabled(),
			ResourceID:     benefit.GetResourceId(),
			ResourceType:   benefit.GetResourceType(),
			ExecutionScope: benefit.GetExecutionScope(),
			AutoEquip:      benefit.GetAutoEquip(),
			SortOrder:      benefit.GetSortOrder(),
			MetadataJSON:   benefit.GetMetadataJson(),
			Presentation:   vipBenefitPresentationFromProto(benefit.GetPresentation()),
		})
	}
	return items
}

func vipBenefitPresentationToProto(presentation *ledger.VipBenefitPresentation) *walletv1.VipBenefitPresentation {
	if presentation == nil {
		return nil
	}
	result := &walletv1.VipBenefitPresentation{
		Description:  presentation.Description,
		PreviewItems: make([]*walletv1.VipBenefitPreviewItem, 0, len(presentation.PreviewItems)),
	}
	for _, item := range presentation.PreviewItems {
		result.PreviewItems = append(result.PreviewItems, &walletv1.VipBenefitPreviewItem{
			PreviewId: item.PreviewID, Title: item.Title, MediaType: item.MediaType,
			AssetUrl: item.AssetURL, PreviewUrl: item.PreviewURL, AnimationUrl: item.AnimationURL,
			CompositionType: item.CompositionType, SortOrder: item.SortOrder,
		})
	}
	if presentation.NumericReward != nil {
		result.NumericReward = &walletv1.VipBenefitNumericReward{
			Label: presentation.NumericReward.Label, Value: presentation.NumericReward.Value,
			Unit: presentation.NumericReward.Unit, Period: presentation.NumericReward.Period,
		}
	}
	return result
}

func vipBenefitPresentationFromProto(presentation *walletv1.VipBenefitPresentation) *ledger.VipBenefitPresentation {
	if presentation == nil {
		return nil
	}
	result := &ledger.VipBenefitPresentation{
		Description:  presentation.GetDescription(),
		PreviewItems: make([]ledger.VipBenefitPreviewItem, 0, len(presentation.GetPreviewItems())),
	}
	for _, item := range presentation.GetPreviewItems() {
		if item == nil {
			continue
		}
		result.PreviewItems = append(result.PreviewItems, ledger.VipBenefitPreviewItem{
			PreviewID: item.GetPreviewId(), Title: item.GetTitle(), MediaType: item.GetMediaType(),
			AssetURL: item.GetAssetUrl(), PreviewURL: item.GetPreviewUrl(), AnimationURL: item.GetAnimationUrl(),
			CompositionType: item.GetCompositionType(), SortOrder: item.GetSortOrder(),
		})
	}
	if reward := presentation.GetNumericReward(); reward != nil {
		result.NumericReward = &ledger.VipBenefitNumericReward{
			Label: reward.GetLabel(), Value: reward.GetValue(), Unit: reward.GetUnit(), Period: reward.GetPeriod(),
		}
	}
	return result
}

func vipTrialCardToProto(card ledger.VipTrialCard) *walletv1.VipTrialCard {
	if card.TrialCardID == "" {
		return nil
	}
	return &walletv1.VipTrialCard{
		TrialCardId:         card.TrialCardID,
		EntitlementId:       card.EntitlementID,
		ResourceId:          card.ResourceID,
		UserId:              card.UserID,
		Level:               card.Level,
		Name:                card.Name,
		Status:              card.Status,
		Equipped:            card.Equipped,
		DurationMs:          card.DurationMS,
		EffectiveAtMs:       card.EffectiveAtMS,
		ExpiresAtMs:         card.ExpiresAtMS,
		RemainingDurationMs: card.RemainingDurationMS,
		GrantSource:         card.GrantSource,
		SourceGrantId:       card.SourceGrantID,
		CreatedAtMs:         card.CreatedAtMS,
		UpdatedAtMs:         card.UpdatedAtMS,
	}
}

func vipStateToProto(state ledger.VipState) *walletv1.VipState {
	return &walletv1.VipState{
		PaidVip: vipToProto(state.PaidVip),
		EquippedTrialCard: func() *walletv1.VipTrialCard {
			if state.EquippedTrialCard == nil {
				return nil
			}
			return vipTrialCardToProto(*state.EquippedTrialCard)
		}(),
		EffectiveVip:      vipToProto(state.EffectiveVip),
		EffectiveSource:   state.EffectiveSource,
		EffectiveBenefits: vipBenefitsToProto(state.EffectiveBenefits),
		EvaluatedAtMs:     state.EvaluatedAtMS,
		ProgramConfig:     vipProgramConfigToProto(state.ProgramConfig),
		UserSettings:      vipUserSettingsToProto(state.UserSettings),
	}
}

func vipRewardItemsToProto(items []ledger.VipRewardItem) []*walletv1.VipRewardItem {
	resp := make([]*walletv1.VipRewardItem, 0, len(items))
	for _, item := range items {
		resp = append(resp, &walletv1.VipRewardItem{
			ResourceId:   item.ResourceID,
			ResourceCode: item.ResourceCode,
			ResourceType: item.ResourceType,
			Name:         item.Name,
			Quantity:     item.Quantity,
			ExpiresAtMs:  item.ExpiresAtMS,
			AssetUrl:     item.AssetURL,
			PreviewUrl:   item.PreviewURL,
			AnimationUrl: item.AnimationURL,
		})
	}
	return resp
}
