// Package grpc 将 user-service 认证、用户和短号用例适配成 protobuf gRPC 服务。
package grpc

import (
	userv1 "hyapp.local/api/proto/user/v1"
	authdomain "hyapp/services/user-service/internal/domain/auth"
	hostdomain "hyapp/services/user-service/internal/domain/host"
	invitedomain "hyapp/services/user-service/internal/domain/invite"
	mictimedomain "hyapp/services/user-service/internal/domain/mictime"
	userdomain "hyapp/services/user-service/internal/domain/user"
	authservice "hyapp/services/user-service/internal/service/auth"
	userservice "hyapp/services/user-service/internal/service/user"
)

// toProtoApp 把 App 注册表事实转换为 protobuf 投影。
func toProtoApp(app userdomain.App) *userv1.App {
	return &userv1.App{
		AppId:       app.AppID,
		AppCode:     app.AppCode,
		AppName:     app.AppName,
		PackageName: app.PackageName,
		Platform:    app.Platform,
		Status:      app.Status,
		CreatedAtMs: app.CreatedAtMs,
		UpdatedAtMs: app.UpdatedAtMs,
	}
}

// toProtoToken 把领域令牌转换为 protobuf 响应。
func toProtoToken(token authdomain.Token) *userv1.AuthToken {
	// Token 中包含 refresh token 原文，只能出现在登录/刷新响应路径。
	return &userv1.AuthToken{
		AppCode:                  token.AppCode,
		UserId:                   token.UserID,
		DisplayUserId:            token.DisplayUserID,
		DefaultDisplayUserId:     token.DefaultDisplayUserID,
		DisplayUserIdKind:        token.DisplayUserIDKind,
		DisplayUserIdExpiresAtMs: token.DisplayUserIDExpiresAtMs,
		ProfileCompleted:         token.ProfileCompleted,
		OnboardingStatus:         token.OnboardingStatus,
		SessionId:                token.SessionID,
		AccessToken:              token.AccessToken,
		RefreshToken:             token.RefreshToken,
		ExpiresInSec:             token.ExpiresInSec,
		TokenType:                token.TokenType,
	}
}

// toProtoUser 把领域用户转换为 protobuf 用户投影。
func toProtoUser(user userdomain.User) *userv1.User {
	// User proto 只暴露用户主状态和当前展示短号，不包含密码、三方身份或房间状态。
	return &userv1.User{
		AppCode:                  user.AppCode,
		UserId:                   user.UserID,
		Status:                   toProtoStatus(user.Status),
		CreatedAtMs:              user.CreatedAtMs,
		UpdatedAtMs:              user.UpdatedAtMs,
		DisplayUserId:            user.CurrentDisplayUserID,
		DefaultDisplayUserId:     user.DefaultDisplayUserID,
		DisplayUserIdKind:        string(user.CurrentDisplayUserIDKind),
		DisplayUserIdExpiresAtMs: user.CurrentDisplayUserIDExpiresAtMs,
		Username:                 user.Username,
		Gender:                   user.Gender,
		Country:                  user.Country,
		Avatar:                   user.Avatar,
		Birth:                    user.BirthDate,
		Language:                 user.Language,
		CountryId:                user.CountryID,
		CountryName:              user.CountryName,
		CountryDisplayName:       user.CountryDisplayName,
		RegionId:                 user.RegionID,
		RegionCode:               user.RegionCode,
		RegionName:               user.RegionName,
		ProfileCompleted:         user.ProfileCompleted,
		ProfileCompletedAtMs:     user.ProfileCompletedAtMs,
		OnboardingStatus:         string(user.OnboardingStatus),
		IsoNumeric:               user.ISONumeric,
		PhoneCountryCode:         user.PhoneCountryCode,
		CountryEnabled:           user.CountryEnabled,
		Invite:                   toProtoInviteOverview(user.InviteOverview),
	}
}

func toProtoBusinessUserLookupItem(item userdomain.BusinessUserLookupItem) *userv1.BusinessUserLookupItem {
	return &userv1.BusinessUserLookupItem{
		UserId:        item.UserID,
		DisplayUserId: item.DisplayUserID,
		Username:      item.Username,
		Avatar:        item.Avatar,
		Status:        toProtoStatus(item.Status),
		RegionId:      item.RegionID,
		RegionCode:    item.RegionCode,
		RegionName:    item.RegionName,
	}
}

func toProtoManagerUserBlock(block userservice.ManagerUserBlock) *userv1.ManagerUserBlock {
	// 经理封禁记录是 H5 弹窗的列表数据；这里仅暴露可展示和可操作的稳定字段。
	return &userv1.ManagerUserBlock{
		BlockId:             block.BlockID,
		ManagerUserId:       block.ManagerUserID,
		TargetUserId:        block.TargetUserID,
		TargetDisplayUserId: block.TargetDisplayUserID,
		TargetUsername:      block.TargetUsername,
		TargetAvatar:        block.TargetAvatar,
		Country:             block.Country,
		BlockedUntilMs:      block.BlockedUntilMS,
		Status:              block.Status,
		Reason:              block.Reason,
		CreatedAtMs:         block.CreatedAtMS,
		UpdatedAtMs:         block.UpdatedAtMS,
	}
}

func toProtoInviteOverview(overview invitedomain.Overview) *userv1.InviteOverview {
	if overview.MyInviteCode == "" && !overview.InviteEnabled && overview.InviteCount == 0 && overview.ValidInviteCount == 0 && overview.ValidInviteThresholdCoin == 0 {
		return nil
	}
	return &userv1.InviteOverview{
		MyInviteCode:             overview.MyInviteCode,
		InviteEnabled:            overview.InviteEnabled,
		InviteCount:              overview.InviteCount,
		ValidInviteCount:         overview.ValidInviteCount,
		ValidInviteThresholdCoin: overview.ValidInviteThresholdCoin,
	}
}

func toProtoInviteBinding(binding invitedomain.Binding) *userv1.InviteBinding {
	if !binding.Bound && binding.InviteCode == "" && binding.InviterUserID == 0 {
		return nil
	}
	return &userv1.InviteBinding{
		Bound:         binding.Bound,
		InviteCode:    binding.InviteCode,
		InviterUserId: binding.InviterUserID,
	}
}

func toProtoMicLifetimeStats(stats mictimedomain.LifetimeStats) *userv1.UserMicLifetimeStats {
	return &userv1.UserMicLifetimeStats{
		AppCode:        stats.AppCode,
		UserId:         stats.UserID,
		SeatOccupiedMs: stats.SeatOccupiedMs,
		MicOnlineMs:    stats.MicOnlineMs,
		SessionCount:   stats.SessionCount,
		FirstMicAtMs:   stats.FirstMicAtMs,
		LastMicAtMs:    stats.LastMicAtMs,
		UpdatedAtMs:    stats.UpdatedAtMs,
	}
}

func toProtoProfileStats(stats userdomain.ProfileStats) *userv1.UserProfileStats {
	return &userv1.UserProfileStats{
		AppCode:        stats.AppCode,
		UserId:         stats.UserID,
		VisitorsCount:  stats.VisitorsCount,
		FollowingCount: stats.FollowingCount,
		FriendsCount:   stats.FriendsCount,
		UpdatedAtMs:    stats.UpdatedAtMs,
	}
}

func toProtoProfileVisitRecord(record userdomain.ProfileVisitRecord) *userv1.ProfileVisitRecord {
	return &userv1.ProfileVisitRecord{
		VisitorUserId:   record.VisitorUserID,
		TargetUserId:    record.TargetUserID,
		VisitCount:      record.VisitCount,
		LastVisitedAtMs: record.LastVisitedAtMs,
	}
}

func toProtoFollowRecord(record userdomain.FollowRecord) *userv1.FollowRecord {
	return &userv1.FollowRecord{
		FollowerUserId: record.FollowerUserID,
		FolloweeUserId: record.FolloweeUserID,
		FollowedAtMs:   record.FollowedAtMs,
	}
}

func toProtoFriendRecord(record userdomain.FriendRecord) *userv1.FriendRecord {
	return &userv1.FriendRecord{
		UserId:       record.UserID,
		FriendUserId: record.FriendUserID,
		FriendedAtMs: record.FriendedAtMs,
	}
}

func toProtoFriendApplication(application userdomain.FriendApplication) *userv1.FriendApplication {
	return &userv1.FriendApplication{
		RequesterUserId: application.RequesterUserID,
		TargetUserId:    application.TargetUserID,
		Status:          application.Status,
		CreatedAtMs:     application.CreatedAtMs,
		UpdatedAtMs:     application.UpdatedAtMs,
	}
}

func toProtoUserReport(report userdomain.Report) *userv1.UserReport {
	return &userv1.UserReport{
		ReportId:       report.ReportID,
		ReporterUserId: report.ReporterUserID,
		TargetType:     report.TargetType,
		UserId:         report.UserID,
		RoomId:         report.RoomID,
		ReportType:     report.ReportType,
		Reason:         report.Reason,
		ImageUrls:      append([]string{}, report.ImageURLs...),
		Status:         report.Status,
		CreatedAtMs:    report.CreatedAtMs,
	}
}

// toProtoCountry 把国家领域对象转换为管理 RPC 响应。
func toProtoCountry(country userdomain.Country) *userv1.Country {
	return &userv1.Country{
		AppCode:            country.AppCode,
		CountryId:          country.CountryID,
		CountryName:        country.CountryName,
		CountryCode:        country.CountryCode,
		CountryDisplayName: country.CountryDisplayName,
		SortOrder:          country.SortOrder,
		CreatedAtMs:        country.CreatedAtMs,
		UpdatedAtMs:        country.UpdatedAtMs,
		IsoAlpha3:          country.ISOAlpha3,
		IsoNumeric:         country.ISONumeric,
		PhoneCountryCode:   country.PhoneCountryCode,
		Enabled:            country.Enabled,
		Flag:               country.Flag,
	}
}

// toProtoRegion 把区域领域对象转换为管理 RPC 响应。
func toProtoRegion(region userdomain.Region) *userv1.Region {
	return &userv1.Region{
		AppCode:     region.AppCode,
		RegionId:    region.RegionID,
		RegionCode:  region.RegionCode,
		Name:        region.Name,
		Status:      region.Status,
		Countries:   append([]string{}, region.Countries...),
		SortOrder:   region.SortOrder,
		CreatedAtMs: region.CreatedAtMs,
		UpdatedAtMs: region.UpdatedAtMs,
	}
}

// toProtoIdentity 把短号领域投影转换为 protobuf 响应。
func toProtoIdentity(identity userdomain.Identity) *userv1.UserIdentity {
	// Identity proto 用于短号解析和修改结果，字段语义与领域 Identity 保持一致。
	return &userv1.UserIdentity{
		AppCode:                  identity.AppCode,
		UserId:                   identity.UserID,
		DisplayUserId:            identity.DisplayUserID,
		Status:                   string(identity.Status),
		DefaultDisplayUserId:     identity.DefaultDisplayUserID,
		DisplayUserIdKind:        string(identity.DisplayUserIDKind),
		DisplayUserIdExpiresAtMs: identity.DisplayUserIDExpiresAtMs,
	}
}

// toProtoStatus 固定未知状态的安全默认值。
func toProtoStatus(status userdomain.Status) userv1.UserStatus {
	// 状态枚举转换集中在这里，未知状态使用 UNSPECIFIED 安全兜底。
	switch status {
	case userdomain.StatusActive:
		return userv1.UserStatus_USER_STATUS_ACTIVE
	case userdomain.StatusDisabled:
		return userv1.UserStatus_USER_STATUS_DISABLED
	case userdomain.StatusBanned:
		return userv1.UserStatus_USER_STATUS_BANNED
	default:
		return userv1.UserStatus_USER_STATUS_UNSPECIFIED
	}
}

func fromProtoStatus(status userv1.UserStatus) userdomain.Status {
	switch status {
	case userv1.UserStatus_USER_STATUS_ACTIVE:
		return userdomain.StatusActive
	case userv1.UserStatus_USER_STATUS_DISABLED:
		return userdomain.StatusDisabled
	case userv1.UserStatus_USER_STATUS_BANNED:
		return userdomain.StatusBanned
	default:
		return ""
	}
}

// authMeta 提取认证链路审计所需的 gateway 透传字段。
func authMeta(meta *userv1.RequestMeta) authservice.Meta {
	if meta == nil {
		// meta 可选，缺失时审计字段为空但不能让 RPC panic。
		return authservice.Meta{}
	}

	// 只提取认证审计需要的字段，device_id 在各 RPC 中单独传入。
	return authservice.Meta{
		AppCode:     meta.GetAppCode(),
		RequestID:   meta.GetRequestId(),
		ClientIP:    meta.GetClientIp(),
		UserAgent:   meta.GetUserAgent(),
		CountryByIP: meta.GetCountryByIp(),
		Platform:    meta.GetPlatform(),
		Language:    meta.GetLanguage(),
		Timezone:    meta.GetTimezone(),
	}
}

// toProtoHostProfile 把 host profile 事实转换为内部 gRPC 投影。
func toProtoHostProfile(profile hostdomain.HostProfile) *userv1.HostProfile {
	if profile.UserID == 0 {
		return nil
	}
	return &userv1.HostProfile{
		UserId:                   profile.UserID,
		Status:                   profile.Status,
		RegionId:                 profile.RegionID,
		CurrentAgencyId:          profile.CurrentAgencyID,
		CurrentMembershipId:      profile.CurrentMembershipID,
		CurrentAgencyOwnerUserId: profile.CurrentAgencyOwnerUserID,
		Source:                   profile.Source,
		FirstBecameHostAtMs:      profile.FirstBecameHostAtMs,
		CreatedAtMs:              profile.CreatedAtMs,
		UpdatedAtMs:              profile.UpdatedAtMs,
	}
}

// toProtoAgency 把 Agency 事实转换为内部 gRPC 投影。
func toProtoAgency(agency hostdomain.Agency) *userv1.Agency {
	if agency.AgencyID == 0 {
		return nil
	}
	return &userv1.Agency{
		AgencyId:        agency.AgencyID,
		OwnerUserId:     agency.OwnerUserID,
		RegionId:        agency.RegionID,
		ParentBdUserId:  agency.ParentBDUserID,
		Name:            agency.Name,
		Status:          agency.Status,
		JoinEnabled:     agency.JoinEnabled,
		MaxHosts:        agency.MaxHosts,
		CreatedByUserId: agency.CreatedByUserID,
		CreatedAtMs:     agency.CreatedAtMs,
		UpdatedAtMs:     agency.UpdatedAtMs,
		Avatar:          agency.OwnerAvatar,
		HostCount:       agency.ActiveHostCount,
	}
}

// toProtoBDProfile 把 BD profile 事实转换为内部 gRPC 投影。
func toProtoBDProfile(profile hostdomain.BDProfile) *userv1.BDProfile {
	if profile.UserID == 0 {
		return nil
	}
	return &userv1.BDProfile{
		UserId:             profile.UserID,
		Role:               profile.Role,
		RegionId:           profile.RegionID,
		ParentLeaderUserId: profile.ParentLeaderUserID,
		Status:             profile.Status,
		CreatedByUserId:    profile.CreatedByUserID,
		CreatedAtMs:        profile.CreatedAtMs,
		UpdatedAtMs:        profile.UpdatedAtMs,
	}
}

// toProtoCoinSellerProfile 把币商身份事实转换为内部 gRPC 投影。
func toProtoCoinSellerProfile(profile hostdomain.CoinSellerProfile) *userv1.CoinSellerProfile {
	if profile.UserID == 0 {
		return nil
	}
	return &userv1.CoinSellerProfile{
		UserId:            profile.UserID,
		Status:            profile.Status,
		MerchantAssetType: profile.MerchantAssetType,
		CreatedByUserId:   profile.CreatedByUserID,
		CreatedAtMs:       profile.CreatedAtMs,
		UpdatedAtMs:       profile.UpdatedAtMs,
	}
}

// toProtoCoinSellerListItem 把区域币商列表项转换为 App 读取投影。
func toProtoCoinSellerListItem(item hostdomain.CoinSellerListItem) *userv1.CoinSellerListItem {
	if item.UserID == 0 {
		return nil
	}
	return &userv1.CoinSellerListItem{
		UserId:             item.UserID,
		DisplayUserId:      item.DisplayUserID,
		Username:           item.Username,
		Avatar:             item.Avatar,
		CountryId:          item.CountryID,
		CountryCode:        item.CountryCode,
		CountryName:        item.CountryName,
		CountryDisplayName: item.CountryDisplayName,
		RegionId:           item.RegionID,
		RegionCode:         item.RegionCode,
		RegionName:         item.RegionName,
		Status:             item.Status,
		MerchantAssetType:  item.MerchantAssetType,
		UpdatedAtMs:        item.UpdatedAtMs,
	}
}

// toProtoUserRoleSummary 把 host domain 入口显隐摘要转换为 gRPC 投影。
func toProtoUserRoleSummary(summary hostdomain.UserRoleSummary) *userv1.UserRoleSummary {
	if summary.UserID == 0 {
		return nil
	}
	return &userv1.UserRoleSummary{
		UserId:                 summary.UserID,
		IsHost:                 summary.IsHost,
		IsAgency:               summary.IsAgency,
		IsManager:              summary.IsManager,
		IsBd:                   summary.IsBD,
		IsBdLeader:             summary.IsBDLeader,
		IsCoinSeller:           summary.IsCoinSeller,
		HostStatus:             summary.HostStatus,
		AgencyId:               summary.AgencyID,
		BdId:                   summary.BDID,
		BdStatus:               summary.BDStatus,
		CoinSellerStatus:       summary.CoinSellerStatus,
		PendingRoleInvitations: summary.PendingRoleInvitations,
	}
}

// toProtoAgencyMembership 把 Agency membership 事实转换为内部 gRPC 投影。
func toProtoAgencyMembership(membership hostdomain.AgencyMembership) *userv1.AgencyMembership {
	if membership.MembershipID == 0 {
		return nil
	}
	return &userv1.AgencyMembership{
		MembershipId:   membership.MembershipID,
		AgencyId:       membership.AgencyID,
		HostUserId:     membership.HostUserID,
		RegionId:       membership.RegionID,
		MembershipType: membership.MembershipType,
		Status:         membership.Status,
		JoinedAtMs:     membership.JoinedAtMs,
		EndedAtMs:      membership.EndedAtMs,
		EndedByUserId:  membership.EndedByUserID,
		EndedReason:    membership.EndedReason,
		CreatedAtMs:    membership.CreatedAtMs,
		UpdatedAtMs:    membership.UpdatedAtMs,
	}
}

// toProtoAgencyApplication 把 Agency application 事实转换为内部 gRPC 投影。
func toProtoAgencyApplication(application hostdomain.AgencyApplication) *userv1.AgencyApplication {
	if application.ApplicationID == 0 {
		return nil
	}
	return &userv1.AgencyApplication{
		ApplicationId:    application.ApplicationID,
		CommandId:        application.CommandID,
		ApplicantUserId:  application.ApplicantUserID,
		AgencyId:         application.AgencyID,
		RegionId:         application.RegionID,
		Status:           application.Status,
		ReviewedByUserId: application.ReviewedByUserID,
		ReviewReason:     application.ReviewReason,
		ReviewedAtMs:     application.ReviewedAtMs,
		CreatedAtMs:      application.CreatedAtMs,
		UpdatedAtMs:      application.UpdatedAtMs,
	}
}

// toProtoRoleInvitation 把角色邀请事实转换为内部 gRPC 投影。
func toProtoRoleInvitation(invitation hostdomain.RoleInvitation) *userv1.RoleInvitation {
	if invitation.InvitationID == 0 {
		return nil
	}
	return &userv1.RoleInvitation{
		InvitationId:       invitation.InvitationID,
		CommandId:          invitation.CommandID,
		InvitationType:     invitation.InvitationType,
		Status:             invitation.Status,
		InviterUserId:      invitation.InviterUserID,
		InviterBdUserId:    invitation.InviterBDUserID,
		TargetUserId:       invitation.TargetUserID,
		RegionId:           invitation.RegionID,
		AgencyName:         invitation.AgencyName,
		ParentBdUserId:     invitation.ParentBDUserID,
		ParentLeaderUserId: invitation.ParentLeaderUserID,
		ProcessedByUserId:  invitation.ProcessedByUserID,
		ProcessReason:      invitation.ProcessReason,
		ProcessedAtMs:      invitation.ProcessedAtMs,
		CreatedAtMs:        invitation.CreatedAtMs,
		UpdatedAtMs:        invitation.UpdatedAtMs,
	}
}
