package mysql

import (
	"context"
	"fmt"
	"math"
	"time"

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

// AdminCreditAsset 在一个事务内写入后台调账交易、分录和 outbox。
func (r *Repository) AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) {
	if r == nil || r.db == nil {
		return ledger.AssetBalance{}, "", xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	command.AppCode = appcode.FromContext(ctx)

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

	requestHash := adminCreditRequestHash(command)
	if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeManualCredit); err != nil || exists {
		if err != nil || !exists {
			return ledger.AssetBalance{}, "", err
		}
		balance, balanceErr := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, command.TargetUserID, command.AssetType)
		return balance, txRow.TransactionID, balanceErr
	}

	nowMs := time.Now().UnixMilli()

	account, err := r.lockAccount(ctx, tx, command.TargetUserID, command.AssetType, command.Amount > 0, nowMs)
	if err != nil {
		return ledger.AssetBalance{}, "", err
	}
	if command.Amount < 0 && (command.Amount == math.MinInt64 || account.AvailableAmount < -command.Amount) {
		return ledger.AssetBalance{}, "", xerr.New(xerr.InsufficientBalance, "insufficient balance")
	}
	transactionID := transactionID(command.AppCode, command.CommandID)
	metadata := adminCreditMetadata{
		AppCode:        command.AppCode,
		TargetUserID:   command.TargetUserID,
		AssetType:      command.AssetType,
		Amount:         command.Amount,
		OperatorUserID: command.OperatorUserID,
		Reason:         command.Reason,
		Remark:         command.Remark,
		EvidenceRef:    command.EvidenceRef,
	}
	if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeManualCredit, requestHash, command.EvidenceRef, metadata, nowMs); err != nil {
		return ledger.AssetBalance{}, "", err
	}
	if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
		return ledger.AssetBalance{}, "", err
	}
	balance := ledger.AssetBalance{
		AppCode:         command.AppCode,
		UserID:          command.TargetUserID,
		AssetType:       command.AssetType,
		AvailableAmount: account.AvailableAmount + command.Amount,
		FrozenAmount:    account.FrozenAmount,
		Version:         account.Version + 1,
		UpdatedAtMs:     nowMs,
	}
	if err := r.insertEntry(ctx, tx, walletEntry{
		TransactionID:  transactionID,
		UserID:         command.TargetUserID,
		AssetType:      command.AssetType,
		AvailableDelta: command.Amount,
		FrozenDelta:    0,
		AvailableAfter: balance.AvailableAmount,
		FrozenAfter:    balance.FrozenAmount,
		CreatedAtMS:    nowMs,
	}); err != nil {
		return ledger.AssetBalance{}, "", err
	}
	if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
		balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, command.AssetType, command.Amount, 0, balance.AvailableAmount, balance.FrozenAmount, balance.Version, metadata, nowMs, bizTypeManualCredit),
	}); err != nil {
		return ledger.AssetBalance{}, "", err
	}
	if err := tx.Commit(); err != nil {
		return ledger.AssetBalance{}, "", err
	}

	return balance, transactionID, nil
}

func adminCreditRequestHash(command ledger.AdminCreditAssetCommand) string {
	if command.Remark == "" {
		return stableHash(fmt.Sprintf("admin_credit|%s|%d|%s|%d|%d|%s|%s",
			appcode.Normalize(command.AppCode),
			command.TargetUserID,
			command.AssetType,
			command.Amount,
			command.OperatorUserID,
			command.Reason,
			command.EvidenceRef,
		))
	}
	return stableHash(fmt.Sprintf("admin_credit_v2|%s|%d|%s|%d|%d|%s|%s|%s",
		appcode.Normalize(command.AppCode),
		command.TargetUserID,
		command.AssetType,
		command.Amount,
		command.OperatorUserID,
		command.Reason,
		command.Remark,
		command.EvidenceRef,
	))
}
