package hostwithdrawal

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

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

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

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

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

func (s *Service) List(ctx context.Context, query listQuery) ([]itemDTO, int64, error) {
	var err error
	query, err = normalizeListQuery(query)
	if err != nil {
		return nil, 0, err
	}
	if s == nil || s.adminDB == nil || s.walletDB == nil {
		return nil, 0, fmt.Errorf("host withdrawal storage is not configured")
	}

	keywordUserIDs, err := s.resolveKeywordUserIDs(ctx, query.AppCode, query.Keyword)
	if err != nil {
		return nil, 0, err
	}
	fetchLimit := offset(query.Page, query.PageSize) + query.PageSize

	items := make([]itemDTO, 0, fetchLimit)
	total := int64(0)
	if query.RecordType == "" || query.RecordType == recordTypeSalaryTransfer {
		// 工资转币商只展示已完成账本事实；pending/approved/rejected 属于提现申请状态，不能误筛到钱包转账。
		if query.Status == "" || query.Status == statusCompleted {
			count, err := s.countSalaryTransfers(ctx, query, keywordUserIDs)
			if err != nil {
				return nil, 0, err
			}
			total += count
			records, err := s.listSalaryTransfers(ctx, query, keywordUserIDs, fetchLimit)
			if err != nil {
				return nil, 0, err
			}
			items = append(items, records...)
		}
	}
	if query.RecordType == "" || query.RecordType == recordTypeWithdrawal {
		// 提现申请状态来自后台申请表；completed 只代表钱包转币商完成态，所以提现分支直接跳过。
		if query.Status != statusCompleted {
			count, err := s.countWithdrawalApplications(ctx, query, keywordUserIDs)
			if err != nil {
				return nil, 0, err
			}
			total += count
			records, err := s.listWithdrawalApplications(ctx, query, keywordUserIDs, fetchLimit)
			if err != nil {
				return nil, 0, err
			}
			items = append(items, records...)
		}
	}

	enriched, err := s.enrichUsers(ctx, query.AppCode, items)
	if err != nil {
		return nil, 0, err
	}
	return pageMergedItems(enriched, query.Page, query.PageSize), total, nil
}

func (s *Service) countSalaryTransfers(ctx context.Context, query listQuery, keywordUserIDs []int64) (int64, error) {
	whereSQL, args := salaryTransferWhere(query, keywordUserIDs)
	var total int64
	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)
	return total, err
}

func (s *Service) listSalaryTransfers(ctx context.Context, query listQuery, keywordUserIDs []int64, limit int) ([]itemDTO, error) {
	if limit <= 0 {
		return nil, nil
	}
	whereSQL, args := salaryTransferWhere(query, keywordUserIDs)
	rows, err := s.walletDB.QueryContext(ctx, `
		SELECT e.entry_id, e.transaction_id, wt.command_id, e.user_id, e.counterparty_user_id,
		       e.available_delta, e.available_after, COALESCE(CAST(wt.metadata_json AS CHAR), '{}'), 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 ?`,
		append(args, limit)...,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	items := make([]itemDTO, 0, limit)
	for rows.Next() {
		var (
			entryID            int64
			transactionID      string
			commandID          string
			sellerUserID       int64
			sourceUserID       int64
			availableDelta     int64
			sellerBalanceAfter int64
			metadataJSON       string
			createdAtMS        int64
		)
		if err := rows.Scan(&entryID, &transactionID, &commandID, &sellerUserID, &sourceUserID, &availableDelta, &sellerBalanceAfter, &metadataJSON, &createdAtMS); err != nil {
			return nil, err
		}
		metadata, err := parseMetadataJSON(metadataJSON)
		if err != nil {
			return nil, err
		}
		if value := metadataInt64(metadata, "source_user_id", "sourceUserId"); value > 0 {
			sourceUserID = value
		}
		if value := metadataInt64(metadata, "seller_user_id", "sellerUserId"); value > 0 {
			sellerUserID = value
		}
		assetType := firstNonEmpty(metadataString(metadata, "salary_asset_type"), query.SalaryAssetType)
		coinAmount := metadataInt64(metadata, "coin_amount", "coinAmount")
		if coinAmount == 0 {
			coinAmount = absInt64(availableDelta)
		}
		if metadata == nil {
			metadata = map[string]any{}
		}
		metadata["seller_balance_after"] = sellerBalanceAfter
		items = append(items, itemDTO{
			ID:              fmt.Sprintf("%s:%d", recordTypeSalaryTransfer, entryID),
			RecordType:      recordTypeSalaryTransfer,
			Identity:        identityFromAsset(assetType),
			SalaryAssetType: assetType,
			SourceUserID:    formatOptionalID(sourceUserID),
			SourceUser:      userDTO{UserID: formatOptionalID(sourceUserID)},
			SellerUserID:    formatOptionalID(sellerUserID),
			SellerUser:      userDTO{UserID: formatOptionalID(sellerUserID)},
			USDMinorAmount:  metadataInt64(metadata, "salary_usd_minor", "salaryUsdMinor"),
			CoinAmount:      coinAmount,
			Status:          statusCompleted,
			TransactionID:   transactionID,
			CommandID:       commandID,
			Metadata:        metadata,
			CreatedAtMS:     createdAtMS,
			UpdatedAtMS:     createdAtMS,
		})
	}
	return items, rows.Err()
}

func (s *Service) countWithdrawalApplications(ctx context.Context, query listQuery, keywordUserIDs []int64) (int64, error) {
	whereSQL, args := withdrawalWhere(query, keywordUserIDs)
	var total int64
	err := s.adminDB.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM admin_user_withdrawal_applications a
		`+whereSQL,
		args...,
	).Scan(&total)
	return total, err
}

func (s *Service) listWithdrawalApplications(ctx context.Context, query listQuery, keywordUserIDs []int64, limit int) ([]itemDTO, error) {
	if limit <= 0 {
		return nil, nil
	}
	whereSQL, args := withdrawalWhere(query, keywordUserIDs)
	rows, err := s.adminDB.QueryContext(ctx, `
		SELECT id, user_id, salary_asset_type, withdraw_amount_minor, withdraw_method, withdraw_address,
		       freeze_command_id, freeze_transaction_id, audit_command_id, audit_transaction_id,
		       status, COALESCE(audit_remark, ''), audit_image_url, created_at_ms, updated_at_ms
		FROM admin_user_withdrawal_applications a
		`+whereSQL+`
		ORDER BY a.created_at_ms DESC, a.id DESC
		LIMIT ?`,
		append(args, limit)...,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	items := make([]itemDTO, 0, limit)
	for rows.Next() {
		var (
			id                  int64
			userIDText          string
			assetType           string
			amountMinor         int64
			withdrawMethod      string
			withdrawAddress     string
			freezeCommandID     string
			freezeTransactionID string
			auditCommandID      string
			auditTransactionID  string
			status              string
			auditRemark         string
			auditImageURL       string
			createdAtMS         int64
			updatedAtMS         int64
		)
		if err := rows.Scan(&id, &userIDText, &assetType, &amountMinor, &withdrawMethod, &withdrawAddress, &freezeCommandID, &freezeTransactionID, &auditCommandID, &auditTransactionID, &status, &auditRemark, &auditImageURL, &createdAtMS, &updatedAtMS); err != nil {
			return nil, err
		}
		sourceUserID := parseInt64OrZero(userIDText)
		items = append(items, itemDTO{
			ID:                  fmt.Sprintf("%s:%d", recordTypeWithdrawal, id),
			RecordType:          recordTypeWithdrawal,
			Identity:            identityFromAsset(assetType),
			SalaryAssetType:     assetType,
			SourceUserID:        formatOptionalID(sourceUserID),
			SourceUser:          userDTO{UserID: firstNonEmpty(formatOptionalID(sourceUserID), userIDText)},
			USDMinorAmount:      amountMinor,
			Status:              status,
			CommandID:           firstNonEmpty(auditCommandID, freezeCommandID),
			ApplicationID:       strconv.FormatInt(id, 10),
			FreezeTransactionID: freezeTransactionID,
			AuditTransactionID:  auditTransactionID,
			WithdrawMethod:      withdrawMethod,
			WithdrawAddress:     withdrawAddress,
			AuditRemark:         auditRemark,
			AuditImageURL:       auditImageURL,
			Metadata:            map[string]any{},
			CreatedAtMS:         createdAtMS,
			UpdatedAtMS:         updatedAtMS,
		})
	}
	return items, rows.Err()
}

func salaryTransferWhere(query listQuery, keywordUserIDs []int64) (string, []any) {
	where := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type = ? AND wt.status = ?"
	args := []any{query.AppCode, assetCoinSellerCoin, bizSalaryTransfer, transactionSucceeded}
	if query.SalaryAssetType != "" {
		// 身份维度是工资资产类型，来源写在交易 metadata 中；只筛工资转币商事实，不触碰普通币商出货或后台进货。
		where += " AND JSON_UNQUOTE(JSON_EXTRACT(wt.metadata_json, '$.salary_asset_type')) = ?"
		args = append(args, query.SalaryAssetType)
	}
	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 keywordSQL, keywordArgs := salaryTransferKeywordSQL(query.Keyword, keywordUserIDs); keywordSQL != "" {
		where += " AND (" + keywordSQL + ")"
		args = append(args, keywordArgs...)
	}
	return where, args
}

func salaryTransferKeywordSQL(keyword string, userIDs []int64) (string, []any) {
	clauses := make([]string, 0, 4)
	args := make([]any, 0)
	if len(userIDs) > 0 {
		clauses = append(clauses, "e.user_id IN ("+placeholders(len(userIDs))+")")
		for _, id := range userIDs {
			args = append(args, id)
		}
		clauses = append(clauses, "e.counterparty_user_id IN ("+placeholders(len(userIDs))+")")
		for _, id := range userIDs {
			args = append(args, id)
		}
	}
	if keyword = strings.TrimSpace(keyword); keyword != "" {
		like := "%" + keyword + "%"
		clauses = append(clauses, "e.transaction_id LIKE ?", "wt.command_id LIKE ?", "CAST(e.user_id AS CHAR) LIKE ?", "CAST(e.counterparty_user_id AS CHAR) LIKE ?")
		args = append(args, like, like, like, like)
	}
	return strings.Join(clauses, " OR "), args
}

func withdrawalWhere(query listQuery, keywordUserIDs []int64) (string, []any) {
	where := "WHERE a.app_code = ?"
	args := []any{query.AppCode}
	if query.SalaryAssetType != "" {
		where += " AND a.salary_asset_type = ?"
		args = append(args, query.SalaryAssetType)
	}
	if query.Status != "" {
		where += " AND a.status = ?"
		args = append(args, query.Status)
	}
	if query.StartAtMS > 0 {
		where += " AND a.created_at_ms >= ?"
		args = append(args, query.StartAtMS)
	}
	if query.EndAtMS > 0 {
		where += " AND a.created_at_ms < ?"
		args = append(args, query.EndAtMS)
	}
	if keywordSQL, keywordArgs := withdrawalKeywordSQL(query.Keyword, keywordUserIDs); keywordSQL != "" {
		where += " AND (" + keywordSQL + ")"
		args = append(args, keywordArgs...)
	}
	return where, args
}

func withdrawalKeywordSQL(keyword string, userIDs []int64) (string, []any) {
	clauses := make([]string, 0, 8)
	args := make([]any, 0)
	if len(userIDs) > 0 {
		clauses = append(clauses, "a.user_id IN ("+placeholders(len(userIDs))+")")
		for _, id := range userIDs {
			args = append(args, strconv.FormatInt(id, 10))
		}
	}
	if keyword = strings.TrimSpace(keyword); keyword != "" {
		like := "%" + keyword + "%"
		clauses = append(clauses,
			"CAST(a.id AS CHAR) LIKE ?",
			"a.user_id LIKE ?",
			"a.withdraw_method LIKE ?",
			"a.withdraw_address LIKE ?",
			"a.freeze_transaction_id LIKE ?",
			"a.audit_transaction_id LIKE ?",
			"a.approver_name LIKE ?",
		)
		args = append(args, like, like, like, like, like, like, like)
	}
	return strings.Join(clauses, " OR "), args
}

func (s *Service) resolveKeywordUserIDs(ctx context.Context, appCode string, keyword string) ([]int64, error) {
	keyword = strings.TrimSpace(keyword)
	if keyword == "" {
		return nil, nil
	}
	userIDs := make([]int64, 0, 4)
	if numeric, err := strconv.ParseInt(keyword, 10, 64); err == nil && numeric > 0 {
		// 账务事实可能早于用户资料补全；数字关键字直接加入候选，避免资料缺失吞掉真实流水。
		userIDs = append(userIDs, numeric)
	}
	if s == nil || s.userDB == nil {
		return positiveUniqueInt64s(userIDs), nil
	}
	matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, time.Now().UnixMilli())
	args := append([]any{appCode}, matchArgs...)
	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, err
	}
	defer rows.Close()
	for rows.Next() {
		var userID int64
		if err := rows.Scan(&userID); err != nil {
			return nil, err
		}
		userIDs = append(userIDs, userID)
	}
	return positiveUniqueInt64s(userIDs), rows.Err()
}

func (s *Service) enrichUsers(ctx context.Context, appCode string, items []itemDTO) ([]itemDTO, error) {
	userIDs := make([]int64, 0, len(items)*2)
	for _, item := range items {
		if id := parseInt64OrZero(item.SourceUserID); id > 0 {
			userIDs = append(userIDs, id)
		}
		if id := parseInt64OrZero(item.SellerUserID); id > 0 {
			userIDs = append(userIDs, id)
		}
	}
	profiles, err := s.userProfiles(ctx, appCode, userIDs)
	if err != nil {
		return nil, err
	}
	out := append([]itemDTO(nil), items...)
	for i := range out {
		if id := parseInt64OrZero(out[i].SourceUserID); id > 0 {
			if profile, ok := profiles[id]; ok {
				out[i].SourceUser = userDTOFromProfile(profile)
			}
		}
		if id := parseInt64OrZero(out[i].SellerUserID); id > 0 {
			if profile, ok := profiles[id]; ok {
				out[i].SellerUser = userDTOFromProfile(profile)
			}
		}
	}
	return out, nil
}

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 = positiveUniqueInt64s(userIDs)
	if len(userIDs) == 0 {
		return result, nil
	}
	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 userDTOFromProfile(profile userProfile) userDTO {
	return userDTO{
		UserID:        strconv.FormatInt(profile.UserID, 10),
		DisplayUserID: profile.DisplayUserID,
		Username:      profile.Username,
		Avatar:        profile.Avatar,
	}
}

func pageMergedItems(items []itemDTO, page int, pageSize int) []itemDTO {
	sort.SliceStable(items, func(i int, j int) bool {
		if items[i].CreatedAtMS != items[j].CreatedAtMS {
			return items[i].CreatedAtMS > items[j].CreatedAtMS
		}
		if items[i].RecordType != items[j].RecordType {
			return items[i].RecordType < items[j].RecordType
		}
		return recordNumericID(items[i]) > recordNumericID(items[j])
	})
	start := offset(page, pageSize)
	if start >= len(items) {
		return []itemDTO{}
	}
	end := start + pageSize
	if end > len(items) {
		end = len(items)
	}
	return items[start:end]
}

func recordNumericID(item itemDTO) int64 {
	if item.RecordType == recordTypeWithdrawal {
		return parseInt64OrZero(item.ApplicationID)
	}
	parts := strings.Split(item.ID, ":")
	if len(parts) == 2 {
		return parseInt64OrZero(parts[1])
	}
	return 0
}

func parseMetadataJSON(value string) (map[string]any, error) {
	value = strings.TrimSpace(value)
	if value == "" || value == "null" {
		return map[string]any{}, nil
	}
	var metadata map[string]any
	if err := json.Unmarshal([]byte(value), &metadata); err != nil {
		return nil, err
	}
	if metadata == nil {
		return map[string]any{}, nil
	}
	return metadata, nil
}

func metadataString(metadata map[string]any, key string) string {
	value, ok := metadata[key]
	if !ok || value == nil {
		return ""
	}
	if typed, ok := value.(string); ok {
		return strings.TrimSpace(typed)
	}
	return strings.TrimSpace(fmt.Sprint(value))
}

func metadataInt64(metadata map[string]any, keys ...string) int64 {
	for _, key := range keys {
		value, ok := metadata[key]
		if !ok || value == nil {
			continue
		}
		switch typed := value.(type) {
		case float64:
			return int64(typed)
		case int64:
			return typed
		case int:
			return int64(typed)
		case string:
			if parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64); err == nil {
				return parsed
			}
		}
	}
	return 0
}

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

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

func parseInt64OrZero(value string) int64 {
	parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
	if err != nil || parsed <= 0 {
		return 0
	}
	return parsed
}

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

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 positiveUniqueInt64s(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
}
