package gameapi

import (
	"fmt"
	"strings"

	gamev1 "hyapp.local/api/proto/game/v1"
)

type gameListData struct {
	Games        []gameItemData `json:"games"`
	ServerTimeMS int64          `json:"server_time_ms"`
}

type gameItemData struct {
	GameID       string `json:"game_id"`
	PlatformCode string `json:"platform_code"`
	NameKey      string `json:"name_key"`
	Name         string `json:"name"`
	IconURL      string `json:"icon_url"`
	CoverURL     string `json:"cover_url"`
	Category     string `json:"category"`
	LaunchMode   string `json:"launch_mode"`
	Orientation  string `json:"orientation"`
	SafeHeight   int32  `json:"safe_height"`
	SafeHeightV2 int32  `json:"safeHeight"`
	MinCoin      int64  `json:"min_coin"`
	Enabled      bool   `json:"enabled"`
	Maintenance  bool   `json:"maintenance"`
	SortOrder    int32  `json:"sort_order"`
}

type exploreWinnerListData struct {
	Items        []exploreWinnerItemData `json:"items"`
	Total        int                     `json:"total"`
	ServerTimeMS int64                   `json:"server_time_ms"`
}

type exploreWinnerItemData struct {
	WinID        string `json:"win_id"`
	GameCode     string `json:"game_code"`
	GameID       string `json:"game_id"`
	UserID       string `json:"user_id"`
	UserIDNumber int64  `json:"user_id_number"`
	DisplayName  string `json:"display_name"`
	AvatarURL    string `json:"avatar_url"`
	CoinAmount   int64  `json:"coin_amount"`
	WonAtMS      int64  `json:"won_at_ms"`
}

type gameLaunchData struct {
	SessionID    string `json:"session_id"`
	LaunchURL    string `json:"launch_url"`
	ExpiresAtMS  int64  `json:"expires_at_ms"`
	Orientation  string `json:"orientation"`
	SafeHeight   int32  `json:"safe_height"`
	SafeHeightV2 int32  `json:"safeHeight"`
	ServerTimeMS int64  `json:"server_time_ms"`
}

type gameBridgeScriptData struct {
	BridgeScriptURL       string `json:"bridge_script_url"`
	BridgeScriptURLV2     string `json:"bridgeScriptUrl"`
	BridgeScriptVersion   string `json:"bridge_script_version"`
	BridgeScriptVersionV2 string `json:"bridgeScriptVersion"`
	BridgeScriptSHA256    string `json:"bridge_script_sha256"`
	BridgeScriptSHA256V2  string `json:"bridgeScriptSha256"`
	ServerTimeMS          int64  `json:"server_time_ms"`
}

type diceMatchData struct {
	Match        diceMatchItemData `json:"match"`
	ServerTimeMS int64             `json:"server_time_ms"`
}

type diceConfigData struct {
	Config       diceConfigItemData `json:"config"`
	ServerTimeMS int64              `json:"server_time_ms"`
}

type roomRPSConfigData struct {
	Config       roomRPSConfigItemData `json:"config"`
	ServerTimeMS int64                 `json:"server_time_ms"`
}

type roomRPSChallengeData struct {
	Challenge    roomRPSChallengeItemData `json:"challenge"`
	ServerTimeMS int64                    `json:"server_time_ms"`
}

type roomRPSChallengeListData struct {
	Challenges   []*roomRPSChallengeItemData `json:"challenges"`
	NextCursor   string                      `json:"next_cursor"`
	PageSize     int32                       `json:"page_size"`
	ServerTimeMS int64                       `json:"server_time_ms"`
}

type diceConfigItemData struct {
	AppCode          string                `json:"app_code"`
	GameID           string                `json:"game_id"`
	Status           string                `json:"status"`
	StakeOptions     []diceStakeOptionData `json:"stake_options"`
	FeeBPS           int32                 `json:"fee_bps"`
	PoolBPS          int32                 `json:"pool_bps"`
	MinPlayers       int32                 `json:"min_players"`
	MaxPlayers       int32                 `json:"max_players"`
	RobotEnabled     bool                  `json:"robot_enabled"`
	RobotMatchWaitMS int64                 `json:"robot_match_wait_ms"`
	PoolBalanceCoin  int64                 `json:"pool_balance_coin"`
}

type diceStakeOptionData struct {
	StakeCoin int64 `json:"stake_coin"`
	Enabled   bool  `json:"enabled"`
	SortOrder int32 `json:"sort_order"`
}

type roomRPSConfigItemData struct {
	AppCode            string                 `json:"app_code"`
	GameID             string                 `json:"game_id"`
	Status             string                 `json:"status"`
	ChallengeTimeoutMS int64                  `json:"challenge_timeout_ms"`
	RevealCountdownMS  int64                  `json:"reveal_countdown_ms"`
	StakeGifts         []roomRPSStakeGiftData `json:"stake_gifts"`
}

type roomRPSStakeGiftData struct {
	GiftID           string `json:"gift_id"`
	GiftIDNumber     int64  `json:"gift_id_number"`
	GiftName         string `json:"gift_name"`
	GiftIconURL      string `json:"gift_icon_url"`
	GiftAnimationURL string `json:"gift_animation_url"`
	GiftPriceCoin    int64  `json:"gift_price_coin"`
	Enabled          bool   `json:"enabled"`
	SortOrder        int32  `json:"sort_order"`
}

type diceMatchItemData struct {
	AppCode         string                    `json:"app_code"`
	MatchID         string                    `json:"match_id"`
	GameID          string                    `json:"game_id"`
	RoomID          string                    `json:"room_id"`
	RegionID        int64                     `json:"region_id"`
	MinPlayers      int32                     `json:"min_players"`
	MaxPlayers      int32                     `json:"max_players"`
	CurrentPlayers  int32                     `json:"current_players"`
	StakeCoin       int64                     `json:"stake_coin"`
	RoundNo         int32                     `json:"round_no"`
	Status          string                    `json:"status"`
	Result          string                    `json:"result"`
	Participants    []diceParticipantItemData `json:"participants"`
	JoinDeadlineMS  int64                     `json:"join_deadline_ms"`
	ReadyAtMS       int64                     `json:"ready_at_ms"`
	CreatedAtMS     int64                     `json:"created_at_ms"`
	UpdatedAtMS     int64                     `json:"updated_at_ms"`
	SettledAtMS     int64                     `json:"settled_at_ms"`
	CanceledAtMS    int64                     `json:"canceled_at_ms"`
	Phase           string                    `json:"phase"`
	PhaseDeadlineMS int64                     `json:"phase_deadline_ms"`
	FeeBPS          int32                     `json:"fee_bps"`
	PoolBPS         int32                     `json:"pool_bps"`
	MatchMode       string                    `json:"match_mode"`
	ForcedResult    string                    `json:"forced_result"`
	PoolDeltaCoin   int64                     `json:"pool_delta_coin"`
}

type diceParticipantItemData struct {
	UserID          string         `json:"user_id"`
	UserIDNumber    int64          `json:"user_id_number"`
	SeatNo          int32          `json:"seat_no"`
	Status          string         `json:"status"`
	StakeCoin       int64          `json:"stake_coin"`
	DicePoints      []int32        `json:"dice_points"`
	RPSGesture      string         `json:"rps_gesture"`
	Gesture         string         `json:"gesture"`
	Result          string         `json:"result"`
	PayoutCoin      int64          `json:"payout_coin"`
	BalanceAfter    int64          `json:"balance_after"`
	JoinedAtMS      int64          `json:"joined_at_ms"`
	UpdatedAtMS     int64          `json:"updated_at_ms"`
	ParticipantType string         `json:"participant_type"`
	IsRobot         bool           `json:"is_robot"`
	User            diceUserData   `json:"user"`
	AvatarFrame     map[string]any `json:"avatar_frame"`
}

type diceUserData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id"`
	Nickname      string `json:"nickname"`
	Avatar        string `json:"avatar"`
}

type roomRPSChallengeItemData struct {
	AppCode            string                `json:"app_code"`
	ChallengeID        string                `json:"challenge_id"`
	RoomID             string                `json:"room_id"`
	RegionID           int64                 `json:"region_id"`
	Status             string                `json:"status"`
	StakeGiftID        string                `json:"stake_gift_id"`
	StakeGiftIDNumber  int64                 `json:"stake_gift_id_number"`
	StakeCoin          int64                 `json:"stake_coin"`
	Initiator          roomRPSPlayerItemData `json:"initiator"`
	Challenger         roomRPSPlayerItemData `json:"challenger"`
	WinnerUserID       string                `json:"winner_user_id"`
	WinnerUserIDNumber int64                 `json:"winner_user_id_number"`
	SettlementStatus   string                `json:"settlement_status"`
	FailureReason      string                `json:"failure_reason"`
	TimeoutAtMS        int64                 `json:"timeout_at_ms"`
	RevealAtMS         int64                 `json:"reveal_at_ms"`
	CreatedAtMS        int64                 `json:"created_at_ms"`
	MatchedAtMS        int64                 `json:"matched_at_ms"`
	SettledAtMS        int64                 `json:"settled_at_ms"`
	UpdatedAtMS        int64                 `json:"updated_at_ms"`
}

type roomRPSPlayerItemData struct {
	UserID       string       `json:"user_id"`
	UserIDNumber int64        `json:"user_id_number"`
	Gesture      string       `json:"gesture"`
	Result       string       `json:"result"`
	BalanceAfter int64        `json:"balance_after"`
	JoinedAtMS   int64        `json:"joined_at_ms"`
	User         diceUserData `json:"user"`
}

func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData {
	if resp == nil {
		return gameListData{}
	}
	items := make([]gameItemData, 0, len(resp.GetGames()))
	for _, item := range resp.GetGames() {
		items = append(items, gameItemData{
			GameID:       item.GetGameId(),
			PlatformCode: item.GetPlatformCode(),
			NameKey:      item.GetNameKey(),
			Name:         item.GetName(),
			IconURL:      item.GetIconUrl(),
			CoverURL:     item.GetCoverUrl(),
			Category:     item.GetCategory(),
			LaunchMode:   item.GetLaunchMode(),
			Orientation:  item.GetOrientation(),
			SafeHeight:   item.GetSafeHeight(),
			SafeHeightV2: item.GetSafeHeight(),
			MinCoin:      item.GetMinCoin(),
			Enabled:      item.GetEnabled(),
			Maintenance:  item.GetMaintenance(),
			SortOrder:    item.GetSortOrder(),
		})
	}
	return gameListData{Games: items, ServerTimeMS: resp.GetServerTimeMs()}
}

func exploreWinnerListDataFromProto(resp *gamev1.ListExploreWinnersResponse) exploreWinnerListData {
	if resp == nil {
		return exploreWinnerListData{}
	}
	items := make([]exploreWinnerItemData, 0, len(resp.GetWinners()))
	for _, item := range resp.GetWinners() {
		items = append(items, exploreWinnerItemData{
			WinID:        item.GetWinId(),
			GameCode:     item.GetGameCode(),
			GameID:       item.GetGameId(),
			UserID:       int64String(item.GetUserId()),
			UserIDNumber: item.GetUserId(),
			DisplayName:  item.GetDisplayName(),
			AvatarURL:    item.GetAvatarUrl(),
			CoinAmount:   item.GetCoinAmount(),
			WonAtMS:      item.GetWonAtMs(),
		})
	}
	return exploreWinnerListData{Items: items, Total: len(items), ServerTimeMS: resp.GetServerTimeMs()}
}

func gameLaunchDataFromProto(resp *gamev1.LaunchGameResponse) gameLaunchData {
	if resp == nil {
		return gameLaunchData{}
	}
	return gameLaunchData{
		SessionID:    resp.GetSessionId(),
		LaunchURL:    resp.GetLaunchUrl(),
		ExpiresAtMS:  resp.GetExpiresAtMs(),
		Orientation:  resp.GetOrientation(),
		SafeHeight:   resp.GetSafeHeight(),
		SafeHeightV2: resp.GetSafeHeight(),
		ServerTimeMS: resp.GetServerTimeMs(),
	}
}

func diceConfigDataFromProto(resp *gamev1.DiceConfigResponse) diceConfigData {
	if resp == nil {
		return diceConfigData{}
	}
	config := resp.GetConfig()
	options := make([]diceStakeOptionData, 0, len(config.GetStakeOptions()))
	for _, option := range config.GetStakeOptions() {
		options = append(options, diceStakeOptionData{
			StakeCoin: option.GetStakeCoin(),
			Enabled:   option.GetEnabled(),
			SortOrder: option.GetSortOrder(),
		})
	}
	return diceConfigData{
		Config: diceConfigItemData{
			AppCode:          config.GetAppCode(),
			GameID:           config.GetGameId(),
			Status:           config.GetStatus(),
			StakeOptions:     options,
			FeeBPS:           config.GetFeeBps(),
			PoolBPS:          config.GetPoolBps(),
			MinPlayers:       config.GetMinPlayers(),
			MaxPlayers:       config.GetMaxPlayers(),
			RobotEnabled:     config.GetRobotEnabled(),
			RobotMatchWaitMS: config.GetRobotMatchWaitMs(),
			PoolBalanceCoin:  config.GetPoolBalanceCoin(),
		},
		ServerTimeMS: resp.GetServerTimeMs(),
	}
}

func roomRPSConfigDataFromProto(resp *gamev1.RoomRPSConfigResponse) roomRPSConfigData {
	if resp == nil {
		return roomRPSConfigData{}
	}
	// 配置响应保持一层 config 包装，方便 Flutter 在同一个模型里同时读取配置和 server_time_ms 做倒计时校准。
	return roomRPSConfigData{Config: roomRPSConfigItemDataFromProto(resp.GetConfig()), ServerTimeMS: resp.GetServerTimeMs()}
}

func roomRPSConfigItemDataFromProto(config *gamev1.RoomRPSConfig) roomRPSConfigItemData {
	if config == nil {
		return roomRPSConfigItemData{}
	}
	gifts := make([]roomRPSStakeGiftData, 0, len(config.GetStakeGifts()))
	for _, gift := range config.GetStakeGifts() {
		// 礼物 ID 以字符串为主，兼容数字字段；Flutter 展示、缓存和提交 body 都优先使用 gift_id。
		gifts = append(gifts, roomRPSStakeGiftData{
			GiftID:           roomRPSStakeGiftIDFromProto(gift),
			GiftIDNumber:     gift.GetGiftId(),
			GiftName:         gift.GetGiftName(),
			GiftIconURL:      gift.GetGiftIconUrl(),
			GiftAnimationURL: gift.GetGiftAnimationUrl(),
			GiftPriceCoin:    gift.GetGiftPriceCoin(),
			Enabled:          gift.GetEnabled(),
			SortOrder:        gift.GetSortOrder(),
		})
	}
	return roomRPSConfigItemData{
		AppCode:            config.GetAppCode(),
		GameID:             config.GetGameId(),
		Status:             config.GetStatus(),
		ChallengeTimeoutMS: config.GetChallengeTimeoutMs(),
		RevealCountdownMS:  config.GetRevealCountdownMs(),
		StakeGifts:         gifts,
	}
}

func diceMatchDataFromProto(resp *gamev1.DiceMatchResponse) diceMatchData {
	if resp == nil {
		return diceMatchData{}
	}
	return diceMatchData{
		Match:        diceMatchItemDataFromProto(resp.GetMatch()),
		ServerTimeMS: resp.GetServerTimeMs(),
	}
}

func diceMatchItemDataFromProto(match *gamev1.DiceMatch) diceMatchItemData {
	if match == nil {
		return diceMatchItemData{}
	}
	participants := make([]diceParticipantItemData, 0, len(match.GetParticipants()))
	for _, participant := range match.GetParticipants() {
		participants = append(participants, diceParticipantItemDataFromProto(participant))
	}
	return diceMatchItemData{
		AppCode:         match.GetAppCode(),
		MatchID:         match.GetMatchId(),
		GameID:          match.GetGameId(),
		RoomID:          match.GetRoomId(),
		RegionID:        match.GetRegionId(),
		MinPlayers:      match.GetMinPlayers(),
		MaxPlayers:      match.GetMaxPlayers(),
		CurrentPlayers:  match.GetCurrentPlayers(),
		StakeCoin:       match.GetStakeCoin(),
		RoundNo:         match.GetRoundNo(),
		Status:          match.GetStatus(),
		Result:          match.GetResult(),
		Participants:    participants,
		JoinDeadlineMS:  match.GetJoinDeadlineMs(),
		ReadyAtMS:       match.GetReadyAtMs(),
		CreatedAtMS:     match.GetCreatedAtMs(),
		UpdatedAtMS:     match.GetUpdatedAtMs(),
		SettledAtMS:     match.GetSettledAtMs(),
		CanceledAtMS:    match.GetCanceledAtMs(),
		Phase:           match.GetPhase(),
		PhaseDeadlineMS: match.GetPhaseDeadlineMs(),
		FeeBPS:          match.GetFeeBps(),
		PoolBPS:         match.GetPoolBps(),
		MatchMode:       match.GetMatchMode(),
		ForcedResult:    match.GetForcedResult(),
		PoolDeltaCoin:   match.GetPoolDeltaCoin(),
	}
}

func diceParticipantItemDataFromProto(participant *gamev1.DiceParticipant) diceParticipantItemData {
	if participant == nil {
		return diceParticipantItemData{}
	}
	return diceParticipantItemData{
		UserID:          int64String(participant.GetUserId()),
		UserIDNumber:    participant.GetUserId(),
		SeatNo:          participant.GetSeatNo(),
		Status:          participant.GetStatus(),
		StakeCoin:       participant.GetStakeCoin(),
		DicePoints:      participant.GetDicePoints(),
		RPSGesture:      participant.GetRpsGesture(),
		Gesture:         participant.GetRpsGesture(),
		Result:          participant.GetResult(),
		PayoutCoin:      participant.GetPayoutCoin(),
		BalanceAfter:    participant.GetBalanceAfter(),
		JoinedAtMS:      participant.GetJoinedAtMs(),
		UpdatedAtMS:     participant.GetUpdatedAtMs(),
		ParticipantType: participant.GetParticipantType(),
		IsRobot:         participant.GetIsRobot(),
		AvatarFrame:     map[string]any{},
	}
}

func gameBridgeScriptDataFromProto(resp *gamev1.GetBridgeScriptResponse) gameBridgeScriptData {
	if resp == nil {
		return gameBridgeScriptData{}
	}
	config := resp.GetConfig()
	return gameBridgeScriptData{
		BridgeScriptURL:       config.GetBridgeScriptUrl(),
		BridgeScriptURLV2:     config.GetBridgeScriptUrl(),
		BridgeScriptVersion:   config.GetBridgeScriptVersion(),
		BridgeScriptVersionV2: config.GetBridgeScriptVersion(),
		BridgeScriptSHA256:    config.GetBridgeScriptSha256(),
		BridgeScriptSHA256V2:  config.GetBridgeScriptSha256(),
		ServerTimeMS:          resp.GetServerTimeMs(),
	}
}

func int64String(value int64) string {
	return fmt.Sprintf("%d", value)
}

func roomRPSStakeGiftIDFromProto(gift *gamev1.RoomRPSStakeGift) string {
	if gift == nil {
		return ""
	}
	if text := strings.TrimSpace(gift.GetGiftIdText()); text != "" {
		return text
	}
	if gift.GetGiftId() > 0 {
		return int64String(gift.GetGiftId())
	}
	return ""
}

func roomRPSChallengeStakeGiftIDFromProto(challenge *gamev1.RoomRPSChallenge) string {
	if challenge == nil {
		return ""
	}
	if text := strings.TrimSpace(challenge.GetStakeGiftIdText()); text != "" {
		return text
	}
	if challenge.GetStakeGiftId() > 0 {
		return int64String(challenge.GetStakeGiftId())
	}
	return ""
}

func roomRPSChallengeDataFromProto(resp *gamev1.RoomRPSChallengeResponse) roomRPSChallengeData {
	if resp == nil {
		return roomRPSChallengeData{}
	}
	// 单挑战响应和列表响应使用同一个 challenge 结构，避免 Flutter 为发起、应战、详情维护多套字段映射。
	return roomRPSChallengeData{Challenge: roomRPSChallengeItemDataFromProto(resp.GetChallenge()), ServerTimeMS: resp.GetServerTimeMs()}
}

func roomRPSChallengeListDataFromProto(resp *gamev1.ListRoomRPSChallengesResponse) roomRPSChallengeListData {
	if resp == nil {
		return roomRPSChallengeListData{}
	}
	items := make([]*roomRPSChallengeItemData, 0, len(resp.GetChallenges()))
	for _, challenge := range resp.GetChallenges() {
		// 使用指针切片是为了后续展示资料补全可以原地写入 initiator/challenger.user，不再复制整份挑战单。
		item := roomRPSChallengeItemDataFromProto(challenge)
		items = append(items, &item)
	}
	return roomRPSChallengeListData{
		Challenges:   items,
		NextCursor:   resp.GetNextCursor(),
		PageSize:     resp.GetPageSize(),
		ServerTimeMS: resp.GetServerTimeMs(),
	}
}

func roomRPSChallengeItemDataFromProto(challenge *gamev1.RoomRPSChallenge) roomRPSChallengeItemData {
	if challenge == nil {
		return roomRPSChallengeItemData{}
	}
	// game-service 返回的是订单事实，gateway 这里只做 JSON 形态转换；状态、胜负、结算失败原因不能在这里二次推导。
	// 所有可能超过 JS 安全整数范围的 ID 都保留 string 和 number 双字段，Flutter 优先使用 string，后台排查仍可读 number。
	return roomRPSChallengeItemData{
		AppCode:            challenge.GetAppCode(),
		ChallengeID:        challenge.GetChallengeId(),
		RoomID:             challenge.GetRoomId(),
		RegionID:           challenge.GetRegionId(),
		Status:             challenge.GetStatus(),
		StakeGiftID:        roomRPSChallengeStakeGiftIDFromProto(challenge),
		StakeGiftIDNumber:  challenge.GetStakeGiftId(),
		StakeCoin:          challenge.GetStakeCoin(),
		Initiator:          roomRPSPlayerItemDataFromProto(challenge.GetInitiator()),
		Challenger:         roomRPSPlayerItemDataFromProto(challenge.GetChallenger()),
		WinnerUserID:       int64String(challenge.GetWinnerUserId()),
		WinnerUserIDNumber: challenge.GetWinnerUserId(),
		SettlementStatus:   challenge.GetSettlementStatus(),
		FailureReason:      challenge.GetFailureReason(),
		TimeoutAtMS:        challenge.GetTimeoutAtMs(),
		RevealAtMS:         challenge.GetRevealAtMs(),
		CreatedAtMS:        challenge.GetCreatedAtMs(),
		MatchedAtMS:        challenge.GetMatchedAtMs(),
		SettledAtMS:        challenge.GetSettledAtMs(),
		UpdatedAtMS:        challenge.GetUpdatedAtMs(),
	}
}

func roomRPSPlayerItemDataFromProto(player *gamev1.RoomRPSPlayer) roomRPSPlayerItemData {
	if player == nil {
		return roomRPSPlayerItemData{}
	}
	// user 先放 user_id 兜底；真正的 nickname/avatar 在 enrichRoomRPSChallenges 里批量补齐，资料失败不影响订单返回。
	return roomRPSPlayerItemData{
		UserID:       int64String(player.GetUserId()),
		UserIDNumber: player.GetUserId(),
		Gesture:      player.GetGesture(),
		Result:       player.GetResult(),
		BalanceAfter: player.GetBalanceAfter(),
		JoinedAtMS:   player.GetJoinedAtMs(),
		User:         diceUserData{UserID: int64String(player.GetUserId())},
	}
}
