package grpc

import (
	"context"

	"hyapp/pkg/xerr"
	hostservice "hyapp/services/user-service/internal/service/host"

	userv1 "hyapp.local/api/proto/user/v1"
)

// 这个文件保持薄传输层：只做 proto 与业务层的数据转换和错误映射。
// 角色授权、区域校验、幂等和事务都在业务层/持久化层，避免不同入口行为分叉。

// SearchAgencies 查询当前用户同国家可加入 Agency。
func (s *Server) SearchAgencies(ctx context.Context, req *userv1.SearchAgenciesRequest) (*userv1.SearchAgenciesResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	agencies, err := s.hostSvc.SearchAgencies(ctx, req.GetUserId(), req.GetKeyword(), req.GetPageSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.SearchAgenciesResponse{Agencies: make([]*userv1.Agency, 0, len(agencies))}
	for _, agency := range agencies {
		resp.Agencies = append(resp.Agencies, toProtoAgency(agency))
	}
	return resp, nil
}

// ApplyToAgency 创建用户加入 Agency 的待处理申请。
func (s *Server) ApplyToAgency(ctx context.Context, req *userv1.ApplyToAgencyRequest) (*userv1.ApplyToAgencyResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// command_id 和 user_id 来自 App 调用约定；当前阶段没有在传输层注入认证上下文。
	application, err := s.hostSvc.ApplyToAgency(ctx, hostservice.ApplyToAgencyInput{
		CommandID: req.GetCommandId(),
		UserID:    req.GetUserId(),
		AgencyID:  req.GetAgencyId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.ApplyToAgencyResponse{Application: toProtoAgencyApplication(application)}, nil
}

// ReviewAgencyApplication 审核用户加入 Agency 申请。
func (s *Server) ReviewAgencyApplication(ctx context.Context, req *userv1.ReviewAgencyApplicationRequest) (*userv1.ReviewAgencyApplicationResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// reviewer_user_id 只是传入业务层的操作者；是否为 Agency 拥有者在持久化层锁内确认。
	result, err := s.hostSvc.ReviewAgencyApplication(ctx, hostservice.ReviewAgencyApplicationInput{
		CommandID:      req.GetCommandId(),
		ReviewerUserID: req.GetReviewerUserId(),
		ApplicationID:  req.GetApplicationId(),
		Decision:       req.GetDecision(),
		Reason:         req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.ReviewAgencyApplicationResponse{
		Application: toProtoAgencyApplication(result.Application),
		HostProfile: toProtoHostProfile(result.HostProfile),
		Membership:  toProtoAgencyMembership(result.Membership),
	}, nil
}

// KickAgencyHost 结束普通 Host 的 Agency 归属，并按领域规则返回移除后的 Host 身份状态。
func (s *Server) KickAgencyHost(ctx context.Context, req *userv1.KickAgencyHostRequest) (*userv1.KickAgencyHostResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// 传输层不判断拥有者和成员关系，防止未来 HTTP/任务入口复制一份不一致规则。
	result, err := s.hostSvc.KickAgencyHost(ctx, hostservice.KickAgencyHostInput{
		CommandID:      req.GetCommandId(),
		OperatorUserID: req.GetOperatorUserId(),
		AgencyID:       req.GetAgencyId(),
		HostUserID:     req.GetHostUserId(),
		Reason:         req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.KickAgencyHostResponse{
		Membership:  toProtoAgencyMembership(result.Membership),
		HostProfile: toProtoHostProfile(result.HostProfile),
	}, nil
}

// InviteAgency 创建用户成为 Agency owner 的待确认邀请，目标接受后才创建关系事实。
func (s *Server) InviteAgency(ctx context.Context, req *userv1.InviteAgencyRequest) (*userv1.InviteAgencyResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// 邀请人的 BD 身份、App 级区域策略和目标身份冲突都由业务事务校验。
	invitation, err := s.hostSvc.InviteAgency(ctx, hostservice.InviteAgencyInput{
		CommandID:     req.GetCommandId(),
		InviterUserID: req.GetInviterUserId(),
		TargetUserID:  req.GetTargetUserId(),
		AgencyName:    req.GetAgencyName(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.InviteAgencyResponse{Invitation: toProtoRoleInvitation(invitation)}, nil
}

// InviteBD 创建用户成为普通 BD 的待确认邀请，只有 BD 负责人可调用。
func (s *Server) InviteBD(ctx context.Context, req *userv1.InviteBDRequest) (*userv1.InviteBDResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// BD 负责人授权在持久化层内基于 bd_leader_profiles 当前事实判断。
	invitation, err := s.hostSvc.InviteBD(ctx, hostservice.InviteBDInput{
		CommandID:     req.GetCommandId(),
		InviterUserID: req.GetInviterUserId(),
		TargetUserID:  req.GetTargetUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.InviteBDResponse{Invitation: toProtoRoleInvitation(invitation)}, nil
}

// InviteHost 创建用户加入 Agency 的待确认邀请，只有 active Agency owner 可调用。
func (s *Server) InviteHost(ctx context.Context, req *userv1.InviteHostRequest) (*userv1.InviteHostResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// 发起人是否拥有 active Agency、目标是否同区和是否已有归属都在持久化事务内锁定判断。
	invitation, err := s.hostSvc.InviteHost(ctx, hostservice.InviteHostInput{
		CommandID:     req.GetCommandId(),
		InviterUserID: req.GetInviterUserId(),
		TargetUserID:  req.GetTargetUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.InviteHostResponse{Invitation: toProtoRoleInvitation(invitation)}, nil
}

// ListBDLeaderBDs 返回当前 BD Leader 直属 BD 列表。
func (s *Server) ListBDLeaderBDs(ctx context.Context, req *userv1.ListBDLeaderBDsRequest) (*userv1.ListBDLeaderBDsResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	profiles, err := s.hostSvc.ListBDLeaderBDs(ctx, hostservice.ListBDLeaderBDsCommand{
		LeaderUserID: req.GetLeaderUserId(),
		Status:       req.GetStatus(),
		PageSize:     int(req.GetPageSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.ListBDLeaderBDsResponse{BdProfiles: make([]*userv1.BDProfile, 0, len(profiles))}
	for _, profile := range profiles {
		resp.BdProfiles = append(resp.BdProfiles, toProtoBDProfile(profile))
	}
	return resp, nil
}

// ListBDLeaderAgencies 返回当前 BD Leader 团队下的 Agency 列表。
func (s *Server) ListBDLeaderAgencies(ctx context.Context, req *userv1.ListBDLeaderAgenciesRequest) (*userv1.ListBDLeaderAgenciesResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	agencies, err := s.hostSvc.ListBDLeaderAgencies(ctx, hostservice.ListBDLeaderAgenciesCommand{
		LeaderUserID: req.GetLeaderUserId(),
		Status:       req.GetStatus(),
		PageSize:     int(req.GetPageSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.ListBDLeaderAgenciesResponse{Agencies: make([]*userv1.Agency, 0, len(agencies))}
	for _, agency := range agencies {
		resp.Agencies = append(resp.Agencies, toProtoAgency(agency))
	}
	return resp, nil
}

// ListBDAgencies 返回当前 BD 自己拓展的 Agency 列表。
func (s *Server) ListBDAgencies(ctx context.Context, req *userv1.ListBDAgenciesRequest) (*userv1.ListBDAgenciesResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	agencies, err := s.hostSvc.ListBDAgencies(ctx, hostservice.ListBDAgenciesCommand{
		BDUserID: req.GetBdUserId(),
		Status:   req.GetStatus(),
		PageSize: int(req.GetPageSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.ListBDAgenciesResponse{Agencies: make([]*userv1.Agency, 0, len(agencies))}
	for _, agency := range agencies {
		resp.Agencies = append(resp.Agencies, toProtoAgency(agency))
	}
	return resp, nil
}

// ListManagerTeamAgencies 返回经理团队树内全部有效 Agency；经理停用时由业务层返回 PermissionDenied。
func (s *Server) ListManagerTeamAgencies(ctx context.Context, req *userv1.ListManagerTeamAgenciesRequest) (*userv1.ListManagerTeamAgenciesResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	result, err := s.hostSvc.ListManagerTeamAgencies(ctx, hostservice.ListManagerTeamAgenciesCommand{
		ManagerUserID: req.GetManagerUserId(),
		PageSize:      int(req.GetPageSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.ListManagerTeamAgenciesResponse{
		Agencies:       make([]*userv1.ManagerTeamAgency, 0, len(result.Agencies)),
		TotalBdLeaders: int32(result.TotalBDLeaders),
		TotalBds:       int32(result.TotalBDs),
		Truncated:      result.Truncated,
	}
	for _, agency := range result.Agencies {
		resp.Agencies = append(resp.Agencies, &userv1.ManagerTeamAgency{
			AgencyId:       agency.AgencyID,
			OwnerUserId:    agency.OwnerUserID,
			ParentBdUserId: agency.ParentBDUserID,
		})
	}
	return resp, nil
}

// ListRoleInvitations 返回目标用户收到的角色邀请消息。
func (s *Server) ListRoleInvitations(ctx context.Context, req *userv1.ListRoleInvitationsRequest) (*userv1.ListRoleInvitationsResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// 查询只接受 target_user_id，不允许调用方按 inviter 任意枚举别人的消息。
	invitations, err := s.hostSvc.ListRoleInvitations(ctx, hostservice.ListRoleInvitationsCommand{
		TargetUserID: req.GetTargetUserId(),
		Status:       req.GetStatus(),
		PageSize:     int(req.GetPageSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.ListRoleInvitationsResponse{Invitations: make([]*userv1.RoleInvitation, 0, len(invitations))}
	for _, invitation := range invitations {
		resp.Invitations = append(resp.Invitations, toProtoRoleInvitation(invitation))
	}
	return resp, nil
}

// GetRoleInvitation 返回一条邀请事实，用于 action-confirm 以 user-service 的 owner 状态修复确认消息。
func (s *Server) GetRoleInvitation(ctx context.Context, req *userv1.GetRoleInvitationRequest) (*userv1.GetRoleInvitationResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// actor_user_id 必须是邀请发起人或目标用户；权限判断下沉到业务层，避免内部调用绕过边界。
	invitation, err := s.hostSvc.GetRoleInvitation(ctx, req.GetActorUserId(), req.GetInvitationId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.GetRoleInvitationResponse{Invitation: toProtoRoleInvitation(invitation)}, nil
}

// ProcessRoleInvitation 处理 Host、Agency 或 BD 邀请。
func (s *Server) ProcessRoleInvitation(ctx context.Context, req *userv1.ProcessRoleInvitationRequest) (*userv1.ProcessRoleInvitationResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	// actor_user_id 的含义由动作决定：取消是邀请人，接受/拒绝是目标用户。
	result, err := s.hostSvc.ProcessRoleInvitation(ctx, hostservice.ProcessRoleInvitationInput{
		CommandID:    req.GetCommandId(),
		ActorUserID:  req.GetActorUserId(),
		InvitationID: req.GetInvitationId(),
		Action:       req.GetAction(),
		Reason:       req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.ProcessRoleInvitationResponse{
		Invitation:  toProtoRoleInvitation(result.Invitation),
		HostProfile: toProtoHostProfile(result.HostProfile),
		Agency:      toProtoAgency(result.Agency),
		Membership:  toProtoAgencyMembership(result.Membership),
		BdProfile:   toProtoBDProfile(result.BDProfile),
	}, nil
}

// GetHostProfile 返回当前用户主播身份事实。
func (s *Server) GetHostProfile(ctx context.Context, req *userv1.GetHostProfileRequest) (*userv1.GetHostProfileResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	profile, err := s.hostSvc.GetHostProfile(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.GetHostProfileResponse{HostProfile: toProtoHostProfile(profile)}, nil
}

// BatchGetHostProfiles 一次返回多个用户的主播身份事实，缺失用户不返回占位对象。
func (s *Server) BatchGetHostProfiles(ctx context.Context, req *userv1.BatchGetHostProfilesRequest) (*userv1.BatchGetHostProfilesResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	profiles, err := s.hostSvc.BatchGetHostProfiles(ctx, req.GetUserIds())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	result := make(map[int64]*userv1.HostProfile, len(profiles))
	for userID, profile := range profiles {
		// map key 固定使用 user_id，调用方按原 target 顺序重新组装业务快照。
		result[userID] = toProtoHostProfile(profile)
	}
	return &userv1.BatchGetHostProfilesResponse{HostProfiles: result}, nil
}

// GetBDProfile 按调用方声明的角色返回普通 BD 或 BD Leader 身份事实。
func (s *Server) GetBDProfile(ctx context.Context, req *userv1.GetBDProfileRequest) (*userv1.GetBDProfileResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	profile, err := s.hostSvc.GetBDProfile(ctx, req.GetUserId(), req.GetRole())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.GetBDProfileResponse{BdProfile: toProtoBDProfile(profile)}, nil
}

// GetCoinSellerProfile 返回当前用户币商身份事实。
func (s *Server) GetCoinSellerProfile(ctx context.Context, req *userv1.GetCoinSellerProfileRequest) (*userv1.GetCoinSellerProfileResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	profile, err := s.hostSvc.GetCoinSellerProfile(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.GetCoinSellerProfileResponse{CoinSellerProfile: toProtoCoinSellerProfile(profile)}, nil
}

// ListActiveCoinSellersInMyRegion 返回当前用户同区域启用币商。
func (s *Server) ListActiveCoinSellersInMyRegion(ctx context.Context, req *userv1.ListActiveCoinSellersInMyRegionRequest) (*userv1.ListActiveCoinSellersInMyRegionResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	items, err := s.hostSvc.ListActiveCoinSellersInMyRegion(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.ListActiveCoinSellersInMyRegionResponse{CoinSellers: make([]*userv1.CoinSellerListItem, 0, len(items))}
	for _, item := range items {
		resp.CoinSellers = append(resp.CoinSellers, toProtoCoinSellerListItem(item))
	}
	return resp, nil
}

// CreateSubCoinSeller 处理父币商在 H5 币商中心搜索直开直属子币商。
func (s *Server) CreateSubCoinSeller(ctx context.Context, req *userv1.CreateSubCoinSellerRequest) (*userv1.CreateSubCoinSellerResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	result, err := s.hostSvc.CreateSubCoinSeller(ctx, hostservice.CreateSubCoinSellerInput{
		CommandID:    req.GetCommandId(),
		ParentUserID: req.GetParentUserId(),
		ChildUserID:  req.GetChildUserId(),
		RequestID:    req.GetMeta().GetRequestId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CreateSubCoinSellerResponse{
		Relation: toProtoCoinSellerSubRelation(result.Relation),
		Child:    toProtoCoinSellerListItem(result.Child),
	}, nil
}

// SubmitSubCoinSellerApplication 处理父币商邀请子币商申请；提交后等待后台审批，不立即创建关系。
func (s *Server) SubmitSubCoinSellerApplication(ctx context.Context, req *userv1.SubmitSubCoinSellerApplicationRequest) (*userv1.SubmitSubCoinSellerApplicationResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	result, err := s.hostSvc.SubmitSubCoinSellerApplication(ctx, hostservice.SubmitSubCoinSellerApplicationInput{
		CommandID:    req.GetCommandId(),
		ParentUserID: req.GetParentUserId(),
		TargetUserID: req.GetTargetUserId(),
		RequestID:    req.GetMeta().GetRequestId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.SubmitSubCoinSellerApplicationResponse{
		Application: toProtoCoinSellerSubApplication(result.Application),
	}, nil
}

// ListSubCoinSellers 返回父币商直属子币商分页列表。
func (s *Server) ListSubCoinSellers(ctx context.Context, req *userv1.ListSubCoinSellersRequest) (*userv1.ListSubCoinSellersResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	result, err := s.hostSvc.ListSubCoinSellers(ctx, hostservice.ListSubCoinSellersCommand{
		ParentUserID: req.GetParentUserId(),
		Page:         int(req.GetPage()),
		PageSize:     int(req.GetPageSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &userv1.ListSubCoinSellersResponse{
		Children: make([]*userv1.CoinSellerListItem, 0, len(result.Items)),
		Total:    result.Total,
		Page:     int32(result.Page),
		PageSize: int32(result.PageSize),
	}
	for _, item := range result.Items {
		resp.Children = append(resp.Children, toProtoCoinSellerListItem(item))
	}
	return resp, nil
}

// CheckCoinSellerSubRelation 校验父子币商关系和父级权限，供 gateway 子币商库存转账前调用。
func (s *Server) CheckCoinSellerSubRelation(ctx context.Context, req *userv1.CheckCoinSellerSubRelationRequest) (*userv1.CheckCoinSellerSubRelationResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	child, err := s.hostSvc.CheckCoinSellerSubRelation(ctx, hostservice.CheckCoinSellerSubRelationCommand{
		ParentUserID: req.GetParentUserId(),
		ChildUserID:  req.GetChildUserId(),
	})
	if err != nil {
		return &userv1.CheckCoinSellerSubRelationResponse{Allowed: false, Reason: string(xerr.CodeOf(err))}, xerr.ToGRPCError(err)
	}
	return &userv1.CheckCoinSellerSubRelationResponse{Allowed: true, Child: toProtoCoinSellerListItem(child)}, nil
}

// GetUserRoleSummary 返回 App 入口显隐所需的角色摘要，替代 gateway 多角色 fanout。
func (s *Server) GetUserRoleSummary(ctx context.Context, req *userv1.GetUserRoleSummaryRequest) (*userv1.GetUserRoleSummaryResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	summary, err := s.hostSvc.GetUserRoleSummary(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.GetUserRoleSummaryResponse{Summary: toProtoUserRoleSummary(summary)}, nil
}

// GetAgency 返回 Agency 当前事实。
func (s *Server) GetAgency(ctx context.Context, req *userv1.GetAgencyRequest) (*userv1.GetAgencyResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	agency, err := s.hostSvc.GetAgency(ctx, req.GetAgencyId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &userv1.GetAgencyResponse{Agency: toProtoAgency(agency)}, nil
}

// UpdateAgencyProfile 修改 Agency 独立资料；用户资料和 Agency 资料不会互相回写。
func (s *Server) UpdateAgencyProfile(ctx context.Context, req *userv1.UpdateAgencyProfileRequest) (*userv1.UpdateAgencyProfileResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	agency, err := s.hostSvc.UpdateAgencyProfile(ctx, hostservice.UpdateAgencyProfileInput{
		CommandID:      req.GetCommandId(),
		OperatorUserID: req.GetOperatorUserId(),
		AgencyID:       req.GetAgencyId(),
		Name:           req.Name,
		Avatar:         req.Avatar,
		RequestID:      req.GetMeta().GetRequestId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.UpdateAgencyProfileResponse{Agency: toProtoAgency(agency)}, nil
}

// CheckBusinessCapability 返回 H5/App 业务入口可用性，具体身份规则由 host service 统一判断。
func (s *Server) CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	allowed, reason, err := s.hostSvc.CheckBusinessCapability(ctx, req.GetActorUserId(), req.GetCapability())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CheckBusinessCapabilityResponse{Allowed: allowed, Reason: reason}, nil
}

// GetRoleScopePolicy 暴露 App 级角色范围元数据，让 gateway/admin 只消费策略结果而不复制 app_code 分支。
func (s *Server) GetRoleScopePolicy(ctx context.Context, req *userv1.GetRoleScopePolicyRequest) (*userv1.GetRoleScopePolicyResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	policy, err := s.hostSvc.ResolveRoleScopePolicy(ctx, req.GetScene())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.GetRoleScopePolicyResponse{Policy: toProtoRoleScopePolicy(policy)}, nil
}

// UpdateRoleScopePolicies 只做协议转换；完整快照校验和 user DB 原子写入由 Host service/repository 负责。
func (s *Server) UpdateRoleScopePolicies(ctx context.Context, req *userv1.UpdateRoleScopePoliciesRequest) (*userv1.UpdateRoleScopePoliciesResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	policies := make([]hostservice.RoleScopePolicy, 0, len(req.GetPolicies()))
	for _, policy := range req.GetPolicies() {
		if policy == nil {
			continue
		}
		policies = append(policies, hostservice.RoleScopePolicy{
			Scene:                       policy.GetScene(),
			BaseScope:                   hostservice.RoleScope(policy.GetBaseScope()),
			ExpandedScope:               hostservice.RoleScope(policy.GetExpandedScope()),
			RegionExpansionConfigurable: policy.GetRegionExpansionConfigurable(),
		})
	}
	stored, err := s.hostSvc.UpdateRoleScopePolicies(ctx, policies)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	response := &userv1.UpdateRoleScopePoliciesResponse{Policies: make([]*userv1.RoleScopePolicy, 0, len(stored))}
	for _, policy := range stored {
		response.Policies = append(response.Policies, toProtoRoleScopePolicy(policy))
	}
	return response, nil
}

func toProtoRoleScopePolicy(policy hostservice.RoleScopePolicy) *userv1.RoleScopePolicy {
	return &userv1.RoleScopePolicy{
		Scene:                       policy.Scene,
		BaseScope:                   string(policy.BaseScope),
		ExpandedScope:               string(policy.ExpandedScope),
		RegionExpansionConfigurable: policy.RegionExpansionConfigurable,
		UpdatedAtMs:                 policy.UpdatedAtMS,
	}
}

// GetAgencyMembers 返回 Agency 成员关系事实列表。
func (s *Server) GetAgencyMembers(ctx context.Context, req *userv1.GetAgencyMembersRequest) (*userv1.GetAgencyMembersResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	memberships, err := s.hostSvc.ListAgencyMembers(ctx, req.GetAgencyId(), req.GetStatus())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.GetAgencyMembersResponse{Memberships: make([]*userv1.AgencyMembership, 0, len(memberships))}
	for _, membership := range memberships {
		resp.Memberships = append(resp.Memberships, toProtoAgencyMembership(membership))
	}
	return resp, nil
}

// GetAgencyApplications 返回 Agency 申请事实列表。
func (s *Server) GetAgencyApplications(ctx context.Context, req *userv1.GetAgencyApplicationsRequest) (*userv1.GetAgencyApplicationsResponse, error) {
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	ctx = contextWithApp(ctx, req.GetMeta())
	applications, err := s.hostSvc.ListAgencyApplications(ctx, req.GetAgencyId(), req.GetStatus())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	resp := &userv1.GetAgencyApplicationsResponse{Applications: make([]*userv1.AgencyApplication, 0, len(applications))}
	for _, application := range applications {
		resp.Applications = append(resp.Applications, toProtoAgencyApplication(application))
	}
	return resp, nil
}
