package wallet

import (
	"context"
	"math"
	"strconv"
	"strings"
	"time"

	domainwallet "github.com/break/junhong_cmp_fiber/internal/domain/wallet"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// RefundCommand 描述一次沿订单原扣款回溯的代理主钱包退款。
type RefundCommand struct {
	OrderID             uint
	RefundID            uint
	Amount              int64
	LegacyPayerShopID   uint
	LegacyDeductAmount  int64
	LegacyRelatedShopID *uint
	AssetType           string
	AssetID             uint
	AssetIdentifier     string
	UserID              uint
	Creator             uint
	Remark              string
	RequestID           string
	CorrelationID       string
}

// RefundResult 返回代理主钱包退款后的资金快照。
type RefundResult struct {
	WalletID       uint
	BalanceBefore  int64
	BalanceAfter   int64
	Version        int
	AlreadyApplied bool
}

// RefundedEvent 是代理主钱包订单退款成功后的可靠资金事实。
type RefundedEvent struct {
	EventID                    string    `json:"event_id"`
	WalletID                   uint      `json:"wallet_id"`
	ShopID                     uint      `json:"shop_id"`
	OrderID                    uint      `json:"order_id"`
	RefundID                   uint      `json:"refund_id"`
	Amount                     int64     `json:"amount"`
	BalanceBefore              int64     `json:"balance_before"`
	BalanceAfter               int64     `json:"balance_after"`
	Version                    int       `json:"version"`
	OriginalDebitTransactionID uint      `json:"original_debit_transaction_id,omitempty"`
	OriginalDeductAmount       int64     `json:"original_deduct_amount"`
	RelatedShopID              *uint     `json:"related_shop_id,omitempty"`
	TransactionSubtype         *string   `json:"transaction_subtype,omitempty"`
	AssetType                  string    `json:"asset_type,omitempty"`
	AssetID                    uint      `json:"asset_id,omitempty"`
	AssetIdentifier            string    `json:"asset_identifier,omitempty"`
	Legacy                     bool      `json:"legacy"`
	OccurredAt                 time.Time `json:"occurred_at"`
	RequestID                  string    `json:"request_id,omitempty"`
	CorrelationID              string    `json:"correlation_id,omitempty"`
}

// RefundEventWriter 在调用方事务内追加代理主钱包退款事件。
type RefundEventWriter interface {
	Append(ctx context.Context, tx *gorm.DB, event RefundedEvent) error
}

// RefundService 统一代理订单退款回充、真实流水和可靠事件。
type RefundService struct {
	eventWriter RefundEventWriter
	now         func() time.Time
}

// NewRefundService 创建统一代理主钱包退款服务。
func NewRefundService(eventWriter RefundEventWriter, now func() time.Time) *RefundService {
	if now == nil {
		now = time.Now
	}
	return &RefundService{eventWriter: eventWriter, now: now}
}

// RefundInTx 在调用方事务内按原扣款事实完成退款回充、版本递增、唯一流水和 Outbox。
func (s *RefundService) RefundInTx(ctx context.Context, tx *gorm.DB, command RefundCommand) (RefundResult, error) {
	if s == nil || s.eventWriter == nil || tx == nil {
		return RefundResult{}, errors.New(errors.CodeInternalError, "代理主钱包退款能力未完整配置")
	}
	if err := validateRefundCommand(command); err != nil {
		return RefundResult{}, err
	}

	origin, err := findOriginalOrderDebit(ctx, tx, command.OrderID)
	if err != nil {
		return RefundResult{}, err
	}
	resolution, err := resolveRefundTarget(command, origin)
	if err != nil {
		return RefundResult{}, err
	}
	stored, err := lockRefundWallet(ctx, tx, resolution)
	if err != nil {
		return RefundResult{}, err
	}

	existing, err := findExistingRefund(ctx, tx, command.RefundID)
	if err != nil {
		return RefundResult{}, err
	}
	if existing != nil {
		if existing.AgentWalletID != stored.ID || existing.Amount != command.Amount ||
			!sameOptionalUint(existing.RelatedShopID, resolution.relatedShopID) ||
			!sameOptionalString(existing.TransactionSubtype, resolution.subtype) ||
			existing.AssetType != resolution.assetType || existing.AssetID != resolution.assetID ||
			existing.AssetIdentifier != resolution.assetIdentifier {
			return RefundResult{}, errors.New(errors.CodeConflict, "退款单已存在不一致的钱包回充流水")
		}
		return RefundResult{
			WalletID: stored.ID, BalanceBefore: existing.BalanceBefore, BalanceAfter: existing.BalanceAfter,
			Version: stored.Version, AlreadyApplied: true,
		}, nil
	}

	aggregate := domainwallet.AgentWallet{
		ID: stored.ID, ShopID: stored.ShopID, WalletType: stored.WalletType,
		Balance: stored.Balance, FrozenBalance: stored.FrozenBalance,
		CreditEnabled: stored.CreditEnabled, CreditLimit: stored.CreditLimit,
		Status: stored.Status, Version: stored.Version,
	}
	if err := aggregate.Credit(command.Amount); err != nil {
		return RefundResult{}, err
	}
	now := s.now().UTC()
	update := tx.WithContext(ctx).Model(&model.AgentWallet{}).
		Where("id = ? AND wallet_type = ? AND status = ? AND version = ?",
			stored.ID, constants.AgentWalletTypeMain, constants.AgentWalletStatusNormal, stored.Version).
		Updates(map[string]any{"balance": aggregate.Balance, "version": gorm.Expr("version + 1"), "updated_at": now})
	if update.Error != nil {
		return RefundResult{}, errors.Wrap(errors.CodeDatabaseError, update.Error, "退回代理主钱包余额失败")
	}
	if update.RowsAffected != 1 {
		return RefundResult{}, errors.New(errors.CodeConflict, "钱包版本已变化，请重试")
	}

	transaction := buildRefundTransaction(stored, aggregate.Balance, command, resolution)
	if err := tx.WithContext(ctx).Create(transaction).Error; err != nil {
		return RefundResult{}, errors.Wrap(errors.CodeDatabaseError, err, "创建代理主钱包退款流水失败")
	}
	event := RefundedEvent{
		EventID:  "agent-wallet:refund:" + strconv.FormatUint(uint64(command.RefundID), 10) + ":refunded",
		WalletID: stored.ID, ShopID: stored.ShopID, OrderID: command.OrderID, RefundID: command.RefundID,
		Amount: command.Amount, BalanceBefore: stored.Balance, BalanceAfter: aggregate.Balance,
		Version: stored.Version + 1, OriginalDebitTransactionID: resolution.originalDebitID,
		OriginalDeductAmount: resolution.deductAmount,
		RelatedShopID:        resolution.relatedShopID, TransactionSubtype: resolution.subtype, Legacy: resolution.legacy,
		AssetType: resolution.assetType, AssetID: resolution.assetID, AssetIdentifier: resolution.assetIdentifier,
		OccurredAt: now, RequestID: command.RequestID, CorrelationID: command.CorrelationID,
	}
	if err := s.eventWriter.Append(ctx, tx, event); err != nil {
		return RefundResult{}, errors.Wrap(errors.CodeDatabaseError, err, "写入代理主钱包退款事件失败")
	}
	return RefundResult{
		WalletID: stored.ID, BalanceBefore: stored.Balance, BalanceAfter: aggregate.Balance,
		Version: stored.Version + 1,
	}, nil
}

type refundResolution struct {
	walletID        uint
	shopID          uint
	originalDebitID uint
	deductAmount    int64
	relatedShopID   *uint
	subtype         *string
	assetType       string
	assetID         uint
	assetIdentifier string
	legacy          bool
}

func validateRefundCommand(command RefundCommand) error {
	if command.OrderID == 0 || command.RefundID == 0 || command.Amount <= 0 {
		return errors.New(errors.CodeInvalidParam, "代理主钱包退款参数无效")
	}
	return nil
}

func findOriginalOrderDebit(ctx context.Context, tx *gorm.DB, orderID uint) (*model.AgentWalletTransaction, error) {
	var transaction model.AgentWalletTransaction
	err := tx.WithContext(ctx).Unscoped().
		Where("reference_type = ? AND reference_id = ? AND transaction_type = ? AND status = ?",
			constants.ReferenceTypeOrder, orderID, constants.AgentTransactionTypeDeduct, constants.TransactionStatusSuccess).
		Order("id ASC").First(&transaction).Error
	if err == nil {
		return &transaction, nil
	}
	if err == gorm.ErrRecordNotFound {
		return nil, nil
	}
	return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询原代理主钱包扣款流水失败")
}

func resolveRefundTarget(command RefundCommand, origin *model.AgentWalletTransaction) (refundResolution, error) {
	if origin != nil {
		if origin.Amount >= 0 || origin.Amount == math.MinInt64 {
			return refundResolution{}, errors.New(errors.CodeInternalError, "原代理主钱包扣款流水金额非法")
		}
		maxAmount := -origin.Amount
		if command.Amount > maxAmount {
			return refundResolution{}, errors.New(errors.CodeInvalidParam, "退款金额不能大于原钱包扣款金额")
		}
		return refundResolution{
			walletID: origin.AgentWalletID, originalDebitID: origin.ID, deductAmount: maxAmount,
			relatedShopID: origin.RelatedShopID, subtype: origin.TransactionSubtype,
			assetType: origin.AssetType, assetID: origin.AssetID, assetIdentifier: origin.AssetIdentifier,
		}, nil
	}
	if command.LegacyPayerShopID == 0 || command.LegacyDeductAmount <= 0 {
		return refundResolution{}, errors.New(errors.CodeInternalError, "历史订单缺少原扣款流水和兼容付款快照")
	}
	if command.Amount > command.LegacyDeductAmount {
		return refundResolution{}, errors.New(errors.CodeInvalidParam, "退款金额不能大于历史订单实付金额")
	}
	return refundResolution{
		shopID: command.LegacyPayerShopID, relatedShopID: command.LegacyRelatedShopID,
		deductAmount: command.LegacyDeductAmount,
		assetType:    command.AssetType, assetID: command.AssetID, assetIdentifier: command.AssetIdentifier,
		legacy: true,
	}, nil
}

func lockRefundWallet(ctx context.Context, tx *gorm.DB, resolution refundResolution) (model.AgentWallet, error) {
	var stored model.AgentWallet
	query := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("wallet_type = ?", constants.AgentWalletTypeMain)
	if resolution.walletID > 0 {
		query = query.Where("id = ?", resolution.walletID)
	} else {
		query = query.Where("shop_id = ?", resolution.shopID)
	}
	if err := query.First(&stored).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return model.AgentWallet{}, errors.New(errors.CodeWalletNotFound, "代理主钱包不存在")
		}
		return model.AgentWallet{}, errors.Wrap(errors.CodeDatabaseError, err, "锁定代理主钱包失败")
	}
	return stored, nil
}

func findExistingRefund(ctx context.Context, tx *gorm.DB, refundID uint) (*model.AgentWalletTransaction, error) {
	var transaction model.AgentWalletTransaction
	err := tx.WithContext(ctx).Unscoped().
		Where("reference_type = ? AND reference_id = ? AND transaction_type = ? AND status = ?",
			constants.ReferenceTypeRefund, refundID, constants.AgentTransactionTypeRefund, constants.TransactionStatusSuccess).
		First(&transaction).Error
	if err == nil {
		return &transaction, nil
	}
	if err == gorm.ErrRecordNotFound {
		return nil, nil
	}
	return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理主钱包退款流水失败")
}

func buildRefundTransaction(stored model.AgentWallet, balanceAfter int64, command RefundCommand, resolution refundResolution) *model.AgentWalletTransaction {
	referenceType := constants.ReferenceTypeRefund
	remark := strings.TrimSpace(command.Remark)
	return &model.AgentWalletTransaction{
		AgentWalletID: stored.ID, ShopID: stored.ShopID, UserID: command.UserID,
		TransactionType: constants.AgentTransactionTypeRefund, TransactionSubtype: resolution.subtype,
		Amount: command.Amount, BalanceBefore: stored.Balance, BalanceAfter: balanceAfter,
		Status: constants.TransactionStatusSuccess, ReferenceType: &referenceType, ReferenceID: &command.RefundID,
		RelatedShopID: resolution.relatedShopID, AssetType: resolution.assetType, AssetID: resolution.assetID,
		AssetIdentifier: resolution.assetIdentifier, Remark: &remark, Creator: command.Creator,
		ShopIDTag: stored.ShopIDTag, EnterpriseIDTag: stored.EnterpriseIDTag,
	}
}

func sameOptionalUint(left, right *uint) bool {
	return (left == nil && right == nil) || (left != nil && right != nil && *left == *right)
}

func sameOptionalString(left, right *string) bool {
	return (left == nil && right == nil) || (left != nil && right != nil && *left == *right)
}
