package coinledger

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"
)

const (
	coinAssetType = "COIN"
	directionIn   = "income"
	directionOut  = "expense"
)

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

type userProfile struct {
	UserID        int64
	DisplayUserID string
	Username      string
	Avatar        string
}

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

func (s *Service) ListCoinLedger(ctx context.Context, appCode string, query listQuery) ([]coinLedgerEntryDTO, int64, error) {
	query = normalizeListQuery(query)
	if s == nil || s.walletDB == nil {
		return nil, 0, fmt.Errorf("wallet mysql is not configured")
	}

	userIDs, userFiltered, err := s.resolveUserFilter(ctx, appCode, query.UserKeyword)
	if err != nil {
		return nil, 0, err
	}
	if userFiltered && len(userIDs) == 0 {
		return []coinLedgerEntryDTO{}, 0, nil
	}

	// 后台只做只读查询：金币流水事实仍以 wallet_entries 追加分录为准，不在 admin 库冗余账务状态。
	whereSQL, args := coinLedgerWhere(appCode, query, userIDs)
	var total int64
	if err := s.walletDB.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM wallet_entries e
		JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id
		`+whereSQL,
		args...,
	).Scan(&total); err != nil {
		return nil, 0, err
	}

	rows, err := s.walletDB.QueryContext(ctx, `
		SELECT e.entry_id, e.transaction_id, e.user_id, wt.biz_type,
		       e.available_delta, e.available_after, e.counterparty_user_id,
		       e.room_id, e.created_at_ms
		FROM wallet_entries e
		JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id
		`+whereSQL+`
		ORDER BY e.created_at_ms DESC, e.entry_id DESC
		LIMIT ? OFFSET ?`,
		append(args, query.PageSize, offset(query.Page, query.PageSize))...,
	)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]coinLedgerEntryDTO, 0, query.PageSize)
	itemUserIDs := make([]int64, 0, query.PageSize)
	for rows.Next() {
		var item coinLedgerEntryDTO
		var userID int64
		var counterpartyUserID int64
		if err := rows.Scan(
			&item.EntryID,
			&item.TransactionID,
			&userID,
			&item.BizType,
			&item.AvailableDelta,
			&item.AvailableAfter,
			&counterpartyUserID,
			&item.RoomID,
			&item.CreatedAtMS,
		); err != nil {
			return nil, 0, err
		}
		item.UserID = strconv.FormatInt(userID, 10)
		item.User = coinLedgerUserDTO{UserID: item.UserID}
		item.CounterpartyUserID = formatOptionalID(counterpartyUserID)
		item.Direction = directionForDelta(item.AvailableDelta)
		item.Amount = absInt64(item.AvailableDelta)
		items = append(items, item)
		itemUserIDs = append(itemUserIDs, userID)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}

	profiles, err := s.userProfiles(ctx, appCode, itemUserIDs)
	if err != nil {
		return nil, 0, err
	}
	for i := range items {
		userID, _ := strconv.ParseInt(items[i].UserID, 10, 64)
		if profile, ok := profiles[userID]; ok {
			items[i].User = coinLedgerUserDTO{
				UserID:        strconv.FormatInt(profile.UserID, 10),
				DisplayUserID: profile.DisplayUserID,
				Username:      profile.Username,
				Avatar:        profile.Avatar,
			}
		}
	}
	return items, total, nil
}

func (s *Service) resolveUserFilter(ctx context.Context, appCode string, keyword string) ([]int64, bool, error) {
	keyword = strings.TrimSpace(keyword)
	if keyword == "" {
		return nil, false, nil
	}
	directUserIDs := make([]int64, 0, 1)
	if numeric, err := strconv.ParseInt(keyword, 10, 64); err == nil && numeric > 0 {
		directUserIDs = append(directUserIDs, numeric)
	}
	if s == nil || s.userDB == nil {
		if len(directUserIDs) > 0 {
			return directUserIDs, true, nil
		}
		return nil, true, fmt.Errorf("user mysql is not configured")
	}

	// 用户列筛选同时支持长 ID、当前短 ID 和昵称；数字关键字直接加入 user_id，避免用户资料缺失时查不到账务事实。
	args := []any{appCode}
	where := "WHERE app_code = ? AND (current_display_user_id = ?"
	args = append(args, keyword)
	if numeric, err := strconv.ParseInt(keyword, 10, 64); err == nil && numeric > 0 {
		where += " OR user_id = ?"
		args = append(args, numeric)
	}
	where += " OR username LIKE ? ESCAPE '\\\\')"
	args = append(args, "%"+escapeLike(keyword)+"%")

	rows, err := s.userDB.QueryContext(ctx, `
		SELECT user_id
		FROM users
		`+where+`
		ORDER BY updated_at_ms DESC, 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)
	}
	return uniqueInt64s(userIDs), true, rows.Err()
}

func (s *Service) userProfiles(ctx context.Context, appCode string, userIDs []int64) (map[int64]userProfile, error) {
	result := make(map[int64]userProfile, len(userIDs))
	if s == nil || s.userDB == nil || len(userIDs) == 0 {
		return result, nil
	}
	userIDs = uniqueInt64s(userIDs)
	args := make([]any, 0, len(userIDs)+1)
	args = append(args, appCode)
	for _, id := range userIDs {
		args = append(args, id)
	}
	rows, err := s.userDB.QueryContext(ctx, fmt.Sprintf(`
		SELECT user_id, current_display_user_id, COALESCE(username, ''), COALESCE(avatar, '')
		FROM users
		WHERE app_code = ? AND user_id IN (%s)
	`, 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.Username, &profile.Avatar); err != nil {
			return nil, err
		}
		result[profile.UserID] = profile
	}
	return result, rows.Err()
}

func coinLedgerWhere(appCode string, query listQuery, userIDs []int64) (string, []any) {
	where := "WHERE e.app_code = ? AND e.asset_type = ?"
	args := []any{appCode, coinAssetType}
	if query.StartAtMS > 0 {
		where += " AND e.created_at_ms >= ?"
		args = append(args, query.StartAtMS)
	}
	if query.EndAtMS > 0 {
		where += " AND e.created_at_ms < ?"
		args = append(args, query.EndAtMS)
	}
	if len(userIDs) > 0 {
		where += fmt.Sprintf(" AND e.user_id IN (%s)", placeholders(len(userIDs)))
		for _, id := range userIDs {
			args = append(args, id)
		}
	}
	return where, args
}

func normalizeListQuery(query listQuery) listQuery {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.PageSize < 1 {
		query.PageSize = 20
	}
	if query.PageSize > 100 {
		query.PageSize = 100
	}
	query.UserKeyword = strings.TrimSpace(query.UserKeyword)
	return query
}

func directionForDelta(delta int64) string {
	if delta < 0 {
		return directionOut
	}
	return directionIn
}

func absInt64(value int64) int64 {
	if value < 0 {
		return -value
	}
	return value
}

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

func offset(page int, pageSize int) int {
	if page < 1 {
		page = 1
	}
	return (page - 1) * pageSize
}

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

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

func escapeLike(value string) string {
	value = strings.ReplaceAll(value, `\`, `\\`)
	value = strings.ReplaceAll(value, `%`, `\%`)
	value = strings.ReplaceAll(value, `_`, `\_`)
	return value
}
