package prettyid

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"math"
	"strconv"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"

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

const internalUserIDMinDigits = 15

var (
	errGrantTargetInvalid  = errors.New("请输入用户 ID 或短号")
	errGrantTargetNotFound = errors.New("用户不存在")
)

type grantTargetResolver interface {
	GetUser(ctx context.Context, req userclient.GetUserRequest) (*userclient.User, error)
	ResolveDisplayUserID(ctx context.Context, req userclient.ResolveDisplayUserIDRequest) (*userclient.UserIdentity, error)
}

type Handler struct {
	user  userclient.Client
	audit shared.OperationLogger
}

func New(user userclient.Client, audit shared.OperationLogger) *Handler {
	return &Handler{user: user, audit: audit}
}

type poolRequest struct {
	Name           string `json:"name"`
	LevelTrack     string `json:"level_track"`
	MinLevel       int32  `json:"min_level"`
	MaxLevel       int32  `json:"max_level"`
	RuleType       string `json:"rule_type"`
	RuleConfigJSON string `json:"rule_config_json"`
	Status         string `json:"status"`
	SortOrder      int32  `json:"sort_order"`
}

type generateRequest struct {
	RuleType       string `json:"rule_type"`
	RuleConfigJSON string `json:"rule_config_json"`
	Count          int32  `json:"count"`
}

type grantRequest struct {
	TargetUserID  any    `json:"target_user_id"`
	DisplayUserID string `json:"display_user_id"`
	DurationMs    int64  `json:"duration_ms"`
	Reason        string `json:"reason"`
}

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

type recycleRequest struct {
	Reason string `json:"reason"`
}

func (h *Handler) ListPools(c *gin.Context) {
	page, pageSize, ok := pageQuery(c)
	if !ok {
		response.BadRequest(c, "分页参数不正确")
		return
	}
	// admin-server 只解析 HTTP 查询参数和当前管理员身份，池状态、等级轨道合法性由 user-service 统一校验。
	items, total, err := h.user.ListPrettyDisplayIDPools(c.Request.Context(), userclient.ListPrettyDisplayIDPoolsRequest{
		RequestID:  middleware.CurrentRequestID(c),
		Caller:     "admin-server",
		Status:     strings.TrimSpace(c.Query("status")),
		LevelTrack: strings.TrimSpace(c.Query("level_track")),
		Page:       page,
		PageSize:   pageSize,
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	response.OK(c, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize})
}

func (h *Handler) CreatePool(c *gin.Context) {
	var req poolRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "靓号池参数不正确")
		return
	}
	// 创建池时操作者从后台登录态读取，不能信任请求体里的管理员 ID。
	pool, err := h.user.CreatePrettyDisplayIDPool(c.Request.Context(), userclient.PrettyDisplayIDPoolRequest{
		RequestID:       middleware.CurrentRequestID(c),
		Caller:          "admin-server",
		Name:            req.Name,
		LevelTrack:      req.LevelTrack,
		MinLevel:        req.MinLevel,
		MaxLevel:        req.MaxLevel,
		RuleType:        req.RuleType,
		RuleConfigJSON:  req.RuleConfigJSON,
		Status:          req.Status,
		SortOrder:       req.SortOrder,
		OperatorAdminID: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "create-pretty-id-pool", "pretty_display_id_pools", pool.PoolID, "success", "name="+pool.Name)
	response.Created(c, pool)
}

func (h *Handler) UpdatePool(c *gin.Context) {
	var req poolRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "靓号池参数不正确")
		return
	}
	// pool_id 来自路径，body 只承载可编辑字段，避免前端误传 ID 导致更新目标不一致。
	poolID := strings.TrimSpace(c.Param("pool_id"))
	pool, err := h.user.UpdatePrettyDisplayIDPool(c.Request.Context(), userclient.PrettyDisplayIDPoolRequest{
		RequestID:       middleware.CurrentRequestID(c),
		Caller:          "admin-server",
		PoolID:          poolID,
		Name:            req.Name,
		LevelTrack:      req.LevelTrack,
		MinLevel:        req.MinLevel,
		MaxLevel:        req.MaxLevel,
		RuleType:        req.RuleType,
		RuleConfigJSON:  req.RuleConfigJSON,
		Status:          req.Status,
		SortOrder:       req.SortOrder,
		OperatorAdminID: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "update-pretty-id-pool", "pretty_display_id_pools", pool.PoolID, "success", "status="+pool.Status)
	response.OK(c, pool)
}

func (h *Handler) Generate(c *gin.Context) {
	var req generateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "生成参数不正确")
		return
	}
	// 生成只从路径读取 pool_id，请求体里的规则和数量会继续交给 user-service 校验并写批次审计。
	poolID := strings.TrimSpace(c.Param("pool_id"))
	batch, err := h.user.GeneratePrettyDisplayIDs(c.Request.Context(), userclient.GeneratePrettyDisplayIDsRequest{
		RequestID:       middleware.CurrentRequestID(c),
		Caller:          "admin-server",
		PoolID:          poolID,
		RuleType:        req.RuleType,
		RuleConfigJSON:  req.RuleConfigJSON,
		Count:           req.Count,
		OperatorAdminID: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "generate-pretty-ids", "pretty_display_id_generation_batches", batch.BatchID, "success", "pool_id="+poolID)
	response.Created(c, batch)
}

func (h *Handler) ListIDs(c *gin.Context) {
	page, pageSize, ok := pageQuery(c)
	if !ok {
		response.BadRequest(c, "分页参数不正确")
		return
	}
	assignedUserID, ok := optionalInt64Query(c, "assigned_user_id")
	if !ok {
		response.BadRequest(c, "assigned_user_id 参数不正确")
		return
	}
	// 列表筛选保持轻量透传，空字符串表示不过滤，assigned_user_id 只在正整数时作为精确条件。
	statusFilter := strings.TrimSpace(c.Query("status"))
	if _, external := appctx.ExternalScopeFromContext(c.Request.Context()); external {
		// 外管入口只提供可发放号码，不能浏览其他区域用户当前占用的靓号。
		statusFilter = "available"
		assignedUserID = 0
	}
	items, total, err := h.user.ListPrettyDisplayIDs(c.Request.Context(), userclient.ListPrettyDisplayIDsRequest{
		RequestID:      middleware.CurrentRequestID(c),
		Caller:         "admin-server",
		PoolID:         strings.TrimSpace(c.Query("pool_id")),
		Source:         strings.TrimSpace(c.Query("source")),
		Status:         statusFilter,
		Keyword:        strings.TrimSpace(c.Query("keyword")),
		AssignedUserID: assignedUserID,
		Page:           page,
		PageSize:       pageSize,
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	if err := attachAssignedUsers(c.Request.Context(), h.user, middleware.CurrentRequestID(c), items); err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	response.OK(c, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize})
}

type assignedUserBatchGetter interface {
	BatchGetUsers(ctx context.Context, req userclient.BatchGetUsersRequest) (map[int64]*userclient.User, error)
}

func attachAssignedUsers(ctx context.Context, getter assignedUserBatchGetter, requestID string, items []*userclient.PrettyDisplayID) error {
	if getter == nil || len(items) == 0 {
		return nil
	}
	userIDs := make([]int64, 0, len(items))
	seen := make(map[int64]struct{}, len(items))
	for _, item := range items {
		if item == nil || item.AssignedUserID <= 0 {
			continue
		}
		if _, ok := seen[item.AssignedUserID]; ok {
			continue
		}
		// pretty_display_ids 用 0 表示未占用；这里只批量补真实正数 user_id，避免前端把哨兵值渲染成用户。
		seen[item.AssignedUserID] = struct{}{}
		userIDs = append(userIDs, item.AssignedUserID)
	}
	if len(userIDs) == 0 {
		return nil
	}
	users, err := getter.BatchGetUsers(ctx, userclient.BatchGetUsersRequest{
		RequestID: requestID,
		Caller:    "admin-server",
		UserIDs:   userIDs,
	})
	if err != nil {
		return err
	}
	for _, item := range items {
		if item == nil || item.AssignedUserID <= 0 {
			continue
		}
		// BatchGetUsers 对缺失用户不返回占位对象；保持 nil 让前端显示空态，不用内部 user_id 冒充昵称或短号。
		item.AssignedUser = users[item.AssignedUserID]
	}
	return nil
}

func (h *Handler) Grant(c *gin.Context) {
	var req grantRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "发放参数不正确")
		return
	}
	targetUserID, targetKind, err := resolveGrantTargetUserID(c.Request.Context(), h.user, middleware.CurrentRequestID(c), req.TargetUserID)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	if scope, external := appctx.ExternalScopeFromContext(c.Request.Context()); external && !prettyIDTargetAllowed(scope, targetUserID) {
		response.Forbidden(c, "目标用户不在当前团队或区域")
		return
	}
	// 后台发放最终仍只把内部 user_id 传给 user-service；短号/靓号只作为后台输入别名，避免运营把 6 位短号误当成内部 ID 导致 NotFound。
	result, err := h.user.AdminGrantPrettyDisplayID(c.Request.Context(), userclient.AdminGrantPrettyDisplayIDRequest{
		RequestID:       middleware.CurrentRequestID(c),
		Caller:          "admin-server",
		TargetUserID:    targetUserID,
		DisplayUserID:   req.DisplayUserID,
		DurationMs:      req.DurationMs,
		Reason:          req.Reason,
		OperatorAdminID: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "grant-pretty-id", "pretty_display_ids", result.PrettyID, "success", "target_user_id="+strconv.FormatInt(targetUserID, 10)+" target_kind="+targetKind)
	response.Created(c, result)
}

func prettyIDTargetAllowed(scope appctx.ExternalScope, targetUserID int64) bool {
	for _, userID := range scope.OwnerUserIDs {
		if scope.RegionID > 0 && userID == targetUserID {
			return true
		}
	}
	return false
}

func (h *Handler) SetStatus(c *gin.Context) {
	var req statusRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "状态参数不正确")
		return
	}
	// 状态修改以路径 pretty_id 为准，请求体只允许目标状态和原因，防止改错号码。
	prettyID := strings.TrimSpace(c.Param("pretty_id"))
	item, err := h.user.SetPrettyDisplayIDStatus(c.Request.Context(), userclient.SetPrettyDisplayIDStatusRequest{
		RequestID:       middleware.CurrentRequestID(c),
		Caller:          "admin-server",
		PrettyID:        prettyID,
		Status:          req.Status,
		Reason:          req.Reason,
		OperatorAdminID: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "set-pretty-id-status", "pretty_display_ids", prettyID, "success", "status="+item.Status)
	response.OK(c, item)
}

func (h *Handler) Recycle(c *gin.Context) {
	var req recycleRequest
	if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
		response.BadRequest(c, "回收参数不正确")
		return
	}
	// 回收只能以路径 pretty_id 定位，不能相信前端传用户 ID 或 lease ID；真实占用关系由 user-service 事务重新锁表校验。
	prettyID := strings.TrimSpace(c.Param("pretty_id"))
	item, err := h.user.RecyclePrettyDisplayID(c.Request.Context(), userclient.RecyclePrettyDisplayIDRequest{
		RequestID:       middleware.CurrentRequestID(c),
		Caller:          "admin-server",
		PrettyID:        prettyID,
		Reason:          strings.TrimSpace(req.Reason),
		OperatorAdminID: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(c, h.audit, "recycle-pretty-id", "pretty_display_ids", prettyID, "success", "status="+item.Status)
	response.OK(c, item)
}

func pageQuery(c *gin.Context) (int32, int32, bool) {
	// 分页参数在 admin-server 先收敛到 1..100，避免后台页面传错值造成 user-service 大分页查询。
	page := int32(1)
	pageSize := int32(20)
	if raw := strings.TrimSpace(c.Query("page")); raw != "" {
		value, err := strconv.ParseInt(raw, 10, 32)
		if err != nil || value <= 0 {
			return 0, 0, false
		}
		page = int32(value)
	}
	if raw := strings.TrimSpace(c.Query("page_size")); raw != "" {
		value, err := strconv.ParseInt(raw, 10, 32)
		if err != nil || value <= 0 {
			return 0, 0, false
		}
		pageSize = int32(value)
	}
	if pageSize > 100 {
		pageSize = 100
	}
	return page, pageSize, true
}

func optionalInt64Query(c *gin.Context, name string) (int64, bool) {
	// 空查询表示不筛选，非空必须是正整数，避免把 0 当成真实用户 ID 传给列表接口。
	raw := strings.TrimSpace(c.Query(name))
	if raw == "" {
		return 0, true
	}
	value, err := strconv.ParseInt(raw, 10, 64)
	return value, err == nil && value > 0
}

func resolveGrantTargetUserID(ctx context.Context, resolver grantTargetResolver, requestID string, raw any) (int64, string, error) {
	keyword, ok := grantTargetKeyword(raw)
	if !ok || resolver == nil {
		return 0, "", errGrantTargetInvalid
	}
	numericID, numericOK := parsePositiveInt64(keyword)
	// 内部 user_id 是雪花长整型，运营常用的短 ID 是 6 位左右；短输入优先走展示号解析，长输入优先保持旧的内部 ID 语义。
	userIDChecked := false
	if numericOK && len(strings.TrimLeft(keyword, "0")) >= internalUserIDMinDigits {
		userIDChecked = true
		if found, err := grantTargetByUserID(ctx, resolver, requestID, numericID); err != nil {
			return 0, "", err
		} else if found {
			return numericID, "user_id", nil
		}
	}
	if identity, err := resolver.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{
		RequestID:     requestID,
		Caller:        "admin-server",
		DisplayUserID: keyword,
	}); err != nil {
		if !isGRPCNotFound(err) {
			return 0, "", err
		}
	} else if identity != nil && identity.UserID > 0 {
		return identity.UserID, "display_user_id", nil
	}
	if numericOK && !userIDChecked {
		if found, err := grantTargetByUserID(ctx, resolver, requestID, numericID); err != nil {
			return 0, "", err
		} else if found {
			return numericID, "user_id", nil
		}
	}
	return 0, "", errGrantTargetNotFound
}

func grantTargetByUserID(ctx context.Context, resolver grantTargetResolver, requestID string, userID int64) (bool, error) {
	// GetUser 只用于确认内部 ID 存在；真实发放仍在 AdminGrantPrettyDisplayID 事务里完成，避免这里承担写语义。
	user, err := resolver.GetUser(ctx, userclient.GetUserRequest{
		RequestID: requestID,
		Caller:    "admin-server",
		UserID:    userID,
	})
	if err != nil {
		if isGRPCNotFound(err) {
			return false, nil
		}
		return false, err
	}
	return user != nil && user.UserID > 0, nil
}

func grantTargetKeyword(raw any) (string, bool) {
	// JSON 入口可能收到字符串、数字或测试里的 json.Number；统一转成精确文本后再决定按内部 ID 还是短号解析。
	switch value := raw.(type) {
	case float64:
		if value <= 0 || value != math.Trunc(value) || value > float64(math.MaxInt64) {
			return "", false
		}
		return strconv.FormatInt(int64(value), 10), true
	case json.Number:
		text := strings.TrimSpace(value.String())
		if _, ok := parsePositiveInt64(text); !ok {
			return "", false
		}
		return text, true
	case string:
		text := strings.TrimSpace(value)
		return text, text != ""
	default:
		return "", false
	}
}

func parsePositiveInt64(text string) (int64, bool) {
	parsed, err := strconv.ParseInt(strings.TrimSpace(text), 10, 64)
	return parsed, err == nil && parsed > 0
}

func isGRPCNotFound(err error) bool {
	return status.Code(err) == codes.NotFound
}
