package gamemanagement

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/middleware"
	gamev1 "hyapp.local/api/proto/game/v1"

	"github.com/gin-gonic/gin"
)

type platformRequest struct {
	PlatformCode string `json:"platformCode"`
	PlatformName string `json:"platformName"`
	Status       string `json:"status"`
	APIBaseURL   string `json:"apiBaseUrl"`
	// AdapterType 只允许服务端白名单中的值，避免后台误填导致回调走错协议。
	AdapterType string `json:"adapterType"`
	// CallbackSecret 只有提交非空时才覆盖旧密钥，编辑其它字段时前端传空即可。
	CallbackSecret      string   `json:"callbackSecret"`
	CallbackIPWhitelist []string `json:"callbackIpWhitelist"`
	// AdapterConfigJSON 是厂商扩展配置，保持 JSON 字符串透传给 game-service。
	AdapterConfigJSON string `json:"adapterConfigJson"`
	SortOrder         int32  `json:"sortOrder"`
}

type catalogRequest struct {
	GameID         string   `json:"gameId"`
	PlatformCode   string   `json:"platformCode"`
	ProviderGameID string   `json:"providerGameId"`
	GameName       string   `json:"gameName"`
	Category       string   `json:"category"`
	IconURL        string   `json:"iconUrl"`
	CoverURL       string   `json:"coverUrl"`
	LaunchMode     string   `json:"launchMode"`
	Orientation    string   `json:"orientation"`
	SafeHeight     int32    `json:"safeHeight"`
	MinCoin        int64    `json:"minCoin"`
	Status         string   `json:"status"`
	SortOrder      int32    `json:"sortOrder"`
	Tags           []string `json:"tags"`
}

type statusRequest struct {
	Status string `json:"status"`
}

type diceConfigRequest struct {
	Status           string         `json:"status"`
	StakeOptions     []diceStakeReq `json:"stakeOptions"`
	FeeBPS           int32          `json:"feeBps"`
	PoolBPS          int32          `json:"poolBps"`
	MinPlayers       int32          `json:"minPlayers"`
	MaxPlayers       int32          `json:"maxPlayers"`
	RobotEnabled     bool           `json:"robotEnabled"`
	RobotMatchWaitMS int64          `json:"robotMatchWaitMs"`
}

type diceStakeReq struct {
	StakeCoin int64 `json:"stakeCoin"`
	Enabled   bool  `json:"enabled"`
	SortOrder int32 `json:"sortOrder"`
}

type dicePoolAdjustRequest struct {
	AmountCoin int64  `json:"amountCoin"`
	Direction  string `json:"direction"`
	Reason     string `json:"reason"`
}

type selfGameStakePoolRequest struct {
	TargetBalanceCoin  int64  `json:"targetBalanceCoin"`
	SafeBalanceCoin    int64  `json:"safeBalanceCoin"`
	WarningBalanceCoin int64  `json:"warningBalanceCoin"`
	DangerBalanceCoin  int64  `json:"dangerBalanceCoin"`
	Status             string `json:"status"`
}

type selfGameNewUserPolicyRequest struct {
	Enabled                         bool   `json:"enabled"`
	ProtectionRounds                int32  `json:"protectionRounds"`
	ProtectionHours                 int32  `json:"protectionHours"`
	MaxStakeCoin                    int64  `json:"maxStakeCoin"`
	LifetimeSubsidyQuotaCoin        int64  `json:"lifetimeSubsidyQuotaCoin"`
	Day1SubsidyQuotaCoin            int64  `json:"day1SubsidyQuotaCoin"`
	SingleRoundSubsidyCapCoin       int64  `json:"singleRoundSubsidyCapCoin"`
	StrategyLevel                   string `json:"strategyLevel"`
	LoseStreakProtectionEnabled     bool   `json:"loseStreakProtectionEnabled"`
	LoseStreakTrigger               int32  `json:"loseStreakTrigger"`
	NormalPhaseTargetWinRatePercent int32  `json:"normalPhaseTargetWinRatePercent"`
	BlackPoolRobotForceWinEnabled   bool   `json:"blackPoolRobotForceWinEnabled"`
}

type diceGenerateRobotsRequest struct {
	Count            int32    `json:"count"`
	NicknamePrefix   string   `json:"nicknamePrefix"`
	NicknameLanguage string   `json:"nicknameLanguage"`
	AvatarURLs       []string `json:"avatarUrls"`
	Country          string   `json:"country"`
	Gender           string   `json:"gender"`
}

type diceRobotStatusRequest struct {
	Status string `json:"status"`
}

type roomRPSConfigRequest struct {
	Status             string                `json:"status"`
	ChallengeTimeoutMS int64                 `json:"challengeTimeoutMs"`
	RevealCountdownMS  int64                 `json:"revealCountdownMs"`
	StakeGifts         []roomRPSStakeGiftReq `json:"stakeGifts"`
}

type roomRPSStakeGiftReq struct {
	GiftID    string `json:"giftId"`
	Enabled   bool   `json:"enabled"`
	SortOrder int32  `json:"sortOrder"`
}

func (r platformRequest) toProto(platformCode string) *gamev1.GamePlatform {
	if platformCode == "" {
		platformCode = r.PlatformCode
	}
	return &gamev1.GamePlatform{
		PlatformCode:        strings.TrimSpace(platformCode),
		PlatformName:        strings.TrimSpace(r.PlatformName),
		Status:              strings.TrimSpace(r.Status),
		ApiBaseUrl:          strings.TrimSpace(r.APIBaseURL),
		AdapterType:         strings.TrimSpace(r.AdapterType),
		CallbackSecret:      strings.TrimSpace(r.CallbackSecret),
		CallbackIpWhitelist: compactTags(r.CallbackIPWhitelist),
		AdapterConfigJson:   strings.TrimSpace(r.AdapterConfigJSON),
		SortOrder:           r.SortOrder,
	}
}

func (r catalogRequest) toProto(gameID string) *gamev1.GameCatalogItem {
	if gameID == "" {
		gameID = r.GameID
	}
	return &gamev1.GameCatalogItem{
		GameId:         strings.TrimSpace(gameID),
		PlatformCode:   strings.TrimSpace(r.PlatformCode),
		ProviderGameId: strings.TrimSpace(r.ProviderGameID),
		GameName:       strings.TrimSpace(r.GameName),
		Category:       strings.TrimSpace(r.Category),
		IconUrl:        strings.TrimSpace(r.IconURL),
		CoverUrl:       strings.TrimSpace(r.CoverURL),
		LaunchMode:     strings.TrimSpace(r.LaunchMode),
		Orientation:    strings.TrimSpace(r.Orientation),
		SafeHeight:     r.SafeHeight,
		MinCoin:        r.MinCoin,
		Status:         strings.TrimSpace(r.Status),
		SortOrder:      r.SortOrder,
		Tags:           compactTags(r.Tags),
	}
}

func (r diceConfigRequest) toProto(gameID string) *gamev1.DiceConfig {
	options := make([]*gamev1.DiceStakeOption, 0, len(r.StakeOptions))
	for _, option := range r.StakeOptions {
		options = append(options, &gamev1.DiceStakeOption{
			StakeCoin: option.StakeCoin,
			Enabled:   option.Enabled,
			SortOrder: option.SortOrder,
		})
	}
	return &gamev1.DiceConfig{
		GameId:           strings.TrimSpace(gameID),
		Status:           strings.TrimSpace(r.Status),
		StakeOptions:     options,
		FeeBps:           r.FeeBPS,
		PoolBps:          r.PoolBPS,
		MinPlayers:       r.MinPlayers,
		MaxPlayers:       r.MaxPlayers,
		RobotEnabled:     r.RobotEnabled,
		RobotMatchWaitMs: r.RobotMatchWaitMS,
	}
}

func (r selfGameNewUserPolicyRequest) toProto(gameID string) *gamev1.SelfGameNewUserPolicy {
	return &gamev1.SelfGameNewUserPolicy{
		GameId:                          strings.TrimSpace(gameID),
		Enabled:                         r.Enabled,
		ProtectionRounds:                r.ProtectionRounds,
		ProtectionHours:                 r.ProtectionHours,
		MaxStakeCoin:                    r.MaxStakeCoin,
		LifetimeSubsidyQuotaCoin:        r.LifetimeSubsidyQuotaCoin,
		Day1SubsidyQuotaCoin:            r.Day1SubsidyQuotaCoin,
		SingleRoundSubsidyCapCoin:       r.SingleRoundSubsidyCapCoin,
		StrategyLevel:                   strings.TrimSpace(r.StrategyLevel),
		LoseStreakProtectionEnabled:     r.LoseStreakProtectionEnabled,
		LoseStreakTrigger:               r.LoseStreakTrigger,
		NormalPhaseTargetWinRatePercent: r.NormalPhaseTargetWinRatePercent,
		BlackPoolRobotForceWinEnabled:   r.BlackPoolRobotForceWinEnabled,
	}
}

func (r selfGameStakePoolRequest) toProto(gameID string, stakeCoin int64) *gamev1.SelfGameStakePool {
	return &gamev1.SelfGameStakePool{
		GameId:             strings.TrimSpace(gameID),
		StakeCoin:          stakeCoin,
		TargetBalanceCoin:  r.TargetBalanceCoin,
		SafeBalanceCoin:    r.SafeBalanceCoin,
		WarningBalanceCoin: r.WarningBalanceCoin,
		DangerBalanceCoin:  r.DangerBalanceCoin,
		Status:             strings.TrimSpace(r.Status),
	}
}

func (r roomRPSConfigRequest) toProto() (*gamev1.RoomRPSConfig, error) {
	status := strings.TrimSpace(r.Status)
	if status == "" {
		// 业务功能配置默认打开；后台没传 status 时不能默认为关闭，否则新环境上线后 App 入口会被静默隐藏。
		status = "active"
	}
	timeoutMS := r.ChallengeTimeoutMS
	if timeoutMS <= 0 {
		// 无人应战超时使用产品默认 10 分钟；0 或负数按“未填写”处理，不允许保存成立刻过期。
		timeoutMS = 10 * 60 * 1000
	}
	revealMS := r.RevealCountdownMS
	if revealMS <= 0 {
		// 揭晓倒计时使用产品默认 3 秒；客户端动画和 IM 倒计时都依赖这个稳定默认值。
		revealMS = 3 * 1000
	}
	if len(r.StakeGifts) != 4 {
		// 房内猜拳礼物档位是产品固定四档，少一档会导致 Flutter 面板缺位，多一档会导致后台和客户端展示不一致。
		return nil, fmt.Errorf("房内猜拳礼物档位必须配置 4 档")
	}
	gifts := make([]*gamev1.RoomRPSStakeGift, 0, len(r.StakeGifts))
	seen := make(map[string]struct{}, len(r.StakeGifts))
	for index, gift := range r.StakeGifts {
		giftID := strings.TrimSpace(gift.GiftID)
		if giftID == "" {
			// gift_id 是后台礼物表的真实主键，当前礼物体系允许字符串；空值不能表示“空档位”，空档位应该通过 enabled=false 表达。
			return nil, fmt.Errorf("第 %d 档礼物 ID 不正确", index+1)
		}
		if _, ok := seen[giftID]; ok {
			// 同一个礼物不能重复配置成多档，否则结算金额和排序展示会变成同一个 gift_id 的多份事实。
			return nil, fmt.Errorf("第 %d 档礼物 ID 重复", index+1)
		}
		seen[giftID] = struct{}{}
		sortOrder := gift.SortOrder
		if sortOrder <= 0 {
			// 未显式给排序时按提交顺序分配递减权重，保证大值优先后仍与前端从上到下的顺序一致。
			sortOrder = int32(len(r.StakeGifts) - index)
		}
		gifts = append(gifts, &gamev1.RoomRPSStakeGift{
			GiftId:     legacyRoomRPSGiftID(giftID),
			GiftIdText: giftID,
			Enabled:    gift.Enabled,
			SortOrder:  sortOrder,
		})
	}
	return &gamev1.RoomRPSConfig{
		// 房内猜拳和独立猜拳必须用不同 game_id；这里固定 room_rps，防止后台误把配置写到独立猜拳。
		GameId:             "room_rps",
		Status:             status,
		ChallengeTimeoutMs: timeoutMS,
		RevealCountdownMs:  revealMS,
		StakeGifts:         gifts,
	}, nil
}

func legacyRoomRPSGiftID(giftID string) int64 {
	value, err := strconv.ParseInt(strings.TrimSpace(giftID), 10, 64)
	if err != nil || value <= 0 {
		return 0
	}
	return value
}

func compactTags(tags []string) []string {
	out := make([]string, 0, len(tags))
	seen := make(map[string]struct{}, len(tags))
	for _, tag := range tags {
		value := strings.TrimSpace(tag)
		if value == "" {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	return out
}

func requestMeta(c *gin.Context) *gamev1.RequestMeta {
	return &gamev1.RequestMeta{
		RequestId:   middleware.CurrentRequestID(c),
		Caller:      "admin-server",
		ActorUserId: int64(middleware.CurrentUserID(c)),
		SentAtMs:    time.Now().UnixMilli(),
		AppCode:     appctx.FromContext(c.Request.Context()),
	}
}
