package roomturnoverreward

import (
	"context"
	"database/sql"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/activityclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	activityv1 "hyapp.local/api/proto/activity/v1"

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

type Handler struct {
	activity activityclient.Client
	userDB   *sql.DB
	audit    shared.OperationLogger
}

func New(activity activityclient.Client, userDB *sql.DB, audit shared.OperationLogger) *Handler {
	return &Handler{activity: activity, userDB: userDB, audit: audit}
}

type configRequest struct {
	RegionID int64     `json:"region_id"`
	Enabled  bool      `json:"enabled"`
	Tiers    []tierDTO `json:"tiers"`
}

type configDTO struct {
	AppCode          string    `json:"app_code"`
	RegionID         int64     `json:"region_id"`
	Enabled          bool      `json:"enabled"`
	Tiers            []tierDTO `json:"tiers"`
	UpdatedByAdminID int64     `json:"updated_by_admin_id"`
	CreatedAtMS      int64     `json:"created_at_ms"`
	UpdatedAtMS      int64     `json:"updated_at_ms"`
}

type tierDTO struct {
	TierID             int64  `json:"tier_id"`
	TierCode           string `json:"tier_code"`
	TierName           string `json:"tier_name"`
	ThresholdCoinSpent int64  `json:"threshold_coin_spent"`
	RewardCoinAmount   int64  `json:"reward_coin_amount"`
	Status             string `json:"status"`
	SortOrder          int32  `json:"sort_order"`
	CreatedAtMS        int64  `json:"created_at_ms"`
	UpdatedAtMS        int64  `json:"updated_at_ms"`
}

type settlementDTO struct {
	SettlementID        string   `json:"settlement_id"`
	RegionID            int64    `json:"region_id"`
	RoomID              string   `json:"room_id"`
	OwnerUserID         int64    `json:"owner_user_id,string"`
	OwnerUser           *userDTO `json:"owner_user,omitempty"`
	PeriodStartMS       int64    `json:"period_start_ms"`
	PeriodEndMS         int64    `json:"period_end_ms"`
	CoinSpent           int64    `json:"coin_spent"`
	TierID              int64    `json:"tier_id"`
	TierCode            string   `json:"tier_code"`
	ThresholdCoinSpent  int64    `json:"threshold_coin_spent"`
	RewardCoinAmount    int64    `json:"reward_coin_amount"`
	Status              string   `json:"status"`
	WalletCommandID     string   `json:"wallet_command_id"`
	WalletTransactionID string   `json:"wallet_transaction_id"`
	FailureReason       string   `json:"failure_reason"`
	SettledAtMS         int64    `json:"settled_at_ms"`
	CreatedAtMS         int64    `json:"created_at_ms"`
	UpdatedAtMS         int64    `json:"updated_at_ms"`
}

type userDTO struct {
	UserID        int64  `json:"user_id,string"`
	DisplayUserID string `json:"display_user_id"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar"`
}

func (h *Handler) GetConfig(c *gin.Context) {
	regionID := queryInt64(c, "region_id", "regionId")
	// 后台服务只透传到 activity-service 获取当前 App 配置；活动规则 owner 不在 admin-server 内部复制。
	resp, err := h.activity.GetRoomTurnoverRewardConfig(c.Request.Context(), &activityv1.GetRoomTurnoverRewardConfigRequest{Meta: h.meta(c), RegionId: regionID})
	if err != nil {
		response.ServerError(c, "获取房间流水奖励配置失败")
		return
	}
	response.OK(c, configFromProto(resp.GetConfig()))
}

func (h *Handler) UpdateConfig(c *gin.Context) {
	var req configRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "房间流水奖励配置参数不正确")
		return
	}
	if req.RegionID < 0 {
		response.BadRequest(c, "区域参数不正确")
		return
	}
	tiers := make([]*activityv1.RoomTurnoverRewardTier, 0, len(req.Tiers))
	for _, tier := range req.Tiers {
		// 后台只做输入清洗和操作者透传；阈值递增、奖励金额和启用状态等业务校验统一由 activity-service 执行。
		tiers = append(tiers, &activityv1.RoomTurnoverRewardTier{
			TierId:             tier.TierID,
			TierCode:           strings.TrimSpace(tier.TierCode),
			TierName:           strings.TrimSpace(tier.TierName),
			ThresholdCoinSpent: tier.ThresholdCoinSpent,
			RewardCoinAmount:   tier.RewardCoinAmount,
			Status:             strings.TrimSpace(tier.Status),
			SortOrder:          tier.SortOrder,
		})
	}
	resp, err := h.activity.UpdateRoomTurnoverRewardConfig(c.Request.Context(), &activityv1.UpdateRoomTurnoverRewardConfigRequest{
		Meta:            h.meta(c),
		RegionId:        req.RegionID,
		Enabled:         req.Enabled,
		Tiers:           tiers,
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := configFromProto(resp.GetConfig())
	// 配置修改必须留下后台审计资源 ID，后续排查结算金额变化时可以回到具体 App 配置记录。
	shared.OperationLogWithResourceID(c, h.audit, "update-room-turnover-reward", "room_turnover_reward_configs", item.AppCode+":"+strconv.FormatInt(item.RegionID, 10), "success", "")
	response.OK(c, item)
}

func (h *Handler) ListSettlements(c *gin.Context) {
	// 列表筛选沿用通用 ListOptions：status 过滤结算状态，keyword 用作 room_id 精确查询，避免后台接口引入模糊扫表。
	options := shared.ListOptions(c)
	ownerKeyword := strings.TrimSpace(firstQuery(c, "owner_user_keyword", "ownerUserKeyword"))
	ownerFilter := shared.UserIdentityFilterFromQuery(c, "owner")
	ownerUserID, ownerMatched, ownerOK := h.resolveOwnerUserID(c.Request.Context(), ownerKeyword, ownerFilter)
	if !ownerOK {
		response.ServerError(c, "查询房主信息失败")
		return
	}
	if !ownerMatched {
		ownerUserID = queryInt64(c, "owner_user_id", "ownerUserId")
	}
	if !ownerMatched && (ownerKeyword != "" || !ownerFilter.IsEmpty()) {
		response.OK(c, response.Page{Items: []settlementDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
		return
	}
	resp, err := h.activity.ListRoomTurnoverRewardSettlements(c.Request.Context(), &activityv1.ListRoomTurnoverRewardSettlementsRequest{
		Meta:        h.meta(c),
		RegionId:    queryInt64(c, "region_id", "regionId"),
		Status:      strings.TrimSpace(options.Status),
		RoomId:      strings.TrimSpace(options.Keyword),
		OwnerUserId: ownerUserID,
		Page:        int32(options.Page),
		PageSize:    int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取房间流水奖励结算记录失败")
		return
	}
	items := make([]settlementDTO, 0, len(resp.GetSettlements()))
	for _, settlement := range resp.GetSettlements() {
		items = append(items, settlementFromProto(settlement))
	}
	// activity-service 返回的是结算事实快照；房主昵称、头像和短号只服务后台展示，所以在 admin-server 按当前页 owner_user_id 批量补齐。
	if err := h.fillOwnerUsers(c.Request.Context(), items); err != nil {
		response.ServerError(c, "补全房主信息失败")
		return
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

func (h *Handler) RetrySettlement(c *gin.Context) {
	settlementID := strings.TrimSpace(c.Param("settlement_id"))
	if settlementID == "" {
		response.BadRequest(c, "结算记录 ID 不正确")
		return
	}
	// retry 只把指定 settlement 交还 activity-service；是否重新取房主、是否调用钱包和是否幂等都由活动服务闭环处理。
	resp, err := h.activity.RetryRoomTurnoverRewardSettlement(c.Request.Context(), &activityv1.RetryRoomTurnoverRewardSettlementRequest{
		Meta:            h.meta(c),
		SettlementId:    settlementID,
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := settlementFromProto(resp.GetSettlement())
	shared.OperationLogWithResourceID(c, h.audit, "retry-room-turnover-reward", "room_turnover_reward_settlements", settlementID, "success", "")
	response.OK(c, item)
}

func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		// 使用后台请求 ID 串起 admin-server、activity-service 和 wallet-service 日志，AppCode 来自当前后台上下文。
		RequestId: middleware.CurrentRequestID(c),
		Caller:    "admin-server",
		AppCode:   appctx.FromContext(c.Request.Context()),
		SentAtMs:  time.Now().UnixMilli(),
	}
}

func (h *Handler) resolveOwnerUserID(ctx context.Context, keyword string, filter shared.UserIdentityFilter) (int64, bool, bool) {
	if filter.IsEmpty() && keyword == "" {
		return 0, false, true
	}
	if !filter.IsEmpty() {
		userID, matched, err := shared.ResolveUserIdentityFilter(ctx, h.userDB, appctx.FromContext(ctx), filter, time.Now().UnixMilli())
		return userID, matched, err == nil
	}
	if h.userDB == nil {
		// userDB 缺失时只允许正整数长 ID 精确筛选，避免把短号或昵称误当成 settlement.owner_user_id 查询。
		userID, matched, err := shared.ResolveExactUserIDOrNumericFallback(ctx, nil, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
		return userID, matched, err == nil
	}
	// 表头筛选先把运营输入解析成唯一房主 user_id，再交给 activity-service 的 owner_user_id 条件，保证分页 total 和列表数据同源。
	userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
	if err != nil {
		return 0, false, false
	}
	return userID, matched, true
}

func (h *Handler) fillOwnerUsers(ctx context.Context, settlements []settlementDTO) error {
	if len(settlements) == 0 {
		return nil
	}
	ids := collectOwnerUserIDs(settlements)
	if len(ids) == 0 {
		return nil
	}
	users, err := h.loadOwnerUsers(ctx, ids)
	if err != nil {
		return err
	}
	for index := range settlements {
		if user, ok := users[settlements[index].OwnerUserID]; ok {
			settlements[index].OwnerUser = &user
			continue
		}
		// 历史结算即使用户资料缺失，也必须保留收款人长 ID，方便后台继续追踪发奖对象。
		settlements[index].OwnerUser = &userDTO{UserID: settlements[index].OwnerUserID}
	}
	return nil
}

func (h *Handler) loadOwnerUsers(ctx context.Context, ids []int64) (map[int64]userDTO, error) {
	if h.userDB == nil || len(ids) == 0 {
		return map[int64]userDTO{}, nil
	}
	args := append([]any{appctx.FromContext(ctx)}, int64Args(ids)...)
	rows, err := h.userDB.QueryContext(ctx, `
		SELECT user_id, current_display_user_id, COALESCE(username, ''), COALESCE(avatar, '')
		FROM users
		WHERE app_code = ? AND user_id IN (`+placeholders(len(ids))+`)`,
		args...,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	users := make(map[int64]userDTO, len(ids))
	for rows.Next() {
		var user userDTO
		if err := rows.Scan(&user.UserID, &user.DisplayUserID, &user.Username, &user.Avatar); err != nil {
			return nil, err
		}
		users[user.UserID] = user
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return users, nil
}

func configFromProto(config *activityv1.RoomTurnoverRewardConfig) configDTO {
	if config == nil {
		return configDTO{Tiers: []tierDTO{}}
	}
	tiers := make([]tierDTO, 0, len(config.GetTiers()))
	for _, tier := range config.GetTiers() {
		tiers = append(tiers, tierFromProto(tier))
	}
	return configDTO{
		AppCode:          config.GetAppCode(),
		RegionID:         config.GetRegionId(),
		Enabled:          config.GetEnabled(),
		Tiers:            tiers,
		UpdatedByAdminID: config.GetUpdatedByAdminId(),
		CreatedAtMS:      config.GetCreatedAtMs(),
		UpdatedAtMS:      config.GetUpdatedAtMs(),
	}
}

func collectOwnerUserIDs(settlements []settlementDTO) []int64 {
	seen := make(map[int64]struct{}, len(settlements))
	ids := make([]int64, 0, len(settlements))
	for _, settlement := range settlements {
		if settlement.OwnerUserID <= 0 {
			continue
		}
		if _, ok := seen[settlement.OwnerUserID]; ok {
			continue
		}
		seen[settlement.OwnerUserID] = struct{}{}
		ids = append(ids, settlement.OwnerUserID)
	}
	return ids
}

func int64Args(ids []int64) []any {
	args := make([]any, 0, len(ids))
	for _, id := range ids {
		args = append(args, id)
	}
	return args
}

func placeholders(count int) string {
	if count <= 0 {
		return ""
	}
	parts := make([]string, count)
	for i := range parts {
		parts[i] = "?"
	}
	return strings.Join(parts, ",")
}

func queryInt64(c *gin.Context, keys ...string) int64 {
	value := strings.TrimSpace(firstQuery(c, keys...))
	if value == "" {
		return 0
	}
	parsed, err := strconv.ParseInt(value, 10, 64)
	if err != nil || parsed < 0 {
		return 0
	}
	return parsed
}

func firstQuery(c *gin.Context, keys ...string) string {
	for _, key := range keys {
		if value := c.Query(key); value != "" {
			return value
		}
	}
	return ""
}

func tierFromProto(tier *activityv1.RoomTurnoverRewardTier) tierDTO {
	if tier == nil {
		return tierDTO{}
	}
	return tierDTO{
		TierID:             tier.GetTierId(),
		TierCode:           tier.GetTierCode(),
		TierName:           tier.GetTierName(),
		ThresholdCoinSpent: tier.GetThresholdCoinSpent(),
		RewardCoinAmount:   tier.GetRewardCoinAmount(),
		Status:             tier.GetStatus(),
		SortOrder:          tier.GetSortOrder(),
		CreatedAtMS:        tier.GetCreatedAtMs(),
		UpdatedAtMS:        tier.GetUpdatedAtMs(),
	}
}

func settlementFromProto(settlement *activityv1.RoomTurnoverRewardSettlement) settlementDTO {
	if settlement == nil {
		return settlementDTO{}
	}
	return settlementDTO{
		SettlementID:        settlement.GetSettlementId(),
		RegionID:            settlement.GetRegionId(),
		RoomID:              settlement.GetRoomId(),
		OwnerUserID:         settlement.GetOwnerUserId(),
		PeriodStartMS:       settlement.GetPeriodStartMs(),
		PeriodEndMS:         settlement.GetPeriodEndMs(),
		CoinSpent:           settlement.GetCoinSpent(),
		TierID:              settlement.GetTierId(),
		TierCode:            settlement.GetTierCode(),
		ThresholdCoinSpent:  settlement.GetThresholdCoinSpent(),
		RewardCoinAmount:    settlement.GetRewardCoinAmount(),
		Status:              settlement.GetStatus(),
		WalletCommandID:     settlement.GetWalletCommandId(),
		WalletTransactionID: settlement.GetWalletTransactionId(),
		FailureReason:       settlement.GetFailureReason(),
		SettledAtMS:         settlement.GetSettledAtMs(),
		CreatedAtMS:         settlement.GetCreatedAtMs(),
		UpdatedAtMS:         settlement.GetUpdatedAtMs(),
	}
}
