package gamemanagement

import (
	"context"
	"errors"
	"strconv"
	"strings"

	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/response"
	gamev1 "hyapp.local/api/proto/game/v1"

	"github.com/gin-gonic/gin"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const gameWhitelistInternalUserIDMinDigits = 15

var errGameWhitelistUserNotFound = errors.New("game whitelist user not found")

type gameWhitelistEnabledRequest struct {
	Enabled bool `json:"enabled"`
}

type gameWhitelistUserRequest struct {
	// 保留 userId JSON 字段兼容已发布管理端；值允许内部 user_id、默认短号或当前有效靓号。
	UserID string `json:"userId"`
}

// gameWhitelistUserResolver 把白名单解析限制在 user-service owner API，避免 admin-server 直查用户库复制身份口径。
type gameWhitelistUserResolver interface {
	GetUser(context.Context, userclient.GetUserRequest) (*userclient.User, error)
	ResolveDisplayUserID(context.Context, userclient.ResolveDisplayUserIDRequest) (*userclient.UserIdentity, error)
}

type gameWhitelistUserDTO struct {
	UserID           string `json:"userId"`
	DisplayUserID    string `json:"displayUserId"`
	Username         string `json:"username"`
	Avatar           string `json:"avatar"`
	Status           string `json:"status"`
	CreatedByAdminID int64  `json:"createdByAdminId"`
	CreatedAtMS      int64  `json:"createdAtMs"`
}

// SetGameWhitelistEnabled 只切换访问策略，不隐式增删成员；空白名单开启后应明确表现为无人可见。
func (h *Handler) SetGameWhitelistEnabled(c *gin.Context) {
	gameID := strings.TrimSpace(c.Param("game_id"))
	var req gameWhitelistEnabledRequest
	if gameID == "" || c.ShouldBindJSON(&req) != nil {
		response.BadRequest(c, "游戏白名单参数不正确")
		return
	}
	resp, err := h.game.SetGameWhitelistEnabled(c.Request.Context(), &gamev1.SetGameWhitelistEnabledRequest{
		Meta: requestMeta(c), GameId: gameID, Enabled: req.Enabled,
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	h.auditLog(c, "set-game-whitelist-enabled", "game_catalog", gameID, strconv.FormatBool(req.Enabled))
	response.OK(c, catalogFromProto(resp.GetGame()))
}

// ListGameWhitelistUsers 从 game-service 读取成员事实，再批量补 user-service 展示资料，避免复制用户主数据到游戏库。
func (h *Handler) ListGameWhitelistUsers(c *gin.Context) {
	gameID := strings.TrimSpace(c.Param("game_id"))
	if gameID == "" {
		response.BadRequest(c, "游戏 ID 参数不正确")
		return
	}
	resp, err := h.game.ListGameWhitelistUsers(c.Request.Context(), &gamev1.ListGameWhitelistUsersRequest{
		Meta: requestMeta(c), GameId: gameID, PageSize: int32(parsePositiveInt(c.Query("pageSize"), 200)),
	})
	if err != nil {
		response.ServerError(c, "获取游戏白名单失败")
		return
	}
	userIDs := make([]int64, 0, len(resp.GetUsers()))
	for _, item := range resp.GetUsers() {
		userIDs = append(userIDs, item.GetUserId())
	}
	profiles := map[int64]*userclient.User{}
	if len(userIDs) > 0 {
		profiles, err = h.user.BatchGetUsers(c.Request.Context(), userclient.BatchGetUsersRequest{
			RequestID: middleware.CurrentRequestID(c), Caller: "admin-server", UserIDs: userIDs,
		})
		if err != nil {
			response.ServerError(c, "获取白名单用户资料失败")
			return
		}
	}
	items := make([]gameWhitelistUserDTO, 0, len(resp.GetUsers()))
	for _, item := range resp.GetUsers() {
		items = append(items, gameWhitelistUserFromProto(item, profiles[item.GetUserId()]))
	}
	response.OK(c, gin.H{"items": items, "serverTimeMs": resp.GetServerTimeMs()})
}

// AddGameWhitelistUser 先把内部 ID、默认短号或当前有效靓号统一解析为 owner user_id，再写游戏访问成员表。
func (h *Handler) AddGameWhitelistUser(c *gin.Context) {
	gameID := strings.TrimSpace(c.Param("game_id"))
	var req gameWhitelistUserRequest
	if gameID == "" || c.ShouldBindJSON(&req) != nil {
		response.BadRequest(c, "游戏白名单用户参数不正确")
		return
	}
	keyword := strings.TrimSpace(req.UserID)
	if keyword == "" {
		response.BadRequest(c, "请输入用户 ID、短号或靓号")
		return
	}
	profile, targetKind, err := resolveGameWhitelistUser(c.Request.Context(), h.user, middleware.CurrentRequestID(c), keyword)
	if errors.Is(err, errGameWhitelistUserNotFound) {
		response.BadRequest(c, "未找到匹配的用户")
		return
	}
	if err != nil || profile == nil {
		response.ServerError(c, "匹配用户失败")
		return
	}
	resp, err := h.game.AddGameWhitelistUser(c.Request.Context(), &gamev1.AddGameWhitelistUserRequest{
		Meta: requestMeta(c), GameId: gameID, UserId: profile.UserID,
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	// 审计只记录最终内部 user_id 和解析类型，避免后续短号/靓号变更让历史记录失去指向。
	h.auditLog(c, "add-game-whitelist-user", "game_user_whitelist", gameID+":"+strconv.FormatInt(profile.UserID, 10), "added target_kind="+targetKind)
	response.OK(c, gameWhitelistUserFromProto(resp.GetUser(), profile))
}

// resolveGameWhitelistUser 延续后台现有身份优先级：短数字和字母靓号优先按展示号解析，雪花长整型优先按内部 ID 读取。
func resolveGameWhitelistUser(ctx context.Context, resolver gameWhitelistUserResolver, requestID string, keyword string) (*userclient.User, string, error) {
	keyword = strings.TrimSpace(keyword)
	if resolver == nil || keyword == "" {
		return nil, "", errGameWhitelistUserNotFound
	}
	numericID, numericErr := strconv.ParseInt(keyword, 10, 64)
	numericOK := numericErr == nil && numericID > 0
	userIDChecked := false
	// 生产内部 user_id 是雪花长整型；先查长数字可保持已发布内部 ID 输入语义，同时仍允许长数字靓号在未命中后回退解析。
	if numericOK && len(strings.TrimLeft(keyword, "0")) >= gameWhitelistInternalUserIDMinDigits {
		userIDChecked = true
		if profile, found, err := getGameWhitelistUser(ctx, resolver, requestID, numericID); err != nil {
			return nil, "", err
		} else if found {
			return profile, "user_id", nil
		}
	}
	identity, err := resolver.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{
		RequestID: requestID, Caller: "admin-server", DisplayUserID: keyword,
	})
	if err == nil && identity != nil && identity.UserID > 0 {
		profile, found, getErr := getGameWhitelistUser(ctx, resolver, requestID, identity.UserID)
		if getErr != nil {
			return nil, "", getErr
		}
		if found {
			return profile, "display_user_id", nil
		}
	} else if err != nil && status.Code(err) != codes.NotFound && status.Code(err) != codes.InvalidArgument {
		// user-service 不可用等系统错误不能伪装成用户不存在，否则运营重试时无法判断真实故障。
		return nil, "", err
	}
	if numericOK && !userIDChecked {
		if profile, found, err := getGameWhitelistUser(ctx, resolver, requestID, numericID); err != nil {
			return nil, "", err
		} else if found {
			return profile, "user_id", nil
		}
	}
	return nil, "", errGameWhitelistUserNotFound
}

func getGameWhitelistUser(ctx context.Context, resolver gameWhitelistUserResolver, requestID string, userID int64) (*userclient.User, bool, error) {
	profile, err := resolver.GetUser(ctx, userclient.GetUserRequest{RequestID: requestID, Caller: "admin-server", UserID: userID})
	if status.Code(err) == codes.NotFound || profile == nil && err == nil {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, err
	}
	return profile, true, nil
}

func (h *Handler) DeleteGameWhitelistUser(c *gin.Context) {
	gameID := strings.TrimSpace(c.Param("game_id"))
	userID, err := strconv.ParseInt(strings.TrimSpace(c.Param("user_id")), 10, 64)
	if gameID == "" || err != nil || userID <= 0 {
		response.BadRequest(c, "游戏白名单用户参数不正确")
		return
	}
	if _, err := h.game.DeleteGameWhitelistUser(c.Request.Context(), &gamev1.DeleteGameWhitelistUserRequest{
		Meta: requestMeta(c), GameId: gameID, UserId: userID,
	}); err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	h.auditLog(c, "delete-game-whitelist-user", "game_user_whitelist", gameID+":"+strconv.FormatInt(userID, 10), "deleted")
	response.OK(c, gin.H{"userId": strconv.FormatInt(userID, 10)})
}

func gameWhitelistUserFromProto(item *gamev1.GameWhitelistUser, profile *userclient.User) gameWhitelistUserDTO {
	if item == nil {
		return gameWhitelistUserDTO{}
	}
	dto := gameWhitelistUserDTO{
		UserID: strconv.FormatInt(item.GetUserId(), 10), CreatedByAdminID: item.GetCreatedByAdminId(), CreatedAtMS: item.GetCreatedAtMs(),
	}
	if profile != nil {
		dto.DisplayUserID = profile.DisplayUserID
		dto.Username = profile.Username
		dto.Avatar = profile.Avatar
		dto.Status = profile.Status
	}
	return dto
}
