package mysql

import (
	"context"
	"fmt"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	"strings"
	"time"
)

// DebitGift 在一个 MySQL 事务内完成 sender 扣费、主播周期钻石入账、分录和 outbox；历史 GIFT_POINT 回执字段保留但不再入账。
func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) {
	if r == nil || r.db == nil {
		return ledger.Receipt{}, 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.Receipt{}, err
	}
	defer func() {

		_ = tx.Rollback()
	}()

	requestHash := debitRequestHash(command)
	bizType := giftDebitBizType(command)
	if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizType); err != nil || exists {
		if err != nil || !exists {
			return ledger.Receipt{}, err
		}
		receipt, receiptErr := r.receiptForGiftTransaction(ctx, tx, txRow.TransactionID)
		return receipt, receiptErr
	}

	nowMs := time.Now().UnixMilli()
	if command.RegionID < 0 {
		return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "region_id is invalid")
	}
	if command.SenderRegionID < 0 {
		return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "sender_region_id is invalid")
	}
	giftConfig, err := r.resolveActiveGiftConfig(ctx, tx, command.GiftID, command.RegionID)
	if err != nil {
		return ledger.Receipt{}, err
	}
	price, err := r.resolveGiftPrice(ctx, tx, command.GiftID, command.PriceVersion, nowMs)
	if err != nil {
		return ledger.Receipt{}, err
	}
	chargeSource := normalizeGiftChargeSource(command.ChargeSource, command.EntitlementID)
	if command.RobotGift && chargeSource == giftChargeSourceBag {
		return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "robot gift cannot use bag entitlement")
	}
	chargeAssetType := price.ChargeAssetType
	if command.RobotGift {

		chargeAssetType = ledger.AssetRobotCoin
	} else if chargeSource == giftChargeSourceBag {
		chargeAssetType = ledger.AssetBagGift
	}
	chargeAmount, err := checkedMul(price.CoinPrice, int64(command.GiftCount))
	if err != nil {
		return ledger.Receipt{}, err
	}

	roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
	if err != nil {
		return ledger.Receipt{}, err
	}

	heatValue, err := giftDiamondAmount(chargeAmount, roomContributionRatio.PPM)
	if err != nil {
		return ledger.Receipt{}, err
	}
	giftIncomeRatio, giftIncomeRatioRegionID, err := r.resolveGiftReturnCoinRatio(ctx, tx, command.AppCode, command.RegionID, giftConfig.GiftTypeCode)
	if err != nil {
		return ledger.Receipt{}, err
	}
	giftIncomeCoinAmount := int64(0)
	if !command.RobotGift {

		giftIncomeCoinAmount, err = giftDiamondAmount(chargeAmount, giftIncomeRatio.PPM)
		if err != nil {
			return ledger.Receipt{}, err
		}
	}
	hostPeriodDiamondAdded := int64(0)
	hostPeriodCycleKey := ""
	giftDiamondRatioPercent := "0.00"
	giftDiamondRatioRegionID := int64(0)
	if command.TargetIsHost && !command.RobotGift {
		ratio, ratioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
		if err != nil {
			return ledger.Receipt{}, err
		}

		hostPeriodDiamondAdded, err = giftDiamondAmount(chargeAmount, ratio.PPM)
		if err != nil {
			return ledger.Receipt{}, err
		}
		giftDiamondRatioPercent = ratio.Percent
		giftDiamondRatioRegionID = ratioRegionID
		hostPeriodCycleKey = hostPeriodCycleKeyFromMS(nowMs)
	}

	accountAssetType := chargeAssetType
	if chargeSource == giftChargeSourceBag {
		accountAssetType = ledger.AssetCoin
	}
	var senderState *walletAccountState
	var targetCoinState *walletAccountState
	if command.RobotGift {

		sender, err := r.lockAccount(ctx, tx, command.SenderUserID, accountAssetType, true, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		senderState = &walletAccountState{account: sender}
	} else {
		lockRequests := []walletAccountLockRequest{{
			UserID:          command.SenderUserID,
			AssetType:       accountAssetType,
			CreateIfMissing: chargeAmount == 0 || chargeSource == giftChargeSourceBag,
		}}
		if giftIncomeCoinAmount > 0 {
			lockRequests = append(lockRequests, walletAccountLockRequest{
				UserID:          command.TargetUserID,
				AssetType:       ledger.AssetCoin,
				CreateIfMissing: true,
			})
		}
		states, err := r.lockAccountsOrdered(ctx, tx, lockRequests, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		senderState = states[walletAccountLockKey(command.SenderUserID, accountAssetType)]
		if giftIncomeCoinAmount > 0 {
			targetCoinState = states[walletAccountLockKey(command.TargetUserID, ledger.AssetCoin)]
		}
	}
	sender := senderState.account
	robotCoinTopUp := int64(0)
	if command.RobotGift && sender.AvailableAmount < chargeAmount {

		robotCoinTopUp = chargeAmount - sender.AvailableAmount
	}
	if !command.RobotGift && chargeSource != giftChargeSourceBag && sender.AvailableAmount < chargeAmount {
		return ledger.Receipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
	}
	senderAfter := sender.AvailableAmount + robotCoinTopUp - chargeAmount
	if chargeSource == giftChargeSourceBag {

		senderAfter = sender.AvailableAmount
	}
	giftIncomeBalanceAfter := int64(0)
	if giftIncomeCoinAmount > 0 {
		if targetCoinState == nil {
			return ledger.Receipt{}, xerr.New(xerr.Internal, "gift income account is missing")
		}
		giftIncomeBalanceAfter = targetCoinState.account.AvailableAmount + giftIncomeCoinAmount
		if command.TargetUserID == command.SenderUserID && accountAssetType == ledger.AssetCoin && chargeSource != giftChargeSourceBag {

			giftIncomeBalanceAfter = senderAfter + giftIncomeCoinAmount
			senderAfter = giftIncomeBalanceAfter
		}
	}
	transactionID := transactionID(command.AppCode, command.CommandID)
	metadata := giftMetadata{
		AppCode:          command.AppCode,
		GiftID:           command.GiftID,
		GiftName:         giftConfig.Name,
		GiftIconURL:      giftDisplayIconURL(giftConfig.Resource),
		GiftAnimationURL: strings.TrimSpace(giftConfig.Resource.AnimationURL),

		GiftEffectTypes:               normalizeGiftEffectTypes(giftConfig.EffectTypes),
		ResourceID:                    giftConfig.ResourceID,
		ResourceSnapshot:              mustJSON(giftConfig.Resource),
		GiftTypeCode:                  giftConfig.GiftTypeCode,
		CPRelationType:                cpRelationTypeFromPresentationJSON(giftConfig.PresentationJSON),
		PresentationJSON:              giftConfig.PresentationJSON,
		SortOrder:                     giftConfig.SortOrder,
		GiftCount:                     command.GiftCount,
		PriceVersion:                  price.PriceVersion,
		ChargeAssetType:               chargeAssetType,
		ChargeAmount:                  chargeAmount,
		CoinSpent:                     chargeAmount,
		ChargeSource:                  chargeSource,
		EntitlementID:                 strings.TrimSpace(command.EntitlementID),
		GiftPointAdded:                0,
		HeatValue:                     heatValue,
		BalanceAfter:                  senderAfter,
		GiftIncomeCoinAmount:          giftIncomeCoinAmount,
		GiftIncomeRatioPercent:        giftIncomeRatio.Percent,
		GiftIncomeRatioRegionID:       giftIncomeRatioRegionID,
		GiftIncomeBalanceAfter:        giftIncomeBalanceAfter,
		BillingReceipt:                billingReceiptID(command.AppCode, command.CommandID),
		SenderUserID:                  command.SenderUserID,
		SenderRegionID:                command.SenderRegionID,
		TargetUserID:                  command.TargetUserID,
		TargetIsHost:                  command.TargetIsHost,
		TargetHostRegionID:            command.TargetHostRegionID,
		TargetAgencyOwnerUserID:       command.TargetAgencyOwnerUserID,
		HostPeriodDiamondAdded:        hostPeriodDiamondAdded,
		GiftDiamondRatioPercent:       giftDiamondRatioPercent,
		GiftDiamondRatioRegionID:      giftDiamondRatioRegionID,
		HostPeriodCycleKey:            hostPeriodCycleKey,
		RoomID:                        command.RoomID,
		RoomRegionID:                  command.RegionID,
		RoomContributionRatioPercent:  roomContributionRatio.Percent,
		RoomContributionRatioRegionID: roomContributionRatioRegionID,
		CoinPrice:                     price.CoinPrice,
		GiftPointAmount:               0,
		HeatUnitValue:                 price.CoinPrice,
		RobotGift:                     command.RobotGift,
		DirectGift:                    command.DirectGift,
	}
	if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizType, requestHash, fmt.Sprintf("%s:%s", command.GiftID, price.PriceVersion), metadata, nowMs); err != nil {
		return ledger.Receipt{}, err
	}
	events := make([]walletOutboxEvent, 0, 5)
	if robotCoinTopUp > 0 {
		topUpAccount, err := r.applyTrackedAccountDelta(ctx, tx, senderState, robotCoinTopUp, 0, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		if err := r.insertEntry(ctx, tx, walletEntry{
			TransactionID:      transactionID,
			UserID:             command.SenderUserID,
			AssetType:          chargeAssetType,
			AvailableDelta:     robotCoinTopUp,
			FrozenDelta:        0,
			AvailableAfter:     topUpAccount.AvailableAmount,
			FrozenAfter:        topUpAccount.FrozenAmount,
			CounterpartyUserID: 0,
			RoomID:             command.RoomID,
			CreatedAtMS:        nowMs,
		}); err != nil {
			return ledger.Receipt{}, err
		}

		sender = senderState.account
	}
	if chargeSource == giftChargeSourceBag {
		resourceEvent, err := r.consumeGiftEntitlementForSend(ctx, tx, command, giftConfig.ResourceID, int64(command.GiftCount), metadata, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		events = append(events, resourceEvent)
	} else {
		debitAccount, err := r.applyTrackedAccountDelta(ctx, tx, senderState, -chargeAmount, 0, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		if err := r.insertEntry(ctx, tx, walletEntry{
			TransactionID:      transactionID,
			UserID:             command.SenderUserID,
			AssetType:          chargeAssetType,
			AvailableDelta:     -chargeAmount,
			FrozenDelta:        0,
			AvailableAfter:     debitAccount.AvailableAmount,
			FrozenAfter:        debitAccount.FrozenAmount,
			CounterpartyUserID: command.TargetUserID,
			RoomID:             command.RoomID,
			CreatedAtMS:        nowMs,
		}); err != nil {
			return ledger.Receipt{}, err
		}
	}
	if giftIncomeCoinAmount > 0 {
		incomeState := targetCoinState
		if command.TargetUserID == command.SenderUserID && accountAssetType == ledger.AssetCoin {

			incomeState = senderState
		}
		incomeAccount, err := r.applyTrackedAccountDelta(ctx, tx, incomeState, giftIncomeCoinAmount, 0, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		metadata.GiftIncomeBalanceAfter = incomeAccount.AvailableAmount
		if command.TargetUserID == command.SenderUserID && accountAssetType == ledger.AssetCoin {
			metadata.BalanceAfter = incomeAccount.AvailableAmount
		}
		if err := r.insertEntry(ctx, tx, walletEntry{
			TransactionID:      transactionID,
			UserID:             command.TargetUserID,
			AssetType:          ledger.AssetCoin,
			AvailableDelta:     giftIncomeCoinAmount,
			FrozenDelta:        0,
			AvailableAfter:     incomeAccount.AvailableAmount,
			FrozenAfter:        incomeAccount.FrozenAmount,
			CounterpartyUserID: command.SenderUserID,
			RoomID:             command.RoomID,
			CreatedAtMS:        nowMs,
		}); err != nil {
			return ledger.Receipt{}, err
		}
	}
	if hostPeriodDiamondAdded > 0 {

		hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, command.CommandID, metadata, nowMs)
		if err != nil {
			return ledger.Receipt{}, err
		}
		metadata.HostPeriodDiamondAfter = hostPeriodDiamondAfter
		metadata.HostPeriodDiamondVersion = hostPeriodDiamondVersion
	}

	if !command.RobotGift {

		if chargeSource != giftChargeSourceBag {
			if command.TargetUserID == command.SenderUserID && chargeAssetType == ledger.AssetCoin {
				events = append(events, balanceChangedEvent(transactionID, command.CommandID, command.SenderUserID, ledger.AssetCoin, giftIncomeCoinAmount-chargeAmount, 0, metadata.BalanceAfter, senderState.account.FrozenAmount, senderState.account.Version, metadata, nowMs, bizType))
			} else {
				events = append(events, balanceChangedEvent(transactionID, command.CommandID, command.SenderUserID, chargeAssetType, -chargeAmount, 0, senderState.account.AvailableAmount, senderState.account.FrozenAmount, senderState.account.Version, metadata, nowMs, bizType))
			}
		}
		if giftIncomeCoinAmount > 0 && !(command.TargetUserID == command.SenderUserID && chargeAssetType == ledger.AssetCoin && chargeSource != giftChargeSourceBag) {
			events = append(events, balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, giftIncomeCoinAmount, 0, metadata.GiftIncomeBalanceAfter, targetCoinState.account.FrozenAmount, targetCoinState.account.Version, metadata, nowMs, bizType))
		}
		events = append(events, giftDebitedEvent(transactionID, command.CommandID, command.SenderUserID, chargeAssetType, -chargeAmount, 0, metadata, nowMs))
		if giftIncomeCoinAmount > 0 {
			events = append(events, giftIncomeCreditedEvent(transactionID, command.CommandID, metadata, nowMs))
		}
	}
	if hostPeriodDiamondAdded > 0 {
		events = append(events, hostPeriodDiamondCreditedEvent(transactionID, command.CommandID, metadata, nowMs))
	}
	if err := r.insertWalletOutbox(ctx, tx, events); err != nil {
		return ledger.Receipt{}, err
	}
	if err := tx.Commit(); err != nil {
		return ledger.Receipt{}, err
	}

	return receiptFromGiftMetadata(transactionID, metadata), nil
}
