package giftrecord

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/modules/shared"
)

const (
	bizTypeGiftDebit       = "gift_debit"
	bizTypeDirectGiftDebit = "direct_gift_debit"
	walletStatusSucceeded  = "succeeded"
)

type Service struct {
	userDB   *sql.DB
	walletDB *sql.DB
}

type giftMetadata struct {
	GiftID       string `json:"gift_id"`
	GiftName     string `json:"gift_name"`
	GiftIconURL  string `json:"gift_icon_url"`
	GiftCount    int32  `json:"gift_count"`
	CoinPrice    int64  `json:"coin_price"`
	ChargeAmount int64  `json:"charge_amount"`
	CoinSpent    int64  `json:"coin_spent"`
	SenderUserID int64  `json:"sender_user_id"`
	TargetUserID int64  `json:"target_user_id"`
	RoomID       string `json:"room_id"`
	DirectGift   bool   `json:"direct_gift"`
}

type userProfile struct {
	UserID               int64
	DisplayUserID        string
	DefaultDisplayUserID string
	PrettyDisplayUserID  string
	PrettyID             string
	Username             string
	Avatar               string
}

func NewService(userDB *sql.DB, walletDB *sql.DB) *Service {
	return &Service{userDB: userDB, walletDB: walletDB}
}

// List 只读取 wallet-service 已成功落账的交易快照：名称、封面、数量和价格都来自送礼发生时的 metadata，
// 不回查当前礼物配置覆盖历史事实，也不扫描 room_outbox 这类会按保留策略清理的投递表。
func (s *Service) List(ctx context.Context, appCode string, query listQuery) ([]recordDTO, int64, error) {
	query = normalizeListQuery(query)
	if s == nil || s.walletDB == nil {
		return nil, 0, fmt.Errorf("wallet mysql is not configured")
	}
	senderIDs, senderFiltered, err := s.resolveSenderFilter(ctx, appCode, query.SenderFilter)
	if err != nil {
		return nil, 0, err
	}
	if senderFiltered && len(senderIDs) == 0 {
		// 用户身份筛选没有命中时直接返回空页，避免在钱包大表上构造永远为假的扫描条件。
		return []recordDTO{}, 0, nil
	}

	whereSQL, args := giftRecordWhere(appCode, query, senderIDs)
	var total int64
	if err := s.walletDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM wallet_transactions "+whereSQL, args...).Scan(&total); err != nil {
		return nil, 0, err
	}

	// created_at_ms + transaction_id 组成稳定倒序；对应 owner 库组合索引，避免同一毫秒多笔交易跨页重复或遗漏。
	rows, err := s.walletDB.QueryContext(ctx, `
		SELECT transaction_id, biz_type, COALESCE(CAST(metadata_json AS CHAR), '{}'), created_at_ms
		FROM wallet_transactions
		`+whereSQL+`
		ORDER BY created_at_ms DESC, transaction_id DESC
		LIMIT ? OFFSET ?`, append(args, query.PageSize, listOffset(query))...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]recordDTO, 0, query.PageSize)
	userIDs := make([]int64, 0, query.PageSize*2)
	for rows.Next() {
		var item recordDTO
		var bizType string
		var metadataJSON string
		if err := rows.Scan(&item.TransactionID, &bizType, &metadataJSON, &item.CreatedAtMS); err != nil {
			return nil, 0, err
		}
		var metadata giftMetadata
		if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil {
			return nil, 0, fmt.Errorf("decode gift transaction %s metadata: %w", item.TransactionID, err)
		}
		item = recordFromMetadata(item, bizType, metadata)
		items = append(items, item)
		userIDs = append(userIDs, metadata.SenderUserID, metadata.TargetUserID)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}

	profiles, err := s.userProfiles(ctx, appCode, userIDs)
	if err != nil {
		return nil, 0, err
	}
	for index := range items {
		senderID, _ := strconv.ParseInt(items[index].Sender.UserID, 10, 64)
		receiverID, _ := strconv.ParseInt(items[index].Receiver.UserID, 10, 64)
		// 用户资料只补充公共用户组件所需展示字段；即使用户资料缺失，也保留账务快照中的长 ID，不吞掉送礼事实。
		if profile, ok := profiles[senderID]; ok {
			items[index].Sender = userDTOFromProfile(profile)
		}
		if profile, ok := profiles[receiverID]; ok {
			items[index].Receiver = userDTOFromProfile(profile)
		}
	}
	return items, total, nil
}

func giftRecordWhere(appCode string, query listQuery, senderIDs []int64) (string, []any) {
	where := "WHERE app_code = ? AND status = ?"
	args := []any{strings.TrimSpace(appCode), walletStatusSucceeded}
	switch query.Scene {
	case sceneRoom:
		where += " AND biz_type = ?"
		args = append(args, bizTypeGiftDebit)
	case sceneDirect:
		where += " AND biz_type = ?"
		args = append(args, bizTypeDirectGiftDebit)
	default:
		where += " AND biz_type IN (?, ?)"
		args = append(args, bizTypeGiftDebit, bizTypeDirectGiftDebit)
	}
	if query.StartAtMS > 0 {
		where += " AND created_at_ms >= ?"
		args = append(args, query.StartAtMS)
	}
	if query.EndAtMS > 0 {
		where += " AND created_at_ms < ?"
		args = append(args, query.EndAtMS)
	}
	if len(senderIDs) > 0 {
		// gift_sender_user_id 是 wallet owner 从不可变 metadata 快照生成的索引列；后台不能为筛选跨库 JOIN 用户表，
		// 因此先在 user DB 解析公共身份字段，再用内部 user_id 命中钱包组合索引完成分页。
		where += " AND gift_sender_user_id IN (" + placeholders(len(senderIDs)) + ")"
		for _, senderID := range senderIDs {
			args = append(args, senderID)
		}
	}
	return where, args
}

func (s *Service) resolveSenderFilter(ctx context.Context, appCode string, filter shared.UserIdentityFilter) ([]int64, bool, error) {
	if filter.IsEmpty() {
		return nil, false, nil
	}
	directUserIDs := make([]int64, 0, 1)
	if userID, err := strconv.ParseInt(strings.TrimSpace(filter.UserID), 10, 64); err == nil && userID > 0 {
		// 钱包事实必须在用户资料已删除或未同步时仍可按内部长 ID 检索。
		directUserIDs = append(directUserIDs, userID)
	}
	if s == nil || s.userDB == nil {
		if len(directUserIDs) > 0 {
			return directUserIDs, true, nil
		}
		return nil, true, fmt.Errorf("user mysql is not configured")
	}

	matchSQL, matchArgs := shared.UserIdentityFieldsSQL("u", "u.user_id", filter, time.Now().UTC().UnixMilli())
	args := append([]any{strings.TrimSpace(appCode)}, matchArgs...)
	// 公共用户筛选允许昵称模糊匹配；最多解析 1000 个内部 ID，既覆盖正常运营检索，又限制后续钱包 IN 条件规模。
	rows, err := s.userDB.QueryContext(ctx, `
		SELECT u.user_id
		FROM users u
		WHERE u.app_code = ? AND `+matchSQL+`
		ORDER BY u.updated_at_ms DESC, u.user_id DESC
		LIMIT 1000`, args...)
	if err != nil {
		return nil, true, err
	}
	defer rows.Close()

	userIDs := append([]int64(nil), directUserIDs...)
	for rows.Next() {
		var userID int64
		if err := rows.Scan(&userID); err != nil {
			return nil, true, err
		}
		userIDs = append(userIDs, userID)
	}
	if err := rows.Err(); err != nil {
		return nil, true, err
	}
	return uniquePositiveInt64s(userIDs), true, nil
}

func recordFromMetadata(item recordDTO, bizType string, metadata giftMetadata) recordDTO {
	item.Scene = sceneRoom
	if bizType == bizTypeDirectGiftDebit || metadata.DirectGift {
		item.Scene = sceneDirect
	}
	item.RoomID = strings.TrimSpace(metadata.RoomID)
	item.Sender = userDTO{UserID: formatUserID(metadata.SenderUserID)}
	item.Receiver = userDTO{UserID: formatUserID(metadata.TargetUserID)}
	item.Gift = giftDTO{
		GiftID:   strings.TrimSpace(metadata.GiftID),
		Name:     firstNonEmpty(metadata.GiftName, metadata.GiftID, "-"),
		CoverURL: strings.TrimSpace(metadata.GiftIconURL),
	}
	item.GiftCount = metadata.GiftCount
	item.UnitValue = metadata.CoinPrice
	item.GiftValue = metadata.CoinSpent
	if item.GiftValue <= 0 {
		item.GiftValue = metadata.ChargeAmount
	}
	if item.UnitValue <= 0 && item.GiftCount > 0 {
		item.UnitValue = item.GiftValue / int64(item.GiftCount)
	}
	return item
}

func (s *Service) userProfiles(ctx context.Context, appCode string, userIDs []int64) (map[int64]userProfile, error) {
	result := make(map[int64]userProfile)
	if s == nil || s.userDB == nil {
		return result, nil
	}
	userIDs = uniquePositiveInt64s(userIDs)
	if len(userIDs) == 0 {
		return result, nil
	}

	nowMS := time.Now().UTC().UnixMilli()
	args := []any{nowMS, nowMS, strings.TrimSpace(appCode)}
	for _, userID := range userIDs {
		args = append(args, userID)
	}
	// 每页最多查询 2 * page_size 个用户；靓号子查询均命中 (app_code,user_id,status) 索引，
	// 这样公共用户组件能展示短 ID/靓号，又不会把用户表 join 进大体量钱包分页查询。
	rows, err := s.userDB.QueryContext(ctx, `
		SELECT users.user_id,
		       users.current_display_user_id,
		       COALESCE(users.default_display_user_id, ''),
		       COALESCE((
		         SELECT lease.display_user_id
		         FROM pretty_display_user_id_leases lease
		         WHERE lease.app_code = users.app_code
		           AND lease.user_id = users.user_id
		           AND lease.status = 'active'
		           AND (COALESCE(lease.expires_at_ms, 0) = 0 OR lease.expires_at_ms > ?)
		         ORDER BY lease.created_at_ms DESC, lease.lease_id DESC
		         LIMIT 1
		       ), ''),
		       COALESCE((
		         SELECT pretty.pretty_id
		         FROM pretty_display_user_id_leases lease
		         JOIN pretty_display_ids pretty
		           ON pretty.app_code = lease.app_code
		          AND pretty.assigned_lease_id = lease.lease_id
		          AND pretty.status = 'assigned'
		         WHERE lease.app_code = users.app_code
		           AND lease.user_id = users.user_id
		           AND lease.status = 'active'
		           AND (COALESCE(lease.expires_at_ms, 0) = 0 OR lease.expires_at_ms > ?)
		         ORDER BY lease.created_at_ms DESC, lease.lease_id DESC
		         LIMIT 1
		       ), ''),
		       COALESCE(users.username, ''), COALESCE(users.avatar, '')
		FROM users
		WHERE users.app_code = ? AND users.user_id IN (`+placeholders(len(userIDs))+`)
	`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var profile userProfile
		if err := rows.Scan(
			&profile.UserID,
			&profile.DisplayUserID,
			&profile.DefaultDisplayUserID,
			&profile.PrettyDisplayUserID,
			&profile.PrettyID,
			&profile.Username,
			&profile.Avatar,
		); err != nil {
			return nil, err
		}
		result[profile.UserID] = profile
	}
	return result, rows.Err()
}

func userDTOFromProfile(profile userProfile) userDTO {
	return userDTO{
		UserID:               formatUserID(profile.UserID),
		DisplayUserID:        profile.DisplayUserID,
		DefaultDisplayUserID: profile.DefaultDisplayUserID,
		PrettyDisplayUserID:  profile.PrettyDisplayUserID,
		PrettyID:             profile.PrettyID,
		Username:             profile.Username,
		Avatar:               profile.Avatar,
	}
}

func uniquePositiveInt64s(values []int64) []int64 {
	seen := make(map[int64]struct{}, len(values))
	result := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	return result
}

func placeholders(count int) string {
	if count <= 0 {
		return ""
	}
	return strings.TrimRight(strings.Repeat("?,", count), ",")
}

func formatUserID(userID int64) string {
	if userID <= 0 {
		return ""
	}
	return strconv.FormatInt(userID, 10)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if normalized := strings.TrimSpace(value); normalized != "" {
			return normalized
		}
	}
	return ""
}
