package roomclient

import (
	"context"
	"fmt"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	roomv1 "hyapp.local/api/proto/room/v1"

	"google.golang.org/grpc"
)

type Client interface {
	ListRooms(ctx context.Context, req ListRoomsRequest) (*ListRoomsResult, error)
	GetRoom(ctx context.Context, req GetRoomRequest) (*Room, error)
	UpdateRoom(ctx context.Context, req UpdateRoomRequest) (*CloseRoomResult, error)
	BanRoom(ctx context.Context, req ModerateRoomRequest) (*CloseRoomResult, error)
	UnbanRoom(ctx context.Context, req ModerateRoomRequest) (*CloseRoomResult, error)
	DeleteRoom(ctx context.Context, req DeleteRoomRequest) (*CloseRoomResult, error)
	GetRoomRocketConfig(ctx context.Context) (RoomRocketConfig, error)
	UpdateRoomRocketConfig(ctx context.Context, req UpdateRoomRocketConfigRequest) (RoomRocketConfig, error)
	GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, error)
	UpdateRoomSeatConfig(ctx context.Context, req UpdateRoomSeatConfigRequest) (RoomSeatConfig, error)
	GetHumanRoomRobotConfig(ctx context.Context) (HumanRoomRobotConfig, error)
	UpdateHumanRoomRobotConfig(ctx context.Context, req UpdateHumanRoomRobotConfigRequest) (HumanRoomRobotConfig, error)
	ListRoomPins(ctx context.Context, req ListRoomPinsRequest) (ListRoomPinsResult, error)
	CreateRoomPin(ctx context.Context, req CreateRoomPinRequest) (RoomPin, error)
	CancelRoomPin(ctx context.Context, req CancelRoomPinRequest) (RoomPin, error)
	ListRobotRooms(ctx context.Context, req ListRobotRoomsRequest) (ListRobotRoomsResult, error)
	CreateRobotRoom(ctx context.Context, req CreateRobotRoomRequest) (RobotRoom, error)
	SetRobotRoomStatus(ctx context.Context, req SetRobotRoomStatusRequest) (RobotRoom, error)
	FilterAvailableRoomRobots(ctx context.Context, userIDs []int64) (FilterAvailableRoomRobotsResult, error)
	RenameOwnerCountryCode(ctx context.Context, req RenameOwnerCountryCodeRequest) (RenameOwnerCountryCodeResult, error)
	CloseRoom(ctx context.Context, req CloseRoomRequest) (*CloseRoomResult, error)
	ReopenRoom(ctx context.Context, req ReopenRoomRequest) (*CloseRoomResult, error)
}

type ListRoomsRequest struct {
	Page          int
	PageSize      int
	Keyword       string
	Status        string
	RegionID      int64
	OwnerUserID   int64
	OwnerUserIDs  []int64
	SortBy        string
	SortDirection string
}

type ListRoomsResult struct {
	Rooms        []Room
	Total        int64
	ServerTimeMs int64
}

type GetRoomRequest struct {
	RoomID string
}

type UpdateRoomRequest struct {
	RequestID       string
	RoomID          string
	ActorUserID     int64
	Title           *string
	CoverURL        *string
	Description     *string
	Mode            *string
	Status          *string
	VisibleRegionID *int64
	CloseReason     string
	AdminID         uint64
	AdminName       string
}

type DeleteRoomRequest struct {
	RequestID   string
	RoomID      string
	ActorUserID int64
	AdminID     uint64
	AdminName   string
}

type ModerateRoomRequest struct {
	RequestID   string
	RoomID      string
	ActorUserID int64
	Reason      string
	AdminID     uint64
	AdminName   string
}

type Room struct {
	RoomID            string
	RoomShortID       string
	VisibleRegionID   int64
	OwnerUserID       int64
	Title             string
	CoverURL          string
	Mode              string
	Status            string
	CloseReason       string
	ClosedByAdminID   uint64
	ClosedByAdminName string
	ClosedAtMS        int64
	Heat              int64
	OnlineCount       int32
	SeatCount         int32
	OccupiedSeatCount int32
	SortScore         int64
	CreatedAtMS       int64
	UpdatedAtMS       int64
}

type RoomRocketConfig struct {
	AppCode           string
	Enabled           bool
	ConfigVersion     int64
	FuelSource        string
	LaunchDelayMS     int64
	BroadcastEnabled  bool
	BroadcastScope    string
	BroadcastDelayMS  int64
	RewardStackPolicy string
	Levels            []RoomRocketLevelConfig
	GiftFuelRules     []GiftFuelRuleConfig
	UpdatedByAdminID  int64
	CreatedAtMS       int64
	UpdatedAtMS       int64
}

type RoomRocketLevelConfig struct {
	Level              int32
	FuelThreshold      int64
	CoverURL           string
	AnimationURL       string
	LaunchAnimationURL string
	LaunchedImageURL   string
	InRoomRewards      []RoomRocketRewardItem
	Top1Rewards        []RoomRocketRewardItem
	IgniterRewards     []RoomRocketRewardItem
}

type RoomRocketRewardItem struct {
	RewardItemID    string
	ResourceGroupID int64
	Weight          int64
	Quantity        int64
	DisplayName     string
	IconURL         string
}

type GiftFuelRuleConfig struct {
	RuleID        string
	GiftID        string
	GiftTypeCode  string
	MultiplierPPM int64
	FixedFuel     int64
	Excluded      bool
}

type UpdateRoomRocketConfigRequest struct {
	Config  RoomRocketConfig
	AdminID int64
}

type RoomSeatConfig struct {
	CandidateSeatCounts []int32
	AllowedSeatCounts   []int32
	DefaultSeatCount    int32
	UpdatedAtMS         int64
}

type UpdateRoomSeatConfigRequest struct {
	AllowedSeatCounts []int32
	DefaultSeatCount  int32
}

type HumanRoomRobotConfig struct {
	AppCode                 string
	Enabled                 bool
	CandidateRoomMaxOnline  int32
	RoomFullStopOnline      int32
	RoomTargetMinOnline     int32
	RoomTargetMaxOnline     int32
	RobotStayMinMS          int64
	RobotStayMaxMS          int64
	RobotReplaceMinMS       int64
	RobotReplaceMaxMS       int64
	NormalGiftIntervalMS    int64
	NormalGiftIntervalMinMS int64
	NormalGiftIntervalMaxMS int64
	GiftIDs                 []string
	LuckyGiftIDs            []string
	SuperLuckyGiftIDs       []string
	LuckyComboMin           int64
	LuckyComboMax           int64
	LuckyPauseMinMS         int64
	LuckyPauseMaxMS         int64
	MaxGiftSenders          int64
	AllowedOwnerIDs         []string
	CountryLimitEnabled     bool
	CountryRules            []HumanRoomRobotCountryRule
	UpdatedByAdminID        uint64
	CreatedAtMS             int64
	UpdatedAtMS             int64
}

type HumanRoomRobotCountryRule struct {
	CountryCode     string
	MaxRoomCount    int32
	AllowedOwnerIDs []string
}

type UpdateHumanRoomRobotConfigRequest struct {
	Config  HumanRoomRobotConfig
	AdminID uint64
}

type RoomPinRoom struct {
	RoomID          string
	RoomShortID     string
	VisibleRegionID int64
	OwnerUserID     int64
	Title           string
	CoverURL        string
	Status          string
}

type RoomPin struct {
	ID                 int64
	VisibleRegionID    int64
	PinType            string
	RoomID             string
	Weight             int64
	Status             string
	PinnedAtMS         int64
	ExpiresAtMS        int64
	CancelledAtMS      int64
	CreatedByAdminID   uint64
	CancelledByAdminID uint64
	CreatedAtMS        int64
	UpdatedAtMS        int64
	Room               RoomPinRoom
}

type ListRoomPinsRequest struct {
	Page     int
	PageSize int
	Keyword  string
	Status   string
	RegionID int64
	PinType  string
}

type ListRoomPinsResult struct {
	Pins  []RoomPin
	Total int64
}

type CreateRoomPinRequest struct {
	RoomID       string
	PinType      string
	Weight       int64
	DurationDays int64
	PinnedAtMS   int64
	ExpiresAtMS  int64
	AdminID      uint64
}

type CancelRoomPinRequest struct {
	PinID   int64
	AdminID uint64
}

type RobotRoomGiftRule struct {
	GiftIDs              []string
	LuckyGiftIDs         []string
	NormalGiftIntervalMS int64
	LuckyComboMin        int64
	LuckyComboMax        int64
	LuckyPauseMinMS      int64
	LuckyPauseMaxMS      int64
	RobotStayMinMS       int64
	RobotStayMaxMS       int64
	RobotReplaceMinMS    int64
	RobotReplaceMaxMS    int64
	MaxGiftSenders       int64
}

type RobotRoom struct {
	AppCode          string
	RoomID           string
	RoomShortID      string
	Title            string
	CoverURL         string
	VisibleRegionID  int64
	Status           string
	OwnerRobotUserID int64
	RobotUserIDs     []int64
	ActiveRobotCount int32
	SeatCount        int32
	GiftRule         RobotRoomGiftRule
	CreatedByAdminID uint64
	CreatedAtMS      int64
	UpdatedAtMS      int64
}

type ListRobotRoomsRequest struct {
	Page     int
	PageSize int
	Status   string
}

type ListRobotRoomsResult struct {
	Rooms []RobotRoom
	Total int64
}

type CreateRobotRoomRequest struct {
	OwnerRobotUserID      int64
	CandidateRobotUserIDs []int64
	MinRobotCount         int32
	MaxRobotCount         int32
	RoomName              string
	RoomAvatar            string
	VisibleRegionID       int64
	OwnerCountryCode      string
	SeatCount             int32
	GiftRule              RobotRoomGiftRule
	AdminID               uint64
}

type SetRobotRoomStatusRequest struct {
	RoomID  string
	Status  string
	AdminID uint64
}

type FilterAvailableRoomRobotsResult struct {
	AvailableUserIDs []int64
	OccupiedUserIDs  []int64
}

type RenameOwnerCountryCodeRequest struct {
	RequestID      string
	OldCountryCode string
	NewCountryCode string
	AdminID        uint64
}

type RenameOwnerCountryCodeResult struct {
	RoomListEntries int64
	RoomSnapshots   int64
	ServerTimeMs    int64
}

type CloseRoomRequest struct {
	RequestID   string
	RoomID      string
	ActorUserID int64
	Reason      string
}

type CloseRoomResult struct {
	Applied       bool
	RoomVersion   int64
	ServerTimeMs  int64
	RoomStatus    string
	OnlineUserNum int
}

type ReopenRoomRequest struct {
	RequestID   string
	RoomID      string
	ActorUserID int64
}

type GRPCClient struct {
	client      roomv1.RoomCommandServiceClient
	queryClient roomv1.RoomQueryServiceClient
}

func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient {
	return &GRPCClient{client: roomv1.NewRoomCommandServiceClient(conn), queryClient: roomv1.NewRoomQueryServiceClient(conn)}
}

func (c *GRPCClient) ListRooms(ctx context.Context, req ListRoomsRequest) (*ListRoomsResult, error) {
	resp, err := c.queryClient.AdminListRooms(ctx, &roomv1.AdminListRoomsRequest{
		Meta:            requestMeta(ctx, "", 0, "admin-list-rooms"),
		Page:            int32(req.Page),
		PageSize:        int32(req.PageSize),
		Keyword:         strings.TrimSpace(req.Keyword),
		Status:          strings.TrimSpace(req.Status),
		VisibleRegionId: req.RegionID,
		OwnerUserId:     req.OwnerUserID,
		OwnerUserIds:    append([]int64(nil), req.OwnerUserIDs...),
		SortBy:          strings.TrimSpace(req.SortBy),
		SortDirection:   strings.TrimSpace(req.SortDirection),
	})
	if err != nil {
		return nil, err
	}
	rooms := make([]Room, 0, len(resp.GetRooms()))
	for _, item := range resp.GetRooms() {
		rooms = append(rooms, roomFromProto(item))
	}
	return &ListRoomsResult{Rooms: rooms, Total: resp.GetTotal(), ServerTimeMs: resp.GetServerTimeMs()}, nil
}

func (c *GRPCClient) GetRoom(ctx context.Context, req GetRoomRequest) (*Room, error) {
	resp, err := c.queryClient.AdminGetRoom(ctx, &roomv1.AdminGetRoomRequest{
		Meta:   requestMeta(ctx, strings.TrimSpace(req.RoomID), 0, "admin-get-room"),
		RoomId: strings.TrimSpace(req.RoomID),
	})
	if err != nil {
		return nil, err
	}
	room := roomFromProto(resp.GetRoom())
	return &room, nil
}

func (c *GRPCClient) UpdateRoom(ctx context.Context, req UpdateRoomRequest) (*CloseRoomResult, error) {
	resp, err := c.client.AdminUpdateRoom(ctx, &roomv1.AdminUpdateRoomRequest{
		Meta:            requestMeta(ctx, req.RoomID, req.ActorUserID, firstNonEmpty(req.RequestID, "admin-update-room")),
		Title:           req.Title,
		CoverUrl:        req.CoverURL,
		Description:     req.Description,
		Mode:            req.Mode,
		Status:          req.Status,
		VisibleRegionId: req.VisibleRegionID,
		CloseReason:     strings.TrimSpace(req.CloseReason),
		AdminId:         req.AdminID,
		AdminName:       strings.TrimSpace(req.AdminName),
	})
	if err != nil {
		return nil, err
	}
	return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}

func (c *GRPCClient) BanRoom(ctx context.Context, req ModerateRoomRequest) (*CloseRoomResult, error) {
	resp, err := c.client.AdminBanRoom(ctx, &roomv1.AdminBanRoomRequest{
		Meta:      requestMeta(ctx, req.RoomID, req.ActorUserID, firstNonEmpty(req.RequestID, "admin-ban-room")),
		Reason:    strings.TrimSpace(req.Reason),
		AdminId:   req.AdminID,
		AdminName: strings.TrimSpace(req.AdminName),
	})
	if err != nil {
		return nil, err
	}
	return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}

func (c *GRPCClient) UnbanRoom(ctx context.Context, req ModerateRoomRequest) (*CloseRoomResult, error) {
	resp, err := c.client.AdminUnbanRoom(ctx, &roomv1.AdminUnbanRoomRequest{
		Meta:      requestMeta(ctx, req.RoomID, req.ActorUserID, firstNonEmpty(req.RequestID, "admin-unban-room")),
		Reason:    strings.TrimSpace(req.Reason),
		AdminId:   req.AdminID,
		AdminName: strings.TrimSpace(req.AdminName),
	})
	if err != nil {
		return nil, err
	}
	return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}

func (c *GRPCClient) DeleteRoom(ctx context.Context, req DeleteRoomRequest) (*CloseRoomResult, error) {
	resp, err := c.client.AdminDeleteRoom(ctx, &roomv1.AdminDeleteRoomRequest{
		Meta:      requestMeta(ctx, req.RoomID, req.ActorUserID, firstNonEmpty(req.RequestID, "admin-delete-room")),
		AdminId:   req.AdminID,
		AdminName: strings.TrimSpace(req.AdminName),
	})
	if err != nil {
		return nil, err
	}
	return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}

func (c *GRPCClient) GetRoomRocketConfig(ctx context.Context) (RoomRocketConfig, error) {
	resp, err := c.queryClient.AdminGetRoomRocketConfig(ctx, &roomv1.AdminGetRoomRocketConfigRequest{
		Meta: requestMeta(ctx, "", 0, "admin-get-room-rocket-config"),
	})
	if err != nil {
		return RoomRocketConfig{}, err
	}
	return roomRocketConfigFromProto(resp.GetConfig()), nil
}

func (c *GRPCClient) UpdateRoomRocketConfig(ctx context.Context, req UpdateRoomRocketConfigRequest) (RoomRocketConfig, error) {
	resp, err := c.client.AdminUpdateRoomRocketConfig(ctx, &roomv1.AdminUpdateRoomRocketConfigRequest{
		Meta:    requestMeta(ctx, "", req.AdminID, "admin-update-room-rocket-config"),
		Config:  roomRocketConfigToProto(req.Config),
		AdminId: req.AdminID,
	})
	if err != nil {
		return RoomRocketConfig{}, err
	}
	return roomRocketConfigFromProto(resp.GetConfig()), nil
}

func (c *GRPCClient) GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, error) {
	resp, err := c.queryClient.AdminGetRoomSeatConfig(ctx, &roomv1.AdminGetRoomSeatConfigRequest{Meta: requestMeta(ctx, "", 0, "admin-get-room-seat-config")})
	if err != nil {
		return RoomSeatConfig{}, err
	}
	return roomSeatConfigFromProto(resp.GetConfig()), nil
}

func (c *GRPCClient) UpdateRoomSeatConfig(ctx context.Context, req UpdateRoomSeatConfigRequest) (RoomSeatConfig, error) {
	resp, err := c.client.AdminUpdateRoomSeatConfig(ctx, &roomv1.AdminUpdateRoomSeatConfigRequest{
		Meta:              requestMeta(ctx, "", 0, "admin-update-room-seat-config"),
		AllowedSeatCounts: req.AllowedSeatCounts,
		DefaultSeatCount:  req.DefaultSeatCount,
	})
	if err != nil {
		return RoomSeatConfig{}, err
	}
	return roomSeatConfigFromProto(resp.GetConfig()), nil
}

func (c *GRPCClient) GetHumanRoomRobotConfig(ctx context.Context) (HumanRoomRobotConfig, error) {
	resp, err := c.queryClient.AdminGetHumanRoomRobotConfig(ctx, &roomv1.AdminGetHumanRoomRobotConfigRequest{Meta: requestMeta(ctx, "", 0, "admin-get-human-room-robot-config")})
	if err != nil {
		return HumanRoomRobotConfig{}, err
	}
	return humanRoomRobotConfigFromProto(resp.GetConfig()), nil
}

func (c *GRPCClient) UpdateHumanRoomRobotConfig(ctx context.Context, req UpdateHumanRoomRobotConfigRequest) (HumanRoomRobotConfig, error) {
	resp, err := c.client.AdminUpdateHumanRoomRobotConfig(ctx, &roomv1.AdminUpdateHumanRoomRobotConfigRequest{
		Meta:    requestMeta(ctx, "", int64(req.AdminID), "admin-update-human-room-robot-config"),
		Config:  humanRoomRobotConfigToProto(req.Config),
		AdminId: req.AdminID,
	})
	if err != nil {
		return HumanRoomRobotConfig{}, err
	}
	return humanRoomRobotConfigFromProto(resp.GetConfig()), nil
}

func (c *GRPCClient) ListRoomPins(ctx context.Context, req ListRoomPinsRequest) (ListRoomPinsResult, error) {
	resp, err := c.queryClient.AdminListRoomPins(ctx, &roomv1.AdminListRoomPinsRequest{
		Meta:            requestMeta(ctx, "", 0, "admin-list-room-pins"),
		Page:            int32(req.Page),
		PageSize:        int32(req.PageSize),
		Keyword:         strings.TrimSpace(req.Keyword),
		Status:          strings.TrimSpace(req.Status),
		VisibleRegionId: req.RegionID,
		PinType:         strings.TrimSpace(req.PinType),
	})
	if err != nil {
		return ListRoomPinsResult{}, err
	}
	pins := make([]RoomPin, 0, len(resp.GetPins()))
	for _, pin := range resp.GetPins() {
		pins = append(pins, roomPinFromProto(pin))
	}
	return ListRoomPinsResult{Pins: pins, Total: resp.GetTotal()}, nil
}

func (c *GRPCClient) CreateRoomPin(ctx context.Context, req CreateRoomPinRequest) (RoomPin, error) {
	resp, err := c.client.AdminCreateRoomPin(ctx, &roomv1.AdminCreateRoomPinRequest{
		Meta:         requestMeta(ctx, strings.TrimSpace(req.RoomID), int64(req.AdminID), "admin-create-room-pin"),
		RoomId:       strings.TrimSpace(req.RoomID),
		PinType:      strings.TrimSpace(req.PinType),
		Weight:       req.Weight,
		DurationDays: req.DurationDays,
		PinnedAtMs:   req.PinnedAtMS,
		ExpiresAtMs:  req.ExpiresAtMS,
		AdminId:      req.AdminID,
	})
	if err != nil {
		return RoomPin{}, err
	}
	return roomPinFromProto(resp.GetPin()), nil
}

func (c *GRPCClient) CancelRoomPin(ctx context.Context, req CancelRoomPinRequest) (RoomPin, error) {
	resp, err := c.client.AdminCancelRoomPin(ctx, &roomv1.AdminCancelRoomPinRequest{
		Meta:    requestMeta(ctx, "", int64(req.AdminID), "admin-cancel-room-pin"),
		PinId:   req.PinID,
		AdminId: req.AdminID,
	})
	if err != nil {
		return RoomPin{}, err
	}
	return roomPinFromProto(resp.GetPin()), nil
}

func (c *GRPCClient) ListRobotRooms(ctx context.Context, req ListRobotRoomsRequest) (ListRobotRoomsResult, error) {
	resp, err := c.queryClient.AdminListRobotRooms(ctx, &roomv1.AdminListRobotRoomsRequest{
		Meta:     requestMeta(ctx, "", 0, "admin-list-robot-rooms"),
		Page:     int32(req.Page),
		PageSize: int32(req.PageSize),
		Status:   strings.TrimSpace(req.Status),
	})
	if err != nil {
		return ListRobotRoomsResult{}, err
	}
	items := make([]RobotRoom, 0, len(resp.GetRooms()))
	for _, item := range resp.GetRooms() {
		items = append(items, robotRoomFromProto(item))
	}
	return ListRobotRoomsResult{Rooms: items, Total: resp.GetTotal()}, nil
}

func (c *GRPCClient) CreateRobotRoom(ctx context.Context, req CreateRobotRoomRequest) (RobotRoom, error) {
	resp, err := c.client.AdminCreateRobotRoom(ctx, &roomv1.AdminCreateRobotRoomRequest{
		Meta:                  requestMeta(ctx, "", int64(req.AdminID), "admin-create-robot-room"),
		OwnerRobotUserId:      req.OwnerRobotUserID,
		CandidateRobotUserIds: append([]int64(nil), req.CandidateRobotUserIDs...),
		MinRobotCount:         req.MinRobotCount,
		MaxRobotCount:         req.MaxRobotCount,
		RoomName:              strings.TrimSpace(req.RoomName),
		RoomAvatar:            strings.TrimSpace(req.RoomAvatar),
		VisibleRegionId:       req.VisibleRegionID,
		OwnerCountryCode:      strings.ToUpper(strings.TrimSpace(req.OwnerCountryCode)),
		SeatCount:             req.SeatCount,
		GiftRule:              robotRoomGiftRuleToProto(req.GiftRule),
		AdminId:               req.AdminID,
	})
	if err != nil {
		return RobotRoom{}, err
	}
	return robotRoomFromProto(resp.GetRoom()), nil
}

func (c *GRPCClient) SetRobotRoomStatus(ctx context.Context, req SetRobotRoomStatusRequest) (RobotRoom, error) {
	resp, err := c.client.AdminSetRobotRoomStatus(ctx, &roomv1.AdminSetRobotRoomStatusRequest{
		Meta:    requestMeta(ctx, strings.TrimSpace(req.RoomID), int64(req.AdminID), "admin-set-robot-room-status"),
		Status:  strings.TrimSpace(req.Status),
		AdminId: req.AdminID,
	})
	if err != nil {
		return RobotRoom{}, err
	}
	return robotRoomFromProto(resp.GetRoom()), nil
}

func (c *GRPCClient) FilterAvailableRoomRobots(ctx context.Context, userIDs []int64) (FilterAvailableRoomRobotsResult, error) {
	resp, err := c.queryClient.AdminFilterAvailableRoomRobots(ctx, &roomv1.AdminFilterAvailableRoomRobotsRequest{
		Meta:    requestMeta(ctx, "", 0, "admin-filter-available-room-robots"),
		UserIds: append([]int64(nil), userIDs...),
	})
	if err != nil {
		return FilterAvailableRoomRobotsResult{}, err
	}
	return FilterAvailableRoomRobotsResult{
		AvailableUserIDs: append([]int64(nil), resp.GetAvailableUserIds()...),
		OccupiedUserIDs:  append([]int64(nil), resp.GetOccupiedUserIds()...),
	}, nil
}

func (c *GRPCClient) RenameOwnerCountryCode(ctx context.Context, req RenameOwnerCountryCodeRequest) (RenameOwnerCountryCodeResult, error) {
	if c == nil || c.client == nil {
		return RenameOwnerCountryCodeResult{}, fmt.Errorf("room service client is not configured")
	}
	requestID := firstNonEmpty(req.RequestID, fmt.Sprintf("admin-country-code-rename-%d", time.Now().UnixMilli()))
	resp, err := c.client.AdminRenameOwnerCountryCode(ctx, &roomv1.AdminRenameOwnerCountryCodeRequest{
		Meta:           requestMeta(ctx, "", int64(req.AdminID), requestID),
		OldCountryCode: strings.ToUpper(strings.TrimSpace(req.OldCountryCode)),
		NewCountryCode: strings.ToUpper(strings.TrimSpace(req.NewCountryCode)),
		AdminId:        req.AdminID,
	})
	if err != nil {
		return RenameOwnerCountryCodeResult{}, err
	}
	return RenameOwnerCountryCodeResult{
		RoomListEntries: resp.GetRoomListEntries(),
		RoomSnapshots:   resp.GetRoomSnapshots(),
		ServerTimeMs:    resp.GetServerTimeMs(),
	}, nil
}

func (c *GRPCClient) CloseRoom(ctx context.Context, req CloseRoomRequest) (*CloseRoomResult, error) {
	return c.setRoomLifecycle(ctx, req.RequestID, req.RoomID, req.ActorUserID, firstNonEmpty(req.Reason, "admin_closed"))
}

func (c *GRPCClient) ReopenRoom(ctx context.Context, req ReopenRoomRequest) (*CloseRoomResult, error) {
	return c.setRoomLifecycle(ctx, req.RequestID, req.RoomID, req.ActorUserID, "admin_reopen")
}

func (c *GRPCClient) setRoomLifecycle(ctx context.Context, rawRequestID string, rawRoomID string, actorUserID int64, rawReason string) (*CloseRoomResult, error) {
	if c == nil || c.client == nil {
		return nil, fmt.Errorf("room service client is not configured")
	}
	roomID := strings.TrimSpace(rawRoomID)
	reason := strings.TrimSpace(rawReason)
	if roomID == "" || actorUserID <= 0 || reason == "" {
		return nil, fmt.Errorf("room lifecycle request is incomplete")
	}
	requestID := strings.TrimSpace(rawRequestID)
	if requestID == "" {
		requestID = fmt.Sprintf("admin-room-lifecycle-%s-%d", roomID, time.Now().UnixMilli())
	}
	resp, err := c.client.CloseRoom(ctx, &roomv1.CloseRoomRequest{
		Meta:   requestMeta(ctx, roomID, actorUserID, requestID+":"+reason),
		Reason: reason,
	})
	if err != nil {
		return nil, err
	}
	return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}

func closeRoomResultFromCommand(result *roomv1.CommandResult, room *roomv1.RoomSnapshot) *CloseRoomResult {
	return &CloseRoomResult{
		Applied:       result.GetApplied(),
		RoomVersion:   result.GetRoomVersion(),
		ServerTimeMs:  result.GetServerTimeMs(),
		RoomStatus:    room.GetStatus(),
		OnlineUserNum: len(room.GetOnlineUsers()),
	}
}

func commandID(requestID string, reason string, roomID string) string {
	base := strings.TrimSpace(requestID)
	if base == "" {
		base = fmt.Sprintf("admin-room-lifecycle-%d", time.Now().UnixMilli())
	}
	value := base + ":" + strings.TrimSpace(reason) + ":" + strings.TrimSpace(roomID)
	if len(value) <= 128 {
		return value
	}
	return value[:128]
}

func requestMeta(ctx context.Context, roomID string, actorUserID int64, seed string) *roomv1.RequestMeta {
	requestID := strings.TrimSpace(seed)
	if requestID == "" {
		requestID = fmt.Sprintf("admin-room-%d", time.Now().UnixMilli())
	}
	return &roomv1.RequestMeta{
		RequestId:   requestID,
		CommandId:   commandID(requestID, "admin", roomID),
		ActorUserId: actorUserID,
		RoomId:      strings.TrimSpace(roomID),
		AppCode:     appctx.FromContext(ctx),
		SentAtMs:    time.Now().UnixMilli(),
	}
}

func roomFromProto(item *roomv1.AdminRoomListItem) Room {
	if item == nil {
		return Room{}
	}
	return Room{
		RoomID:            item.GetRoomId(),
		RoomShortID:       item.GetRoomShortId(),
		VisibleRegionID:   item.GetVisibleRegionId(),
		OwnerUserID:       item.GetOwnerUserId(),
		Title:             item.GetTitle(),
		CoverURL:          item.GetCoverUrl(),
		Mode:              item.GetMode(),
		Status:            item.GetStatus(),
		CloseReason:       item.GetCloseReason(),
		ClosedByAdminID:   item.GetClosedByAdminId(),
		ClosedByAdminName: item.GetClosedByAdminName(),
		ClosedAtMS:        item.GetClosedAtMs(),
		Heat:              item.GetHeat(),
		OnlineCount:       item.GetOnlineCount(),
		SeatCount:         item.GetSeatCount(),
		OccupiedSeatCount: item.GetOccupiedSeatCount(),
		SortScore:         item.GetSortScore(),
		CreatedAtMS:       item.GetCreatedAtMs(),
		UpdatedAtMS:       item.GetUpdatedAtMs(),
	}
}

func roomRocketConfigFromProto(input *roomv1.AdminRoomRocketConfig) RoomRocketConfig {
	if input == nil {
		return RoomRocketConfig{}
	}
	return RoomRocketConfig{
		AppCode:           input.GetAppCode(),
		Enabled:           input.GetEnabled(),
		ConfigVersion:     input.GetConfigVersion(),
		FuelSource:        input.GetFuelSource(),
		LaunchDelayMS:     input.GetLaunchDelayMs(),
		BroadcastEnabled:  input.GetBroadcastEnabled(),
		BroadcastScope:    input.GetBroadcastScope(),
		BroadcastDelayMS:  input.GetBroadcastDelayMs(),
		RewardStackPolicy: input.GetRewardStackPolicy(),
		Levels:            roomRocketLevelsFromProto(input.GetLevels()),
		GiftFuelRules:     roomRocketFuelRulesFromProto(input.GetGiftFuelRules()),
		UpdatedByAdminID:  input.GetUpdatedByAdminId(),
		CreatedAtMS:       input.GetCreatedAtMs(),
		UpdatedAtMS:       input.GetUpdatedAtMs(),
	}
}

func roomSeatConfigFromProto(input *roomv1.AdminRoomSeatConfig) RoomSeatConfig {
	if input == nil {
		return RoomSeatConfig{}
	}
	return RoomSeatConfig{
		CandidateSeatCounts: input.GetCandidateSeatCounts(),
		AllowedSeatCounts:   input.GetAllowedSeatCounts(),
		DefaultSeatCount:    input.GetDefaultSeatCount(),
		UpdatedAtMS:         input.GetUpdatedAtMs(),
	}
}

func humanRoomRobotConfigFromProto(input *roomv1.AdminHumanRoomRobotConfig) HumanRoomRobotConfig {
	if input == nil {
		return HumanRoomRobotConfig{}
	}
	config := HumanRoomRobotConfig{
		AppCode:                 input.GetAppCode(),
		Enabled:                 input.GetEnabled(),
		CandidateRoomMaxOnline:  input.GetCandidateRoomMaxOnline(),
		RoomFullStopOnline:      input.GetRoomFullStopOnline(),
		RoomTargetMinOnline:     input.GetRoomTargetMinOnline(),
		RoomTargetMaxOnline:     input.GetRoomTargetMaxOnline(),
		RobotStayMinMS:          input.GetRobotStayMinMs(),
		RobotStayMaxMS:          input.GetRobotStayMaxMs(),
		RobotReplaceMinMS:       input.GetRobotReplaceMinMs(),
		RobotReplaceMaxMS:       input.GetRobotReplaceMaxMs(),
		NormalGiftIntervalMS:    input.GetNormalGiftIntervalMs(),
		NormalGiftIntervalMinMS: input.GetNormalGiftIntervalMinMs(),
		NormalGiftIntervalMaxMS: input.GetNormalGiftIntervalMaxMs(),
		GiftIDs:                 append([]string(nil), input.GetGiftIds()...),
		LuckyGiftIDs:            append([]string(nil), input.GetLuckyGiftIds()...),
		SuperLuckyGiftIDs:       append([]string(nil), input.GetSuperLuckyGiftIds()...),
		LuckyComboMin:           input.GetLuckyComboMin(),
		LuckyComboMax:           input.GetLuckyComboMax(),
		LuckyPauseMinMS:         input.GetLuckyPauseMinMs(),
		LuckyPauseMaxMS:         input.GetLuckyPauseMaxMs(),
		MaxGiftSenders:          input.GetMaxGiftSenders(),
		AllowedOwnerIDs:         append([]string(nil), input.GetAllowedOwnerIds()...),
		CountryLimitEnabled:     input.GetCountryLimitEnabled(),
		UpdatedByAdminID:        input.GetUpdatedByAdminId(),
		CreatedAtMS:             input.GetCreatedAtMs(),
		UpdatedAtMS:             input.GetUpdatedAtMs(),
	}
	for _, rule := range input.GetCountryRules() {
		config.CountryRules = append(config.CountryRules, HumanRoomRobotCountryRule{
			CountryCode:     rule.GetCountryCode(),
			MaxRoomCount:    rule.GetMaxRoomCount(),
			AllowedOwnerIDs: append([]string(nil), rule.GetAllowedOwnerIds()...),
		})
	}
	return config
}

func humanRoomRobotConfigToProto(input HumanRoomRobotConfig) *roomv1.AdminHumanRoomRobotConfig {
	out := &roomv1.AdminHumanRoomRobotConfig{
		AppCode:                 input.AppCode,
		Enabled:                 input.Enabled,
		CandidateRoomMaxOnline:  input.CandidateRoomMaxOnline,
		RoomFullStopOnline:      input.RoomFullStopOnline,
		RoomTargetMinOnline:     input.RoomTargetMinOnline,
		RoomTargetMaxOnline:     input.RoomTargetMaxOnline,
		RobotStayMinMs:          input.RobotStayMinMS,
		RobotStayMaxMs:          input.RobotStayMaxMS,
		RobotReplaceMinMs:       input.RobotReplaceMinMS,
		RobotReplaceMaxMs:       input.RobotReplaceMaxMS,
		NormalGiftIntervalMs:    input.NormalGiftIntervalMS,
		NormalGiftIntervalMinMs: input.NormalGiftIntervalMinMS,
		NormalGiftIntervalMaxMs: input.NormalGiftIntervalMaxMS,
		GiftIds:                 append([]string(nil), input.GiftIDs...),
		LuckyGiftIds:            append([]string(nil), input.LuckyGiftIDs...),
		SuperLuckyGiftIds:       append([]string(nil), input.SuperLuckyGiftIDs...),
		LuckyComboMin:           input.LuckyComboMin,
		LuckyComboMax:           input.LuckyComboMax,
		LuckyPauseMinMs:         input.LuckyPauseMinMS,
		LuckyPauseMaxMs:         input.LuckyPauseMaxMS,
		MaxGiftSenders:          input.MaxGiftSenders,
		AllowedOwnerIds:         append([]string(nil), input.AllowedOwnerIDs...),
		CountryLimitEnabled:     input.CountryLimitEnabled,
		UpdatedByAdminId:        input.UpdatedByAdminID,
		CreatedAtMs:             input.CreatedAtMS,
		UpdatedAtMs:             input.UpdatedAtMS,
	}
	for _, rule := range input.CountryRules {
		out.CountryRules = append(out.CountryRules, &roomv1.AdminHumanRoomRobotCountryRule{
			CountryCode:     rule.CountryCode,
			MaxRoomCount:    rule.MaxRoomCount,
			AllowedOwnerIds: append([]string(nil), rule.AllowedOwnerIDs...),
		})
	}
	return out
}

func roomPinFromProto(input *roomv1.AdminRoomPin) RoomPin {
	if input == nil {
		return RoomPin{}
	}
	room := input.GetRoom()
	return RoomPin{
		ID:                 input.GetId(),
		VisibleRegionID:    input.GetVisibleRegionId(),
		PinType:            input.GetPinType(),
		RoomID:             input.GetRoomId(),
		Weight:             input.GetWeight(),
		Status:             input.GetStatus(),
		PinnedAtMS:         input.GetPinnedAtMs(),
		ExpiresAtMS:        input.GetExpiresAtMs(),
		CancelledAtMS:      input.GetCancelledAtMs(),
		CreatedByAdminID:   input.GetCreatedByAdminId(),
		CancelledByAdminID: input.GetCancelledByAdminId(),
		CreatedAtMS:        input.GetCreatedAtMs(),
		UpdatedAtMS:        input.GetUpdatedAtMs(),
		Room: RoomPinRoom{
			RoomID:          room.GetRoomId(),
			RoomShortID:     room.GetRoomShortId(),
			VisibleRegionID: room.GetVisibleRegionId(),
			OwnerUserID:     room.GetOwnerUserId(),
			Title:           room.GetTitle(),
			CoverURL:        room.GetCoverUrl(),
			Status:          room.GetStatus(),
		},
	}
}

func robotRoomFromProto(input *roomv1.AdminRobotRoom) RobotRoom {
	if input == nil {
		return RobotRoom{}
	}
	return RobotRoom{
		AppCode:          input.GetAppCode(),
		RoomID:           input.GetRoomId(),
		RoomShortID:      input.GetRoomShortId(),
		Title:            input.GetTitle(),
		CoverURL:         input.GetCoverUrl(),
		VisibleRegionID:  input.GetVisibleRegionId(),
		Status:           input.GetStatus(),
		OwnerRobotUserID: input.GetOwnerRobotUserId(),
		RobotUserIDs:     append([]int64(nil), input.GetRobotUserIds()...),
		ActiveRobotCount: input.GetActiveRobotCount(),
		SeatCount:        input.GetSeatCount(),
		GiftRule:         robotRoomGiftRuleFromProto(input.GetGiftRule()),
		CreatedByAdminID: input.GetCreatedByAdminId(),
		CreatedAtMS:      input.GetCreatedAtMs(),
		UpdatedAtMS:      input.GetUpdatedAtMs(),
	}
}

func robotRoomGiftRuleFromProto(input *roomv1.AdminRobotRoomGiftRule) RobotRoomGiftRule {
	if input == nil {
		return RobotRoomGiftRule{}
	}
	return RobotRoomGiftRule{
		GiftIDs:              append([]string(nil), input.GetGiftIds()...),
		LuckyGiftIDs:         append([]string(nil), input.GetLuckyGiftIds()...),
		NormalGiftIntervalMS: input.GetNormalGiftIntervalMs(),
		LuckyComboMin:        input.GetLuckyComboMin(),
		LuckyComboMax:        input.GetLuckyComboMax(),
		LuckyPauseMinMS:      input.GetLuckyPauseMinMs(),
		LuckyPauseMaxMS:      input.GetLuckyPauseMaxMs(),
		RobotStayMinMS:       input.GetRobotStayMinMs(),
		RobotStayMaxMS:       input.GetRobotStayMaxMs(),
		RobotReplaceMinMS:    input.GetRobotReplaceMinMs(),
		RobotReplaceMaxMS:    input.GetRobotReplaceMaxMs(),
		MaxGiftSenders:       input.GetMaxGiftSenders(),
	}
}

func robotRoomGiftRuleToProto(input RobotRoomGiftRule) *roomv1.AdminRobotRoomGiftRule {
	return &roomv1.AdminRobotRoomGiftRule{
		GiftIds:              append([]string(nil), input.GiftIDs...),
		LuckyGiftIds:         append([]string(nil), input.LuckyGiftIDs...),
		NormalGiftIntervalMs: input.NormalGiftIntervalMS,
		LuckyComboMin:        input.LuckyComboMin,
		LuckyComboMax:        input.LuckyComboMax,
		LuckyPauseMinMs:      input.LuckyPauseMinMS,
		LuckyPauseMaxMs:      input.LuckyPauseMaxMS,
		RobotStayMinMs:       input.RobotStayMinMS,
		RobotStayMaxMs:       input.RobotStayMaxMS,
		RobotReplaceMinMs:    input.RobotReplaceMinMS,
		RobotReplaceMaxMs:    input.RobotReplaceMaxMS,
		MaxGiftSenders:       input.MaxGiftSenders,
	}
}

func roomRocketConfigToProto(input RoomRocketConfig) *roomv1.AdminRoomRocketConfig {
	return &roomv1.AdminRoomRocketConfig{
		AppCode:           input.AppCode,
		Enabled:           input.Enabled,
		FuelSource:        input.FuelSource,
		LaunchDelayMs:     input.LaunchDelayMS,
		BroadcastEnabled:  input.BroadcastEnabled,
		BroadcastScope:    input.BroadcastScope,
		BroadcastDelayMs:  input.BroadcastDelayMS,
		RewardStackPolicy: input.RewardStackPolicy,
		Levels:            roomRocketLevelsToProto(input.Levels),
		GiftFuelRules:     roomRocketFuelRulesToProto(input.GiftFuelRules),
	}
}

func roomRocketLevelsFromProto(input []*roomv1.RoomRocketLevel) []RoomRocketLevelConfig {
	out := make([]RoomRocketLevelConfig, 0, len(input))
	for _, level := range input {
		if level == nil {
			continue
		}
		out = append(out, RoomRocketLevelConfig{
			Level:              level.GetLevel(),
			FuelThreshold:      level.GetFuelThreshold(),
			CoverURL:           level.GetCoverUrl(),
			AnimationURL:       level.GetAnimationUrl(),
			LaunchAnimationURL: level.GetLaunchAnimationUrl(),
			LaunchedImageURL:   level.GetLaunchedImageUrl(),
			InRoomRewards:      roomRocketRewardsFromProto(level.GetInRoomRewards()),
			Top1Rewards:        roomRocketRewardsFromProto(level.GetTop1Rewards()),
			IgniterRewards:     roomRocketRewardsFromProto(level.GetIgniterRewards()),
		})
	}
	return out
}

func roomRocketLevelsToProto(input []RoomRocketLevelConfig) []*roomv1.RoomRocketLevel {
	out := make([]*roomv1.RoomRocketLevel, 0, len(input))
	for _, level := range input {
		out = append(out, &roomv1.RoomRocketLevel{
			Level:              level.Level,
			FuelThreshold:      level.FuelThreshold,
			CoverUrl:           level.CoverURL,
			AnimationUrl:       level.AnimationURL,
			LaunchAnimationUrl: level.LaunchAnimationURL,
			LaunchedImageUrl:   level.LaunchedImageURL,
			InRoomRewards:      roomRocketRewardsToProto(level.InRoomRewards),
			Top1Rewards:        roomRocketRewardsToProto(level.Top1Rewards),
			IgniterRewards:     roomRocketRewardsToProto(level.IgniterRewards),
		})
	}
	return out
}

func roomRocketRewardsFromProto(input []*roomv1.RoomRocketRewardItem) []RoomRocketRewardItem {
	out := make([]RoomRocketRewardItem, 0, len(input))
	for _, reward := range input {
		if reward == nil {
			continue
		}
		out = append(out, RoomRocketRewardItem{
			RewardItemID:    reward.GetRewardItemId(),
			ResourceGroupID: reward.GetResourceGroupId(),
			Weight:          reward.GetWeight(),
			Quantity:        reward.GetQuantity(),
			DisplayName:     reward.GetDisplayName(),
			IconURL:         reward.GetIconUrl(),
		})
	}
	return out
}

func roomRocketRewardsToProto(input []RoomRocketRewardItem) []*roomv1.RoomRocketRewardItem {
	out := make([]*roomv1.RoomRocketRewardItem, 0, len(input))
	for _, reward := range input {
		out = append(out, &roomv1.RoomRocketRewardItem{
			RewardItemId:    reward.RewardItemID,
			ResourceGroupId: reward.ResourceGroupID,
			Weight:          reward.Weight,
			Quantity:        reward.Quantity,
			DisplayName:     reward.DisplayName,
			IconUrl:         reward.IconURL,
		})
	}
	return out
}

func roomRocketFuelRulesFromProto(input []*roomv1.RoomRocketGiftFuelRule) []GiftFuelRuleConfig {
	out := make([]GiftFuelRuleConfig, 0, len(input))
	for _, rule := range input {
		if rule == nil {
			continue
		}
		out = append(out, GiftFuelRuleConfig{
			RuleID:        rule.GetRuleId(),
			GiftID:        rule.GetGiftId(),
			GiftTypeCode:  rule.GetGiftTypeCode(),
			MultiplierPPM: rule.GetMultiplierPpm(),
			FixedFuel:     rule.GetFixedFuel(),
			Excluded:      rule.GetExcluded(),
		})
	}
	return out
}

func roomRocketFuelRulesToProto(input []GiftFuelRuleConfig) []*roomv1.RoomRocketGiftFuelRule {
	out := make([]*roomv1.RoomRocketGiftFuelRule, 0, len(input))
	for _, rule := range input {
		out = append(out, &roomv1.RoomRocketGiftFuelRule{
			RuleId:        rule.RuleID,
			GiftId:        rule.GiftID,
			GiftTypeCode:  rule.GiftTypeCode,
			MultiplierPpm: rule.MultiplierPPM,
			FixedFuel:     rule.FixedFuel,
			Excluded:      rule.Excluded,
		})
	}
	return out
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
