package mysql

import (
	"context"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
)

// ApplyGameCoinChange 在同一事务内完成游戏 COIN 改账、分录和 outbox。
func (r *Repository) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) {
	if r == nil || r.db == nil {
		return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	command.AppCode = appcode.FromContext(ctx)

	bizType, delta, err := gameBizTypeAndDelta(command.OpType, command.CoinAmount)
	if err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}
	defer func() { _ = tx.Rollback() }()

	if txRow, exists, err := r.lookupTransactionWithConflictCode(ctx, tx, command.CommandID, command.RequestHash, bizType, xerr.IdempotencyConflict); err != nil || exists {
		if err != nil || !exists {
			return ledger.GameCoinChangeReceipt{}, err
		}
		receipt, receiptErr := r.receiptForGameTransaction(ctx, tx, txRow.TransactionID)
		receipt.IdempotentReplay = true
		return receipt, receiptErr
	}

	nowMs := time.Now().UnixMilli()
	createIfMissing := delta > 0
	account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, createIfMissing, nowMs)
	if err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}
	if delta < 0 && account.AvailableAmount < -delta {
		return ledger.GameCoinChangeReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
	}

	transactionID := transactionID(command.AppCode, command.CommandID)
	balanceAfter := account.AvailableAmount + delta
	metadata := gameCoinMetadata{
		AppCode:         command.AppCode,
		UserID:          command.UserID,
		PlatformCode:    command.PlatformCode,
		GameID:          command.GameID,
		ProviderOrderID: command.ProviderOrderID,
		ProviderRoundID: command.ProviderRoundID,
		OpType:          command.OpType,
		AssetType:       ledger.AssetCoin,
		CoinAmount:      command.CoinAmount,
		AvailableDelta:  delta,
		RoomID:          command.RoomID,
		BalanceAfter:    balanceAfter,
		AppliedAtMS:     nowMs,
	}
	if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizType, command.RequestHash, command.ProviderOrderID, metadata, nowMs); err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}
	if err := r.applyAccountDelta(ctx, tx, account, delta, 0, nowMs); err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}
	if err := r.insertEntry(ctx, tx, walletEntry{
		TransactionID:  transactionID,
		UserID:         command.UserID,
		AssetType:      ledger.AssetCoin,
		AvailableDelta: delta,
		FrozenDelta:    0,
		AvailableAfter: balanceAfter,
		FrozenAfter:    account.FrozenAmount,
		RoomID:         command.RoomID,
		CreatedAtMS:    nowMs,
	}); err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}
	if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
		balanceChangedEvent(transactionID, command.CommandID, command.UserID, ledger.AssetCoin, delta, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs, bizType),
	}); err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}
	if err := tx.Commit(); err != nil {
		return ledger.GameCoinChangeReceipt{}, err
	}

	return receiptFromGameMetadata(transactionID, metadata, false), nil
}

func gameBizTypeAndDelta(opType string, coinAmount int64) (string, int64, error) {
	switch ledger.NormalizeGameOpType(opType) {
	case ledger.GameOpDebit:
		return bizTypeGameDebit, -coinAmount, nil
	case ledger.GameOpCredit:
		return bizTypeGameCredit, coinAmount, nil
	case ledger.GameOpRefund:
		return bizTypeGameRefund, coinAmount, nil
	case ledger.GameOpReverse:

		return bizTypeGameReverse, -coinAmount, nil
	default:
		return "", 0, xerr.New(xerr.InvalidArgument, "game op_type is invalid")
	}
}
