package sevendaycheckin

import (
	"context"
	"database/sql"
	"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 {
	Enabled bool        `json:"enabled"`
	Rewards []rewardDTO `json:"rewards"`
}

type configDTO struct {
	AppCode          string      `json:"app_code"`
	Enabled          bool        `json:"enabled"`
	ConfigVersion    int64       `json:"config_version"`
	Rewards          []rewardDTO `json:"rewards"`
	UpdatedByAdminID int64       `json:"updated_by_admin_id"`
	CreatedAtMS      int64       `json:"created_at_ms"`
	UpdatedAtMS      int64       `json:"updated_at_ms"`
}

type rewardDTO struct {
	DayIndex        int32 `json:"day_index"`
	ResourceGroupID int64 `json:"resource_group_id"`
}

type claimDTO struct {
	ClaimID         string   `json:"claim_id"`
	CommandID       string   `json:"command_id"`
	UserID          int64    `json:"user_id,string"`
	User            *userDTO `json:"user,omitempty"`
	CycleNo         int64    `json:"cycle_no"`
	CheckinDay      string   `json:"checkin_day"`
	DayIndex        int32    `json:"day_index"`
	ConfigVersion   int64    `json:"config_version"`
	ResourceGroupID int64    `json:"resource_group_id"`
	WalletCommandID string   `json:"wallet_command_id"`
	WalletGrantID   string   `json:"wallet_grant_id"`
	Status          string   `json:"status"`
	FailureReason   string   `json:"failure_reason"`
	SignedAtMS      int64    `json:"signed_at_ms"`
	GrantedAtMS     int64    `json:"granted_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) GetSevenDayCheckInConfig(c *gin.Context) {
	resp, err := h.activity.GetSevenDayCheckInConfig(c.Request.Context(), &activityv1.GetSevenDayCheckInConfigRequest{Meta: h.meta(c)})
	if err != nil {
		response.ServerError(c, "获取七日签到配置失败")
		return
	}
	response.OK(c, configFromProto(resp.GetConfig()))
}

func (h *Handler) UpdateSevenDayCheckInConfig(c *gin.Context) {
	var req configRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "七日签到配置参数不正确")
		return
	}
	rewards := make([]*activityv1.SevenDayCheckInReward, 0, len(req.Rewards))
	for _, reward := range req.Rewards {
		rewards = append(rewards, &activityv1.SevenDayCheckInReward{
			DayIndex:        reward.DayIndex,
			ResourceGroupId: reward.ResourceGroupID,
		})
	}
	resp, err := h.activity.UpdateSevenDayCheckInConfig(c.Request.Context(), &activityv1.UpdateSevenDayCheckInConfigRequest{
		Meta:            h.meta(c),
		Enabled:         req.Enabled,
		Rewards:         rewards,
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := configFromProto(resp.GetConfig())
	shared.OperationLogWithResourceID(c, h.audit, "update-seven-day-checkin", "seven_day_checkin_configs", item.AppCode, "success", "")
	response.OK(c, item)
}

func (h *Handler) ListSevenDayCheckInClaims(c *gin.Context) {
	options := shared.ListOptions(c)
	userFilter := shared.UserIdentityFilterFromQuery(c, "")
	userID, matched, ok := h.resolveClaimUserID(c.Request.Context(), strings.TrimSpace(options.Keyword), userFilter)
	if !ok {
		response.ServerError(c, "查询用户信息失败")
		return
	}
	if (options.Keyword != "" || !userFilter.IsEmpty()) && !matched {
		response.OK(c, response.Page{Items: []claimDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
		return
	}
	dayIndex := parseOptionalInt(c.Query("day_index"))
	if dayIndex < 0 || dayIndex > 7 {
		response.BadRequest(c, "签到天数参数不正确")
		return
	}
	resp, err := h.activity.ListSevenDayCheckInClaims(c.Request.Context(), &activityv1.ListSevenDayCheckInClaimsRequest{
		Meta:       h.meta(c),
		Status:     strings.TrimSpace(options.Status),
		UserId:     userID,
		CheckinDay: strings.TrimSpace(firstQuery(c, "checkin_day", "checkinDay")),
		DayIndex:   int32(dayIndex),
		Page:       int32(options.Page),
		PageSize:   int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取七日签到领取记录失败")
		return
	}
	items := make([]claimDTO, 0, len(resp.GetClaims()))
	for _, claim := range resp.GetClaims() {
		items = append(items, claimFromProto(claim))
	}
	if err := h.fillClaimUsers(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) meta(c *gin.Context) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		RequestId: middleware.CurrentRequestID(c),
		Caller:    "admin-server",
		AppCode:   appctx.FromContext(c.Request.Context()),
		SentAtMs:  time.Now().UnixMilli(),
	}
}

func (h *Handler) resolveClaimUserID(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
	}
	// 签到领取记录按 activity-service 的内部 user_id 查；短号、靓号和昵称先在 admin 侧解析，避免下游误用展示 ID。
	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) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
	if h.userDB == nil || len(claims) == 0 {
		return nil
	}
	ids := collectClaimUserIDs(claims)
	if len(ids) == 0 {
		return 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 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 err
		}
		users[user.UserID] = user
	}
	if err := rows.Err(); err != nil {
		return err
	}
	for index := range claims {
		if user, ok := users[claims[index].UserID]; ok {
			claims[index].User = &user
			continue
		}
		claims[index].User = &userDTO{UserID: claims[index].UserID}
	}
	return nil
}

func configFromProto(config *activityv1.SevenDayCheckInConfig) configDTO {
	if config == nil {
		return configDTO{Rewards: defaultRewards()}
	}
	rewards := make([]rewardDTO, 0, len(config.GetRewards()))
	for _, reward := range config.GetRewards() {
		rewards = append(rewards, rewardDTO{DayIndex: reward.GetDayIndex(), ResourceGroupID: reward.GetResourceGroupId()})
	}
	return configDTO{
		AppCode:          config.GetAppCode(),
		Enabled:          config.GetEnabled(),
		ConfigVersion:    config.GetConfigVersion(),
		Rewards:          rewards,
		UpdatedByAdminID: config.GetUpdatedByAdminId(),
		CreatedAtMS:      config.GetCreatedAtMs(),
		UpdatedAtMS:      config.GetUpdatedAtMs(),
	}
}

func claimFromProto(claim *activityv1.SevenDayCheckInClaim) claimDTO {
	if claim == nil {
		return claimDTO{}
	}
	return claimDTO{
		ClaimID:         claim.GetClaimId(),
		CommandID:       claim.GetCommandId(),
		UserID:          claim.GetUserId(),
		CycleNo:         claim.GetCycleNo(),
		CheckinDay:      claim.GetCheckinDay(),
		DayIndex:        claim.GetDayIndex(),
		ConfigVersion:   claim.GetConfigVersion(),
		ResourceGroupID: claim.GetResourceGroupId(),
		WalletCommandID: claim.GetWalletCommandId(),
		WalletGrantID:   claim.GetWalletGrantId(),
		Status:          claim.GetStatus(),
		FailureReason:   claim.GetFailureReason(),
		SignedAtMS:      claim.GetSignedAtMs(),
		GrantedAtMS:     claim.GetGrantedAtMs(),
		CreatedAtMS:     claim.GetCreatedAtMs(),
		UpdatedAtMS:     claim.GetUpdatedAtMs(),
	}
}

func defaultRewards() []rewardDTO {
	items := make([]rewardDTO, 0, 7)
	for day := int32(1); day <= 7; day++ {
		items = append(items, rewardDTO{DayIndex: day})
	}
	return items
}

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

func parseOptionalInt(value string) int {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0
	}
	var result int
	for _, char := range value {
		if char < '0' || char > '9' {
			return -1
		}
		result = result*10 + int(char-'0')
	}
	return result
}

func collectClaimUserIDs(claims []claimDTO) []int64 {
	seen := make(map[int64]struct{}, len(claims))
	ids := make([]int64, 0, len(claims))
	for _, claim := range claims {
		if claim.UserID <= 0 {
			continue
		}
		if _, ok := seen[claim.UserID]; ok {
			continue
		}
		seen[claim.UserID] = struct{}{}
		ids = append(ids, claim.UserID)
	}
	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 ""
	}
	return strings.TrimRight(strings.Repeat("?,", count), ",")
}
