package userclient

import (
	"context"
	"time"

	"hyapp-admin-server/internal/appctx"
	userv1 "hyapp.local/api/proto/user/v1"

	"google.golang.org/grpc"
)

type Client interface {
	GetUser(ctx context.Context, req GetUserRequest) (*User, error)
	BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error)
	AdminChangeUserCountry(ctx context.Context, req AdminChangeUserCountryRequest) (*ChangeUserCountryResult, error)
	SetUserStatus(ctx context.Context, req SetUserStatusRequest) (*SetUserStatusResult, error)
	GetRegisterRiskConfig(ctx context.Context, req GetRegisterRiskConfigRequest) (*RegisterRiskConfig, error)
	UpdateRegisterRiskConfig(ctx context.Context, req UpdateRegisterRiskConfigRequest) (*RegisterRiskConfig, error)
	RefreshLoginRiskWhitelistCache(ctx context.Context, req RefreshLoginRiskWhitelistCacheRequest) (*LoginRiskWhitelistCacheSnapshot, error)
	ResolveDisplayUserID(ctx context.Context, req ResolveDisplayUserIDRequest) (*UserIdentity, error)
	ListPrettyDisplayIDPools(ctx context.Context, req ListPrettyDisplayIDPoolsRequest) ([]*PrettyDisplayIDPool, int64, error)
	CreatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error)
	UpdatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error)
	GeneratePrettyDisplayIDs(ctx context.Context, req GeneratePrettyDisplayIDsRequest) (*PrettyDisplayIDGenerationBatch, error)
	ListPrettyDisplayIDs(ctx context.Context, req ListPrettyDisplayIDsRequest) ([]*PrettyDisplayID, int64, error)
	SetPrettyDisplayIDStatus(ctx context.Context, req SetPrettyDisplayIDStatusRequest) (*PrettyDisplayID, error)
	RecyclePrettyDisplayID(ctx context.Context, req RecyclePrettyDisplayIDRequest) (*PrettyDisplayID, error)
	AdminGrantPrettyDisplayID(ctx context.Context, req AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResult, error)
	ListCountries(ctx context.Context, req ListCountriesRequest) ([]*Country, error)
	UpdateCountry(ctx context.Context, req UpdateCountryRequest) (*Country, error)
	ListRegions(ctx context.Context, req ListRegionsRequest) ([]*Region, error)
	GetRegion(ctx context.Context, req GetRegionRequest) (*Region, error)
	UpdateRegion(ctx context.Context, req UpdateRegionRequest) (*Region, error)
	ReplaceRegionCountries(ctx context.Context, req ReplaceRegionCountriesRequest) (*Region, error)
	CreateBDLeader(ctx context.Context, req CreateBDLeaderRequest) (*BDProfile, error)
	CreateBD(ctx context.Context, req CreateBDRequest) (*BDProfile, error)
	SetBDStatus(ctx context.Context, req SetBDStatusRequest) (*BDProfile, error)
	CreateCoinSeller(ctx context.Context, req CreateCoinSellerRequest) (*CoinSellerProfile, error)
	SetCoinSellerStatus(ctx context.Context, req SetCoinSellerStatusRequest) (*CoinSellerProfile, error)
	GetCoinSellerProfile(ctx context.Context, req GetCoinSellerProfileRequest) (*CoinSellerProfile, error)
	CreateAgency(ctx context.Context, req CreateAgencyRequest) (*CreateAgencyResult, error)
	CloseAgency(ctx context.Context, req CloseAgencyRequest) (*Agency, error)
	DeleteAgency(ctx context.Context, req DeleteAgencyRequest) (*Agency, error)
	SetAgencyStatus(ctx context.Context, req SetAgencyStatusRequest) (*Agency, error)
	SetAgencyJoinEnabled(ctx context.Context, req SetAgencyJoinEnabledRequest) (*Agency, error)
	QuickCreateAccount(ctx context.Context, req QuickCreateAccountRequest) (*QuickCreateAccountResult, error)
}

type GetUserRequest struct {
	RequestID string
	Caller    string
	UserID    int64
}

// BatchGetUsersRequest 只承载后台展示需要的一组真实 user_id，不在 admin-server 里回查用户库。
type BatchGetUsersRequest struct {
	RequestID string
	Caller    string
	UserIDs   []int64
}

type QuickCreateAccountRequest struct {
	RequestID      string
	Caller         string
	Password       string
	Username       string
	Avatar         string
	Gender         string
	Country        string
	DeviceID       string
	Source         string
	InstallChannel string
	Platform       string
	Language       string
	Timezone       string
}

type QuickCreateAccountResult struct {
	UserID        int64
	DisplayUserID string
	AccessToken   string
}

type SetUserStatusRequest struct {
	RequestID      string
	Caller         string
	TargetUserID   int64
	Status         string
	OperatorType   string
	OperatorUserID int64
	Reason         string
}

type AdminChangeUserCountryRequest struct {
	RequestID    string
	Caller       string
	TargetUserID int64
	Country      string
	AdminUserID  int64
	Reason       string
}

type ChangeUserCountryResult struct {
	User          *User `json:"user"`
	OldRegionID   int64 `json:"oldRegionId"`
	NewRegionID   int64 `json:"newRegionId"`
	RegionChanged bool  `json:"regionChanged"`
}

type SetUserStatusResult struct {
	User                *User  `json:"user"`
	RevokedSessionCount int64  `json:"revokedSessionCount"`
	AccessTokenRevoked  bool   `json:"accessTokenRevoked"`
	AccessTokenError    string `json:"accessTokenRevokeError,omitempty"`
	IMKicked            bool   `json:"imKicked"`
	IMKickError         string `json:"imKickError,omitempty"`
	RoomEvicted         bool   `json:"roomEvicted"`
	RoomID              string `json:"roomId,omitempty"`
	RTCKicked           bool   `json:"rtcKicked"`
	RTCKickError        string `json:"rtcKickError,omitempty"`
	RoomEvictError      string `json:"roomEvictError,omitempty"`
}

type GetRegisterRiskConfigRequest struct {
	RequestID string
	Caller    string
}

type UpdateRegisterRiskConfigRequest struct {
	RequestID            string
	Caller               string
	MaxAccountsPerDevice int32
	OperatorAdminID      int64
}

type RegisterRiskConfig struct {
	AppCode              string `json:"appCode"`
	MaxAccountsPerDevice int32  `json:"maxAccountsPerDevice"`
	UpdatedAtMs          int64  `json:"updatedAtMs"`
	UpdatedByAdminID     int64  `json:"updatedByAdminId"`
}

type RefreshLoginRiskWhitelistCacheRequest struct {
	RequestID string
	Caller    string
}

type LoginRiskWhitelistCacheSnapshot struct {
	Refreshed            bool  `json:"refreshed"`
	IPWhitelistCount     int32 `json:"ipWhitelistCount"`
	UserWhitelistCount   int32 `json:"userWhitelistCount"`
	UserCacheExpiresAtMs int64 `json:"userCacheExpiresAtMs"`
}

type ResolveDisplayUserIDRequest struct {
	RequestID     string
	Caller        string
	DisplayUserID string
}

type UserIdentity struct {
	UserID                   int64  `json:"userId,string"`
	DisplayUserID            string `json:"displayUserId"`
	Status                   string `json:"status"`
	DefaultDisplayUserID     string `json:"defaultDisplayUserId"`
	DisplayUserIDKind        string `json:"displayUserIdKind"`
	DisplayUserIDExpiresAtMs int64  `json:"displayUserIdExpiresAtMs"`
	PrettyID                 string `json:"prettyId,omitempty"`
	PrettyDisplayUserID      string `json:"prettyDisplayUserId,omitempty"`
	AppCode                  string `json:"appCode"`
}

type User struct {
	UserID                   int64  `json:"userId,string"`
	Status                   string `json:"status"`
	CreatedAtMs              int64  `json:"createdAtMs"`
	UpdatedAtMs              int64  `json:"updatedAtMs"`
	DisplayUserID            string `json:"displayUserId"`
	DefaultDisplayUserID     string `json:"defaultDisplayUserId"`
	DisplayUserIDKind        string `json:"displayUserIdKind"`
	DisplayUserIDExpiresAtMs int64  `json:"displayUserIdExpiresAtMs"`
	PrettyID                 string `json:"prettyId,omitempty"`
	PrettyDisplayUserID      string `json:"prettyDisplayUserId,omitempty"`
	Username                 string `json:"username"`
	Gender                   string `json:"gender"`
	Country                  string `json:"country"`
	Avatar                   string `json:"avatar"`
	Birth                    string `json:"birth"`
	Language                 string `json:"language"`
	CountryID                int64  `json:"countryId"`
	CountryName              string `json:"countryName"`
	CountryDisplayName       string `json:"countryDisplayName"`
	RegionID                 int64  `json:"regionId"`
	RegionCode               string `json:"regionCode"`
	RegionName               string `json:"regionName"`
	ProfileCompleted         bool   `json:"profileCompleted"`
	ProfileCompletedAtMs     int64  `json:"profileCompletedAtMs"`
	OnboardingStatus         string `json:"onboardingStatus"`
	ISONumeric               string `json:"isoNumeric"`
	PhoneCountryCode         string `json:"phoneCountryCode"`
	CountryEnabled           bool   `json:"countryEnabled"`
	ContactInfo              string `json:"contactInfo"`
}

type GRPCClient struct {
	client          userv1.UserServiceClient
	authClient      userv1.AuthServiceClient
	identityClient  userv1.UserIdentityServiceClient
	countryClient   userv1.CountryAdminServiceClient
	regionClient    userv1.RegionAdminServiceClient
	prettyClient    userv1.UserPrettyDisplayIDAdminServiceClient
	hostClient      userv1.UserHostServiceClient
	hostAdminClient userv1.UserHostAdminServiceClient
}

func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient {
	return &GRPCClient{
		client:          userv1.NewUserServiceClient(conn),
		authClient:      userv1.NewAuthServiceClient(conn),
		identityClient:  userv1.NewUserIdentityServiceClient(conn),
		countryClient:   userv1.NewCountryAdminServiceClient(conn),
		regionClient:    userv1.NewRegionAdminServiceClient(conn),
		prettyClient:    userv1.NewUserPrettyDisplayIDAdminServiceClient(conn),
		hostClient:      userv1.NewUserHostServiceClient(conn),
		hostAdminClient: userv1.NewUserHostAdminServiceClient(conn),
	}
}

func (c *GRPCClient) QuickCreateAccount(ctx context.Context, req QuickCreateAccountRequest) (*QuickCreateAccountResult, error) {
	resp, err := c.authClient.QuickCreateAccount(ctx, &userv1.QuickCreateAccountRequest{
		Meta: &userv1.RequestMeta{
			RequestId: req.RequestID,
			Caller:    req.Caller,
			SentAtMs:  time.Now().UnixMilli(),
			AppCode:   appctx.FromContext(ctx),
			Platform:  req.Platform,
			Language:  req.Language,
			Timezone:  req.Timezone,
		},
		Password:       req.Password,
		Username:       req.Username,
		Avatar:         req.Avatar,
		Gender:         req.Gender,
		Country:        req.Country,
		DeviceId:       req.DeviceID,
		Source:         req.Source,
		InstallChannel: req.InstallChannel,
		Platform:       req.Platform,
		Language:       req.Language,
		Timezone:       req.Timezone,
	})
	if err != nil {
		return nil, err
	}
	token := resp.GetToken()
	return &QuickCreateAccountResult{
		UserID:        token.GetUserId(),
		DisplayUserID: token.GetDisplayUserId(),
		AccessToken:   token.GetAccessToken(),
	}, nil
}

func (c *GRPCClient) GetUser(ctx context.Context, req GetUserRequest) (*User, error) {
	resp, err := c.client.GetUser(ctx, &userv1.GetUserRequest{
		Meta: &userv1.RequestMeta{
			RequestId: req.RequestID,
			Caller:    req.Caller,
			SentAtMs:  time.Now().UnixMilli(),
			AppCode:   appctx.FromContext(ctx),
		},
		UserId: req.UserID,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoUser(resp.GetUser()), nil
}

func (c *GRPCClient) GetRegisterRiskConfig(ctx context.Context, req GetRegisterRiskConfigRequest) (*RegisterRiskConfig, error) {
	resp, err := c.authClient.GetRegisterRiskConfig(ctx, &userv1.GetRegisterRiskConfigRequest{
		Meta: requestMeta(ctx, req.RequestID, req.Caller),
	})
	if err != nil {
		return nil, err
	}
	return fromProtoRegisterRiskConfig(resp.GetConfig()), nil
}

func (c *GRPCClient) UpdateRegisterRiskConfig(ctx context.Context, req UpdateRegisterRiskConfigRequest) (*RegisterRiskConfig, error) {
	resp, err := c.authClient.UpdateRegisterRiskConfig(ctx, &userv1.UpdateRegisterRiskConfigRequest{
		Meta:                 requestMeta(ctx, req.RequestID, req.Caller),
		MaxAccountsPerDevice: req.MaxAccountsPerDevice,
		OperatorAdminId:      req.OperatorAdminID,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoRegisterRiskConfig(resp.GetConfig()), nil
}

func (c *GRPCClient) RefreshLoginRiskWhitelistCache(ctx context.Context, req RefreshLoginRiskWhitelistCacheRequest) (*LoginRiskWhitelistCacheSnapshot, error) {
	resp, err := c.authClient.RefreshLoginRiskWhitelistCache(ctx, &userv1.RefreshLoginRiskWhitelistCacheRequest{
		Meta: requestMeta(ctx, req.RequestID, req.Caller),
	})
	if err != nil {
		return nil, err
	}
	return &LoginRiskWhitelistCacheSnapshot{
		Refreshed:            resp.GetRefreshed(),
		IPWhitelistCount:     resp.GetIpWhitelistCount(),
		UserWhitelistCount:   resp.GetUserWhitelistCount(),
		UserCacheExpiresAtMs: resp.GetUserCacheExpiresAtMs(),
	}, nil
}

// BatchGetUsers 复用 user-service 主数据 RPC，让后台列表只做展示聚合，不复制用户资料到游戏库。
func (c *GRPCClient) BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error) {
	resp, err := c.client.BatchGetUsers(ctx, &userv1.BatchGetUsersRequest{
		Meta:    requestMeta(ctx, req.RequestID, req.Caller),
		UserIds: req.UserIDs,
	})
	if err != nil {
		return nil, err
	}
	users := make(map[int64]*User, len(resp.GetUsers()))
	for userID, user := range resp.GetUsers() {
		users[userID] = fromProtoUser(user)
	}
	return users, nil
}

func (c *GRPCClient) AdminChangeUserCountry(ctx context.Context, req AdminChangeUserCountryRequest) (*ChangeUserCountryResult, error) {
	resp, err := c.client.AdminChangeUserCountry(ctx, &userv1.AdminChangeUserCountryRequest{
		Meta:         requestMeta(ctx, req.RequestID, req.Caller),
		TargetUserId: req.TargetUserID,
		Country:      req.Country,
		AdminUserId:  req.AdminUserID,
		Reason:       req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return &ChangeUserCountryResult{
		User:          fromProtoUser(resp.GetUser()),
		OldRegionID:   resp.GetOldRegionId(),
		NewRegionID:   resp.GetNewRegionId(),
		RegionChanged: resp.GetRegionChanged(),
	}, nil
}

func (c *GRPCClient) SetUserStatus(ctx context.Context, req SetUserStatusRequest) (*SetUserStatusResult, error) {
	resp, err := c.client.SetUserStatus(ctx, &userv1.SetUserStatusRequest{
		Meta:           requestMeta(ctx, req.RequestID, req.Caller),
		TargetUserId:   req.TargetUserID,
		Status:         userStatusToProto(req.Status),
		OperatorType:   req.OperatorType,
		OperatorUserId: req.OperatorUserID,
		Reason:         req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return &SetUserStatusResult{
		User:                fromProtoUser(resp.GetUser()),
		RevokedSessionCount: resp.GetRevokedSessionCount(),
		AccessTokenRevoked:  resp.GetAccessTokenRevoked(),
		AccessTokenError:    resp.GetAccessTokenRevokeError(),
		IMKicked:            resp.GetImKicked(),
		IMKickError:         resp.GetImKickError(),
		RoomEvicted:         resp.GetRoomEvicted(),
		RoomID:              resp.GetRoomId(),
		RTCKicked:           resp.GetRtcKicked(),
		RTCKickError:        resp.GetRtcKickError(),
		RoomEvictError:      resp.GetRoomEvictError(),
	}, nil
}

func userStatusToProto(status string) userv1.UserStatus {
	switch status {
	case "active":
		return userv1.UserStatus_USER_STATUS_ACTIVE
	case "disabled":
		return userv1.UserStatus_USER_STATUS_DISABLED
	case "banned":
		return userv1.UserStatus_USER_STATUS_BANNED
	default:
		return userv1.UserStatus_USER_STATUS_UNSPECIFIED
	}
}

func (c *GRPCClient) ResolveDisplayUserID(ctx context.Context, req ResolveDisplayUserIDRequest) (*UserIdentity, error) {
	resp, err := c.identityClient.ResolveDisplayUserID(ctx, &userv1.ResolveDisplayUserIDRequest{
		Meta:          requestMeta(ctx, req.RequestID, req.Caller),
		DisplayUserId: req.DisplayUserID,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoUserIdentity(resp.GetIdentity()), nil
}

func fromProtoRegisterRiskConfig(config *userv1.RegisterRiskConfig) *RegisterRiskConfig {
	if config == nil {
		return nil
	}
	return &RegisterRiskConfig{
		AppCode:              config.GetAppCode(),
		MaxAccountsPerDevice: config.GetMaxAccountsPerDevice(),
		UpdatedAtMs:          config.GetUpdatedAtMs(),
		UpdatedByAdminID:     config.GetUpdatedByAdminId(),
	}
}

func fromProtoUser(user *userv1.User) *User {
	if user == nil {
		return nil
	}
	return &User{
		UserID:                   user.GetUserId(),
		Status:                   fromProtoStatus(user.GetStatus()),
		CreatedAtMs:              user.GetCreatedAtMs(),
		UpdatedAtMs:              user.GetUpdatedAtMs(),
		DisplayUserID:            user.GetDisplayUserId(),
		DefaultDisplayUserID:     user.GetDefaultDisplayUserId(),
		DisplayUserIDKind:        user.GetDisplayUserIdKind(),
		DisplayUserIDExpiresAtMs: user.GetDisplayUserIdExpiresAtMs(),
		PrettyID:                 user.GetPrettyId(),
		PrettyDisplayUserID:      user.GetPrettyDisplayUserId(),
		Username:                 user.GetUsername(),
		Gender:                   user.GetGender(),
		Country:                  user.GetCountry(),
		Avatar:                   user.GetAvatar(),
		Birth:                    user.GetBirth(),
		Language:                 user.GetLanguage(),
		CountryID:                user.GetCountryId(),
		CountryName:              user.GetCountryName(),
		CountryDisplayName:       user.GetCountryDisplayName(),
		RegionID:                 user.GetRegionId(),
		RegionCode:               user.GetRegionCode(),
		RegionName:               user.GetRegionName(),
		ProfileCompleted:         user.GetProfileCompleted(),
		ProfileCompletedAtMs:     user.GetProfileCompletedAtMs(),
		OnboardingStatus:         user.GetOnboardingStatus(),
		ISONumeric:               user.GetIsoNumeric(),
		PhoneCountryCode:         user.GetPhoneCountryCode(),
		CountryEnabled:           user.GetCountryEnabled(),
		ContactInfo:              user.GetContactInfo(),
	}
}

func fromProtoStatus(status userv1.UserStatus) string {
	switch status {
	case userv1.UserStatus_USER_STATUS_ACTIVE:
		return "active"
	case userv1.UserStatus_USER_STATUS_DISABLED:
		return "disabled"
	case userv1.UserStatus_USER_STATUS_BANNED:
		return "banned"
	default:
		return ""
	}
}

func fromProtoUserIdentity(identity *userv1.UserIdentity) *UserIdentity {
	if identity == nil {
		return nil
	}
	return &UserIdentity{
		UserID:                   identity.GetUserId(),
		DisplayUserID:            identity.GetDisplayUserId(),
		Status:                   identity.GetStatus(),
		DefaultDisplayUserID:     identity.GetDefaultDisplayUserId(),
		DisplayUserIDKind:        identity.GetDisplayUserIdKind(),
		DisplayUserIDExpiresAtMs: identity.GetDisplayUserIdExpiresAtMs(),
		PrettyID:                 identity.GetPrettyId(),
		PrettyDisplayUserID:      identity.GetPrettyDisplayUserId(),
		AppCode:                  identity.GetAppCode(),
	}
}

type CreateBDLeaderRequest struct {
	RequestID    string
	Caller       string
	CommandID    string
	AdminUserID  int64
	TargetUserID int64
	Reason       string
}

type CreateBDRequest struct {
	RequestID          string
	Caller             string
	CommandID          string
	AdminUserID        int64
	TargetUserID       int64
	ParentLeaderUserID int64
	Reason             string
}

type SetBDStatusRequest struct {
	RequestID    string
	Caller       string
	CommandID    string
	AdminUserID  int64
	TargetUserID int64
	Role         string
	Status       string
	Reason       string
}

type CreateCoinSellerRequest struct {
	RequestID    string
	Caller       string
	CommandID    string
	AdminUserID  int64
	TargetUserID int64
	Reason       string
}

type SetCoinSellerStatusRequest struct {
	RequestID    string
	Caller       string
	CommandID    string
	AdminUserID  int64
	TargetUserID int64
	Status       string
	Reason       string
}

type GetCoinSellerProfileRequest struct {
	RequestID string
	Caller    string
	UserID    int64
}

type CreateAgencyRequest struct {
	RequestID      string
	Caller         string
	CommandID      string
	AdminUserID    int64
	OwnerUserID    int64
	ParentBDUserID int64
	Name           string
	JoinEnabled    bool
	Reason         string
}

type CloseAgencyRequest struct {
	RequestID   string
	Caller      string
	CommandID   string
	AdminUserID int64
	AgencyID    int64
	Reason      string
}

type DeleteAgencyRequest struct {
	RequestID   string
	Caller      string
	CommandID   string
	AdminUserID int64
	AgencyID    int64
	Reason      string
}

type SetAgencyStatusRequest struct {
	RequestID   string
	Caller      string
	CommandID   string
	AdminUserID int64
	AgencyID    int64
	Status      string
	Reason      string
}

type SetAgencyJoinEnabledRequest struct {
	RequestID   string
	Caller      string
	CommandID   string
	AdminUserID int64
	AgencyID    int64
	JoinEnabled bool
	Reason      string
}

type BDProfile struct {
	UserID                 int64        `json:"userId,string"`
	Role                   string       `json:"role"`
	RegionID               int64        `json:"regionId"`
	ParentLeaderUserID     int64        `json:"parentLeaderUserId,string"`
	PositionAlias          string       `json:"positionAlias"`
	Status                 string       `json:"status"`
	CreatedByUserID        int64        `json:"createdByUserId,string"`
	CreatedAtMs            int64        `json:"createdAtMs"`
	UpdatedAtMs            int64        `json:"updatedAtMs"`
	DisplayUserID          string       `json:"displayUserId"`
	Username               string       `json:"username"`
	Avatar                 string       `json:"avatar"`
	Contact                string       `json:"contact"`
	RegionName             string       `json:"regionName"`
	ParentLeaderDisplayID  string       `json:"parentLeaderDisplayUserId"`
	ParentLeaderUsername   string       `json:"parentLeaderUsername"`
	ParentLeaderAvatar     string       `json:"parentLeaderAvatar"`
	CreatedByDisplayUserID string       `json:"createdByDisplayUserId"`
	CreatedByUsername      string       `json:"createdByUsername"`
	CreatedByAvatar        string       `json:"createdByAvatar"`
	AgencyCount            int64        `json:"agencyCount"`
	SubBDCount             int64        `json:"subBdCount"`
	SalaryWallet           SalaryWallet `json:"salaryWallet"`
}

type Agency struct {
	AgencyID              int64        `json:"agencyId,string"`
	OwnerUserID           int64        `json:"ownerUserId,string"`
	RegionID              int64        `json:"regionId"`
	ParentBDUserID        int64        `json:"parentBdUserId,string"`
	Name                  string       `json:"name"`
	Status                string       `json:"status"`
	JoinEnabled           bool         `json:"joinEnabled"`
	MaxHosts              int32        `json:"maxHosts"`
	CreatedByUserID       int64        `json:"createdByUserId,string"`
	CreatedAtMs           int64        `json:"createdAtMs"`
	UpdatedAtMs           int64        `json:"updatedAtMs"`
	OwnerDisplayUserID    string       `json:"ownerDisplayUserId"`
	OwnerUsername         string       `json:"ownerUsername"`
	OwnerAvatar           string       `json:"ownerAvatar"`
	Contact               string       `json:"contact"`
	ParentBDDisplayUserID string       `json:"parentBdDisplayUserId"`
	ParentBDUsername      string       `json:"parentBdUsername"`
	ParentBDAvatar        string       `json:"parentBdAvatar"`
	RegionName            string       `json:"regionName"`
	ActiveHostCount       int64        `json:"activeHostCount"`
	SalaryWallet          SalaryWallet `json:"salaryWallet"`
}

type HostProfile struct {
	UserID                          int64        `json:"userId,string"`
	Status                          string       `json:"status"`
	RegionID                        int64        `json:"regionId"`
	CurrentAgencyID                 int64        `json:"currentAgencyId,string"`
	CurrentMembershipID             int64        `json:"currentMembershipId,string"`
	Source                          string       `json:"source"`
	FirstBecameHostAtMs             int64        `json:"firstBecameHostAtMs"`
	CreatedAtMs                     int64        `json:"createdAtMs"`
	UpdatedAtMs                     int64        `json:"updatedAtMs"`
	DisplayUserID                   string       `json:"displayUserId"`
	Username                        string       `json:"username"`
	Avatar                          string       `json:"avatar"`
	Contact                         string       `json:"contact"`
	RegionName                      string       `json:"regionName"`
	CurrentAgencyName               string       `json:"currentAgencyName"`
	Diamond                         int64        `json:"diamond"`
	CurrentAgencyOwnerUserID        int64        `json:"currentAgencyOwnerUserId,string"`
	CurrentAgencyOwnerDisplayUserID string       `json:"currentAgencyOwnerDisplayUserId"`
	CurrentAgencyOwnerUsername      string       `json:"currentAgencyOwnerUsername"`
	CurrentAgencyOwnerAvatar        string       `json:"currentAgencyOwnerAvatar"`
	SalaryWallet                    SalaryWallet `json:"salaryWallet"`
}

type SalaryWallet struct {
	Role            string `json:"role"`
	UserID          int64  `json:"userId,string"`
	AssetType       string `json:"assetType"`
	AvailableAmount int64  `json:"availableAmount"`
	FrozenAmount    int64  `json:"frozenAmount"`
	UpdatedAtMs     int64  `json:"updatedAtMs"`
}

type CoinSellerProfile struct {
	UserID            int64  `json:"userId,string"`
	Status            string `json:"status"`
	MerchantAssetType string `json:"merchantAssetType"`
	CreatedByUserID   int64  `json:"createdByUserId,string"`
	CreatedAtMs       int64  `json:"createdAtMs"`
	UpdatedAtMs       int64  `json:"updatedAtMs"`
}

type AgencyMembership struct {
	MembershipID   int64  `json:"membershipId,string"`
	AgencyID       int64  `json:"agencyId,string"`
	HostUserID     int64  `json:"hostUserId,string"`
	RegionID       int64  `json:"regionId"`
	MembershipType string `json:"membershipType"`
	Status         string `json:"status"`
	JoinedAtMs     int64  `json:"joinedAtMs"`
	EndedAtMs      int64  `json:"endedAtMs"`
	EndedByUserID  int64  `json:"endedByUserId,string"`
	EndedReason    string `json:"endedReason"`
	CreatedAtMs    int64  `json:"createdAtMs"`
	UpdatedAtMs    int64  `json:"updatedAtMs"`
}

type CreateAgencyResult struct {
	Agency      *Agency           `json:"agency"`
	HostProfile *HostProfile      `json:"hostProfile"`
	Membership  *AgencyMembership `json:"membership"`
}

func (c *GRPCClient) CreateBDLeader(ctx context.Context, req CreateBDLeaderRequest) (*BDProfile, error) {
	resp, err := c.hostAdminClient.CreateBDLeader(ctx, &userv1.CreateBDLeaderRequest{
		Meta:         requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:    req.CommandID,
		AdminUserId:  req.AdminUserID,
		TargetUserId: req.TargetUserID,
		Reason:       req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoBDProfile(resp.GetBdProfile()), nil
}

func (c *GRPCClient) CreateBD(ctx context.Context, req CreateBDRequest) (*BDProfile, error) {
	resp, err := c.hostAdminClient.CreateBD(ctx, &userv1.CreateBDRequest{
		Meta:               requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:          req.CommandID,
		AdminUserId:        req.AdminUserID,
		TargetUserId:       req.TargetUserID,
		ParentLeaderUserId: req.ParentLeaderUserID,
		Reason:             req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoBDProfile(resp.GetBdProfile()), nil
}

func (c *GRPCClient) SetBDStatus(ctx context.Context, req SetBDStatusRequest) (*BDProfile, error) {
	resp, err := c.hostAdminClient.SetBDStatus(ctx, &userv1.SetBDStatusRequest{
		Meta:         requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:    req.CommandID,
		AdminUserId:  req.AdminUserID,
		TargetUserId: req.TargetUserID,
		Role:         req.Role,
		Status:       req.Status,
		Reason:       req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoBDProfile(resp.GetBdProfile()), nil
}

func (c *GRPCClient) CreateCoinSeller(ctx context.Context, req CreateCoinSellerRequest) (*CoinSellerProfile, error) {
	resp, err := c.hostAdminClient.CreateCoinSeller(ctx, &userv1.CreateCoinSellerRequest{
		Meta:         requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:    req.CommandID,
		AdminUserId:  req.AdminUserID,
		TargetUserId: req.TargetUserID,
		Reason:       req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoCoinSellerProfile(resp.GetCoinSellerProfile()), nil
}

func (c *GRPCClient) SetCoinSellerStatus(ctx context.Context, req SetCoinSellerStatusRequest) (*CoinSellerProfile, error) {
	resp, err := c.hostAdminClient.SetCoinSellerStatus(ctx, &userv1.SetCoinSellerStatusRequest{
		Meta:         requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:    req.CommandID,
		AdminUserId:  req.AdminUserID,
		TargetUserId: req.TargetUserID,
		Status:       req.Status,
		Reason:       req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoCoinSellerProfile(resp.GetCoinSellerProfile()), nil
}

func (c *GRPCClient) GetCoinSellerProfile(ctx context.Context, req GetCoinSellerProfileRequest) (*CoinSellerProfile, error) {
	resp, err := c.hostClient.GetCoinSellerProfile(ctx, &userv1.GetCoinSellerProfileRequest{
		Meta:   requestMeta(ctx, req.RequestID, req.Caller),
		UserId: req.UserID,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoCoinSellerProfile(resp.GetCoinSellerProfile()), nil
}

func (c *GRPCClient) CreateAgency(ctx context.Context, req CreateAgencyRequest) (*CreateAgencyResult, error) {
	resp, err := c.hostAdminClient.CreateAgency(ctx, &userv1.CreateAgencyRequest{
		Meta:           requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:      req.CommandID,
		AdminUserId:    req.AdminUserID,
		OwnerUserId:    req.OwnerUserID,
		ParentBdUserId: req.ParentBDUserID,
		Name:           req.Name,
		JoinEnabled:    req.JoinEnabled,
		Reason:         req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return &CreateAgencyResult{
		Agency:      fromProtoAgency(resp.GetAgency()),
		HostProfile: fromProtoHostProfile(resp.GetHostProfile()),
		Membership:  fromProtoAgencyMembership(resp.GetMembership()),
	}, nil
}

func (c *GRPCClient) CloseAgency(ctx context.Context, req CloseAgencyRequest) (*Agency, error) {
	resp, err := c.hostAdminClient.CloseAgency(ctx, &userv1.CloseAgencyRequest{
		Meta:        requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:   req.CommandID,
		AdminUserId: req.AdminUserID,
		AgencyId:    req.AgencyID,
		Reason:      req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoAgency(resp.GetAgency()), nil
}

func (c *GRPCClient) DeleteAgency(ctx context.Context, req DeleteAgencyRequest) (*Agency, error) {
	resp, err := c.hostAdminClient.DeleteAgency(ctx, &userv1.DeleteAgencyRequest{
		Meta:        requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:   req.CommandID,
		AdminUserId: req.AdminUserID,
		AgencyId:    req.AgencyID,
		Reason:      req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoAgency(resp.GetAgency()), nil
}

func (c *GRPCClient) SetAgencyStatus(ctx context.Context, req SetAgencyStatusRequest) (*Agency, error) {
	resp, err := c.hostAdminClient.SetAgencyStatus(ctx, &userv1.SetAgencyStatusRequest{
		Meta:        requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:   req.CommandID,
		AdminUserId: req.AdminUserID,
		AgencyId:    req.AgencyID,
		Status:      req.Status,
		Reason:      req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoAgency(resp.GetAgency()), nil
}

func (c *GRPCClient) SetAgencyJoinEnabled(ctx context.Context, req SetAgencyJoinEnabledRequest) (*Agency, error) {
	resp, err := c.hostAdminClient.SetAgencyJoinEnabled(ctx, &userv1.SetAgencyJoinEnabledRequest{
		Meta:        requestMeta(ctx, req.RequestID, req.Caller),
		CommandId:   req.CommandID,
		AdminUserId: req.AdminUserID,
		AgencyId:    req.AgencyID,
		JoinEnabled: req.JoinEnabled,
		Reason:      req.Reason,
	})
	if err != nil {
		return nil, err
	}
	return fromProtoAgency(resp.GetAgency()), nil
}

func requestMeta(ctx context.Context, requestID string, caller string) *userv1.RequestMeta {
	return &userv1.RequestMeta{
		RequestId: requestID,
		Caller:    caller,
		SentAtMs:  time.Now().UnixMilli(),
		AppCode:   appctx.FromContext(ctx),
	}
}

func fromProtoBDProfile(profile *userv1.BDProfile) *BDProfile {
	if profile == nil {
		return nil
	}
	return &BDProfile{
		UserID:             profile.GetUserId(),
		Role:               profile.GetRole(),
		RegionID:           profile.GetRegionId(),
		ParentLeaderUserID: profile.GetParentLeaderUserId(),
		Status:             profile.GetStatus(),
		CreatedByUserID:    profile.GetCreatedByUserId(),
		CreatedAtMs:        profile.GetCreatedAtMs(),
		UpdatedAtMs:        profile.GetUpdatedAtMs(),
	}
}

func fromProtoAgency(agency *userv1.Agency) *Agency {
	if agency == nil {
		return nil
	}
	return &Agency{
		AgencyID:        agency.GetAgencyId(),
		OwnerUserID:     agency.GetOwnerUserId(),
		RegionID:        agency.GetRegionId(),
		ParentBDUserID:  agency.GetParentBdUserId(),
		Name:            agency.GetName(),
		Status:          agency.GetStatus(),
		JoinEnabled:     agency.GetJoinEnabled(),
		MaxHosts:        agency.GetMaxHosts(),
		CreatedByUserID: agency.GetCreatedByUserId(),
		CreatedAtMs:     agency.GetCreatedAtMs(),
		UpdatedAtMs:     agency.GetUpdatedAtMs(),
		ActiveHostCount: int64(agency.GetHostCount()),
	}
}

func fromProtoHostProfile(profile *userv1.HostProfile) *HostProfile {
	if profile == nil {
		return nil
	}
	return &HostProfile{
		UserID:              profile.GetUserId(),
		Status:              profile.GetStatus(),
		RegionID:            profile.GetRegionId(),
		CurrentAgencyID:     profile.GetCurrentAgencyId(),
		CurrentMembershipID: profile.GetCurrentMembershipId(),
		Source:              profile.GetSource(),
		FirstBecameHostAtMs: profile.GetFirstBecameHostAtMs(),
		CreatedAtMs:         profile.GetCreatedAtMs(),
		UpdatedAtMs:         profile.GetUpdatedAtMs(),
	}
}

func fromProtoCoinSellerProfile(profile *userv1.CoinSellerProfile) *CoinSellerProfile {
	if profile == nil {
		return nil
	}
	return &CoinSellerProfile{
		UserID:            profile.GetUserId(),
		Status:            profile.GetStatus(),
		MerchantAssetType: profile.GetMerchantAssetType(),
		CreatedByUserID:   profile.GetCreatedByUserId(),
		CreatedAtMs:       profile.GetCreatedAtMs(),
		UpdatedAtMs:       profile.GetUpdatedAtMs(),
	}
}

func fromProtoAgencyMembership(membership *userv1.AgencyMembership) *AgencyMembership {
	if membership == nil {
		return nil
	}
	return &AgencyMembership{
		MembershipID:   membership.GetMembershipId(),
		AgencyID:       membership.GetAgencyId(),
		HostUserID:     membership.GetHostUserId(),
		RegionID:       membership.GetRegionId(),
		MembershipType: membership.GetMembershipType(),
		Status:         membership.GetStatus(),
		JoinedAtMs:     membership.GetJoinedAtMs(),
		EndedAtMs:      membership.GetEndedAtMs(),
		EndedByUserID:  membership.GetEndedByUserId(),
		EndedReason:    membership.GetEndedReason(),
		CreatedAtMs:    membership.GetCreatedAtMs(),
		UpdatedAtMs:    membership.GetUpdatedAtMs(),
	}
}
