package services

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"quyun/v2/app/errorx"
	"quyun/v2/app/http/tenant/dto"
	"quyun/v2/app/requests"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	pkgerrors "github.com/pkg/errors"
	"github.com/samber/lo"
	"github.com/sirupsen/logrus"
	"go.ipao.vip/gen"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"go.ipao.vip/gen/types"
)

// PurchaseOrderSnapshot 为“内容购买订单”的下单快照（用于历史展示与争议审计）。
type PurchaseOrderSnapshot struct {
	// ContentID 内容ID。
	ContentID int64 `json:"content_id"`
	// ContentTitle 内容标题（下单时快照，避免事后改名影响历史订单展示）。
	ContentTitle string `json:"content_title"`
	// ContentUserID 内容作者用户ID（用于审计与后续分成扩展）。
	ContentUserID int64 `json:"content_user_id"`
	// ContentVisibility 下单时的可见性快照。
	ContentVisibility consts.ContentVisibility `json:"content_visibility"`
	// PreviewSeconds 下单时的试看秒数快照。
	PreviewSeconds int32 `json:"preview_seconds"`
	// PreviewDownloadable 下单时的试看是否可下载快照（当前固定为 false）。
	PreviewDownloadable bool `json:"preview_downloadable"`
	// Currency 币种：当前固定 CNY（金额单位为分）。
	Currency consts.Currency `json:"currency"`
	// PriceAmount 基础价格（分）。
	PriceAmount int64 `json:"price_amount"`
	// DiscountType 折扣类型（none/percent/amount）。
	DiscountType consts.DiscountType `json:"discount_type"`
	// DiscountValue 折扣值（percent=0-100；amount=分）。
	DiscountValue int64 `json:"discount_value"`
	// DiscountStartAt 折扣开始时间（可选）。
	DiscountStartAt *time.Time `json:"discount_start_at,omitempty"`
	// DiscountEndAt 折扣结束时间（可选）。
	DiscountEndAt *time.Time `json:"discount_end_at,omitempty"`
	// AmountOriginal 原价金额（分）。
	AmountOriginal int64 `json:"amount_original"`
	// AmountDiscount 优惠金额（分）。
	AmountDiscount int64 `json:"amount_discount"`
	// AmountPaid 实付金额（分）。
	AmountPaid int64 `json:"amount_paid"`
	// PurchaseAt 下单时间（逻辑时间）。
	PurchaseAt time.Time `json:"purchase_at"`
	// PurchaseIdempotency 幂等键（可选）。
	PurchaseIdempotency string `json:"purchase_idempotency_key,omitempty"`
	// PurchasePricingNotes 价格计算补充说明（可选，便于排查争议）。
	PurchasePricingNotes string `json:"purchase_pricing_notes,omitempty"`
}

// OrderItemSnapshot 为“订单明细”的内容快照。
type OrderItemSnapshot struct {
	// ContentID 内容ID。
	ContentID int64 `json:"content_id"`
	// ContentTitle 内容标题快照。
	ContentTitle string `json:"content_title"`
	// ContentUserID 内容作者用户ID。
	ContentUserID int64 `json:"content_user_id"`
	// AmountPaid 该行实付金额（分）。
	AmountPaid int64 `json:"amount_paid"`
}

// TopupOrderSnapshot 为“后台充值订单”的快照（用于审计与追责）。
type TopupOrderSnapshot struct {
	// OperatorUserID 充值操作人用户ID（租户管理员）。
	OperatorUserID int64 `json:"operator_user_id"`
	// TargetUserID 充值目标用户ID（租户成员）。
	TargetUserID int64 `json:"target_user_id"`
	// Amount 充值金额（分）。
	Amount int64 `json:"amount"`
	// Currency 币种：当前固定 CNY（金额单位为分）。
	Currency consts.Currency `json:"currency"`
	// Reason 充值原因（可选，强烈建议填写用于审计）。
	Reason string `json:"reason,omitempty"`
	// IdempotencyKey 幂等键（可选）。
	IdempotencyKey string `json:"idempotency_key,omitempty"`
	// TopupAt 充值时间（逻辑时间）。
	TopupAt time.Time `json:"topup_at"`
}

// PurchaseContentParams 定义“租户内使用余额购买内容”的入参。
type PurchaseContentParams struct {
	// TenantID 租户 ID（多租户隔离范围）。
	TenantID int64
	// UserID 购买者用户 ID。
	UserID int64
	// ContentID 内容 ID。
	ContentID int64
	// IdempotencyKey 幂等键：用于确保同一购买请求“至多处理一次”。
	IdempotencyKey string
	// Now 逻辑时间：用于 created_at/paid_at 与账本快照（可选，便于测试/一致性）。
	Now time.Time
}

// PurchaseContentResult 为购买结果（幂等命中时返回已存在的订单/权益状态）。
type PurchaseContentResult struct {
	// Order 订单记录（可能为 nil：例如“已购买且无订单上下文”的快捷路径）。
	Order *models.Order
	// OrderItem 订单明细（本业务为单内容购买，通常只有 1 条）。
	OrderItem *models.OrderItem
	// Access 内容权益（购买完成后应为 active）。
	Access *models.ContentAccess
	// AmountPaid 实付金额（单位：分，CNY）。
	AmountPaid int64
}

// order 提供订单域能力（购买、充值、退款、查询等）。
//
//	@provider
type order struct {
	db     *gorm.DB
	ledger *ledger
}

func marshalSnapshot(v any) types.JSON {
	b, err := json.Marshal(v)
	if err != nil {
		return types.JSON([]byte("{}"))
	}
	if len(b) == 0 {
		return types.JSON([]byte("{}"))
	}
	return types.JSON(b)
}

// AdminTopupUser 租户管理员给租户成员充值（增加该租户下的可用余额）。
func (s *order) AdminTopupUser(
	ctx context.Context,
	tenantID, operatorUserID, targetUserID, amount int64,
	idempotencyKey, reason string,
	now time.Time,
) (*models.Order, error) {
	if tenantID <= 0 || operatorUserID <= 0 || targetUserID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/operator_user_id/target_user_id must be > 0")
	}
	if amount <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("amount must be > 0")
	}
	if now.IsZero() {
		now = time.Now()
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":       tenantID,
		"operator_user":   operatorUserID,
		"target_user":     targetUserID,
		"amount":          amount,
		"idempotency_key": idempotencyKey,
	}).Info("services.order.admin.topup_user")

	var out models.Order

	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 关键前置条件：目标用户必须属于该租户（同时加行锁，避免并发余额写入冲突）。
		var tu models.TenantUser
		if err := tx.
			Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("tenant_id = ? AND user_id = ?", tenantID, targetUserID).
			First(&tu).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errorx.ErrPreconditionFailed.WithMsg("目标用户不属于该租户")
			}
			return err
		}

		// 充值幂等：按 orders(tenant_id,user_id,idempotency_key) 去重，避免重复入账。
		if idempotencyKey != "" {
			var existing models.Order
			if err := tx.Where(
				"tenant_id = ? AND user_id = ? AND idempotency_key = ?",
				tenantID, targetUserID, idempotencyKey,
			).First(&existing).Error; err == nil {
				out = existing
				return nil
			} else if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
		}

		// 先落订单（paid），再写入账本（credit_topup），确保“订单可追溯 + 账本可对账”。
		snapshot := marshalSnapshot(&TopupOrderSnapshot{
			OperatorUserID: operatorUserID,
			TargetUserID:   targetUserID,
			Amount:         amount,
			Currency:       consts.CurrencyCNY,
			Reason:         reason,
			IdempotencyKey: idempotencyKey,
			TopupAt:        now,
		})
		orderModel := models.Order{
			TenantID:       tenantID,
			UserID:         targetUserID,
			Type:           consts.OrderTypeTopup,
			Status:         consts.OrderStatusPaid,
			Currency:       consts.CurrencyCNY,
			AmountOriginal: amount,
			AmountDiscount: 0,
			AmountPaid:     amount,
			Snapshot:       snapshot,
			IdempotencyKey: idempotencyKey,
			PaidAt:         now,
			CreatedAt:      now,
			UpdatedAt:      now,
		}
		if err := tx.Create(&orderModel).Error; err != nil {
			return err
		}

		// 账本幂等键固定使用 topup:<orderID>，保证同一订单不会重复入账。
		ledgerKey := fmt.Sprintf("topup:%d", orderModel.ID)
		remark := reason
		if remark == "" {
			remark = fmt.Sprintf("topup by tenant_admin:%d", operatorUserID)
		}
		if _, err := s.ledger.CreditTopupTx(ctx, tx, tenantID, targetUserID, orderModel.ID, amount, ledgerKey, remark, now); err != nil {
			return err
		}

		out = orderModel
		return nil
	})
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"tenant_id":       tenantID,
			"operator_user":   operatorUserID,
			"target_user":     targetUserID,
			"amount":          amount,
			"idempotency_key": idempotencyKey,
		}).WithError(err).Warn("services.order.admin.topup_user.failed")
		return nil, err
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":   tenantID,
		"target_user": targetUserID,
		"order_id":    out.ID,
		"amount":      amount,
	}).Info("services.order.admin.topup_user.ok")

	return &out, nil
}

// MyOrderPage 分页查询当前用户在租户内的订单。
func (s *order) MyOrderPage(
	ctx context.Context,
	tenantID, userID int64,
	filter *dto.MyOrderListFilter,
) (*requests.Pager, error) {
	if tenantID <= 0 || userID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/user_id must be > 0")
	}
	if filter == nil {
		filter = &dto.MyOrderListFilter{}
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":  tenantID,
		"user_id":    userID,
		"status":     lo.FromPtr(filter.Status),
		"content_id": lo.FromPtr(filter.ContentID),
	}).Info("services.order.me.page")

	filter.Pagination.Format()

	tbl, query := models.OrderQuery.QueryContext(ctx)
	query = query.Preload(tbl.Items)

	conds := []gen.Condition{
		tbl.TenantID.Eq(tenantID),
		tbl.UserID.Eq(userID),
	}
	if filter.Status != nil {
		conds = append(conds, tbl.Status.Eq(*filter.Status))
	}
	if filter.PaidAtFrom != nil {
		conds = append(conds, tbl.PaidAt.Gte(*filter.PaidAtFrom))
	}
	if filter.PaidAtTo != nil {
		conds = append(conds, tbl.PaidAt.Lte(*filter.PaidAtTo))
	}
	if filter.ContentID != nil && *filter.ContentID > 0 {
		oiTbl, _ := models.OrderItemQuery.QueryContext(ctx)
		query = query.LeftJoin(oiTbl, oiTbl.OrderID.EqCol(tbl.ID))
		conds = append(conds, oiTbl.ContentID.Eq(*filter.ContentID))
		query = query.Group(tbl.ID)
	}

	items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
	if err != nil {
		return nil, err
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

// MyOrderDetail 查询当前用户在租户内的订单详情。
func (s *order) MyOrderDetail(ctx context.Context, tenantID, userID, orderID int64) (*models.Order, error) {
	if tenantID <= 0 || userID <= 0 || orderID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/user_id/order_id must be > 0")
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id": tenantID,
		"user_id":   userID,
		"order_id":  orderID,
	}).Info("services.order.me.detail")

	tbl, query := models.OrderQuery.QueryContext(ctx)
	m, err := query.Preload(tbl.Items).Where(
		tbl.TenantID.Eq(tenantID),
		tbl.UserID.Eq(userID),
		tbl.ID.Eq(orderID),
	).First()
	if err != nil {
		return nil, err
	}
	return m, nil
}

// AdminOrderPage 租户管理员分页查询租户内订单。
func (s *order) AdminOrderPage(
	ctx context.Context,
	tenantID int64,
	filter *dto.AdminOrderListFilter,
) (*requests.Pager, error) {
	if tenantID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id must be > 0")
	}
	if filter == nil {
		filter = &dto.AdminOrderListFilter{}
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":  tenantID,
		"user_id":    lo.FromPtr(filter.UserID),
		"status":     lo.FromPtr(filter.Status),
		"content_id": lo.FromPtr(filter.ContentID),
	}).Info("services.order.admin.page")

	filter.Pagination.Format()

	tbl, query := models.OrderQuery.QueryContext(ctx)
	query = query.Preload(tbl.Items)

	conds := []gen.Condition{tbl.TenantID.Eq(tenantID)}
	if filter.UserID != nil {
		conds = append(conds, tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Status != nil {
		conds = append(conds, tbl.Status.Eq(*filter.Status))
	}
	if filter.PaidAtFrom != nil {
		conds = append(conds, tbl.PaidAt.Gte(*filter.PaidAtFrom))
	}
	if filter.PaidAtTo != nil {
		conds = append(conds, tbl.PaidAt.Lte(*filter.PaidAtTo))
	}
	if filter.AmountPaidMin != nil {
		conds = append(conds, tbl.AmountPaid.Gte(*filter.AmountPaidMin))
	}
	if filter.AmountPaidMax != nil {
		conds = append(conds, tbl.AmountPaid.Lte(*filter.AmountPaidMax))
	}
	if filter.ContentID != nil && *filter.ContentID > 0 {
		oiTbl, _ := models.OrderItemQuery.QueryContext(ctx)
		query = query.LeftJoin(oiTbl, oiTbl.OrderID.EqCol(tbl.ID))
		conds = append(conds, oiTbl.ContentID.Eq(*filter.ContentID))
		query = query.Group(tbl.ID)
	}

	items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
	if err != nil {
		return nil, err
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

// AdminOrderDetail 租户管理员查询租户内订单详情。
func (s *order) AdminOrderDetail(ctx context.Context, tenantID, orderID int64) (*models.Order, error) {
	if tenantID <= 0 || orderID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/order_id must be > 0")
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id": tenantID,
		"order_id":  orderID,
	}).Info("services.order.admin.detail")

	tbl, query := models.OrderQuery.QueryContext(ctx)
	m, err := query.Preload(tbl.Items).Where(tbl.TenantID.Eq(tenantID), tbl.ID.Eq(orderID)).First()
	if err != nil {
		return nil, err
	}
	return m, nil
}

// AdminRefundOrder 退款已支付订单（支持强制退款），并立即回收已授予的内容权益。
func (s *order) AdminRefundOrder(
	ctx context.Context,
	tenantID, operatorUserID, orderID int64,
	force bool,
	reason, idempotencyKey string,
	now time.Time,
) (*models.Order, error) {
	if tenantID <= 0 || operatorUserID <= 0 || orderID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/operator_user_id/order_id must be > 0")
	}
	if now.IsZero() {
		now = time.Now()
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":        tenantID,
		"operator_user_id": operatorUserID,
		"order_id":         orderID,
		"force":            force,
		"idempotency_key":  idempotencyKey,
	}).Info("services.order.admin.refund")

	var out *models.Order

	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 行锁锁住订单，避免并发退款/重复退款导致状态错乱。
		var orderModel models.Order
		if err := tx.
			Clauses(clause.Locking{Strength: "UPDATE"}).
			Preload("Items").
			Where("tenant_id = ? AND id = ?", tenantID, orderID).
			First(&orderModel).Error; err != nil {
			return err
		}

		// 状态机：已退款直接幂等返回；仅允许已支付订单退款。
		if orderModel.Status == consts.OrderStatusRefunded {
			out = &orderModel
			return nil
		}
		if orderModel.Status != consts.OrderStatusPaid {
			return errorx.ErrStatusConflict.WithMsg("订单非已支付状态，无法退款")
		}
		if orderModel.PaidAt.IsZero() {
			return errorx.ErrPreconditionFailed.WithMsg("订单缺少 paid_at，无法退款")
		}

		// 时间窗：默认 paid_at + 24h；force=true 可绕过。
		if !force {
			deadline := orderModel.PaidAt.Add(consts.DefaultOrderRefundWindow)
			if now.After(deadline) {
				return errorx.ErrPreconditionFailed.WithMsg("已超过默认退款时间窗")
			}
		}

		amount := orderModel.AmountPaid
		refundKey := fmt.Sprintf("refund:%d", orderModel.ID)

		// 先退余额（账本入账），后更新订单状态与权益，确保退款可对账且可追溯。
		if amount > 0 {
			if _, err := s.ledger.CreditRefundTx(ctx, tx, tenantID, orderModel.UserID, orderModel.ID, amount, refundKey, reason, now); err != nil {
				return err
			}
		}

		// 退款对权益：立即回收 content_access（revoked）。
		for _, item := range orderModel.Items {
			if item == nil {
				continue
			}
			if err := tx.Table(models.TableNameContentAccess).
				Where("tenant_id = ? AND user_id = ? AND content_id = ?", tenantID, orderModel.UserID, item.ContentID).
				Updates(map[string]any{
					"status":     consts.ContentAccessStatusRevoked,
					"revoked_at": now,
					"updated_at": now,
				}).Error; err != nil {
				return err
			}
		}

		// 最后更新订单退款字段，保证退款后的最终状态一致。
		if err := tx.Table(models.TableNameOrder).
			Where("id = ?", orderModel.ID).
			Updates(map[string]any{
				"status":                  consts.OrderStatusRefunded,
				"refunded_at":             now,
				"refund_forced":           force,
				"refund_operator_user_id": operatorUserID,
				"refund_reason":           reason,
				"updated_at":              now,
			}).Error; err != nil {
			return err
		}

		orderModel.Status = consts.OrderStatusRefunded
		orderModel.RefundedAt = now
		orderModel.RefundForced = force
		orderModel.RefundOperatorUserID = operatorUserID
		orderModel.RefundReason = reason
		orderModel.UpdatedAt = now

		out = &orderModel
		return nil
	})
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"tenant_id":        tenantID,
			"operator_user_id": operatorUserID,
			"order_id":         orderID,
			"force":            force,
			"idempotency_key":  idempotencyKey,
		}).WithError(err).Warn("services.order.admin.refund.failed")
		return nil, err
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":        tenantID,
		"operator_user_id": operatorUserID,
		"order_id":         orderID,
		"status":           out.Status,
		"refund_forced":    out.RefundForced,
	}).Info("services.order.admin.refund.ok")

	return out, nil
}

func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentParams) (*PurchaseContentResult, error) {
	if params == nil {
		return nil, errorx.ErrInvalidParameter.WithMsg("params is required")
	}
	if params.TenantID <= 0 || params.UserID <= 0 || params.ContentID <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/user_id/content_id must be > 0")
	}

	now := params.Now
	if now.IsZero() {
		now = time.Now()
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":       params.TenantID,
		"user_id":         params.UserID,
		"content_id":      params.ContentID,
		"idempotency_key": params.IdempotencyKey,
	}).Info("services.order.purchase_content")

	var out PurchaseContentResult

	// 幂等购买采用“三段式”流程，保证一致性：
	// 1) 先独立事务冻结余额（预留资金）；
	// 2) 再用单事务写订单+扣款+授予权益；
	// 3) 若第 2 步失败，则解冻并写入回滚标记，保证重试稳定返回“失败+已回滚”。
	if params.IdempotencyKey != "" {
		freezeKey := fmt.Sprintf("%s:freeze", params.IdempotencyKey)
		debitKey := fmt.Sprintf("%s:debit", params.IdempotencyKey)
		rollbackKey := fmt.Sprintf("%s:rollback", params.IdempotencyKey)

		// 1) 若该幂等键已生成订单，则直接返回订单与权益（幂等命中）。
		{
			tbl, query := models.OrderQuery.QueryContext(ctx)
			existing, err := query.Preload(tbl.Items).Where(
				tbl.TenantID.Eq(params.TenantID),
				tbl.UserID.Eq(params.UserID),
				tbl.IdempotencyKey.Eq(params.IdempotencyKey),
			).First()
			if err == nil {
				out.Order = existing
				if len(existing.Items) > 0 {
					out.OrderItem = existing.Items[0]
				}
				out.AmountPaid = existing.AmountPaid
				if out.OrderItem != nil {
					aTbl, aQuery := models.ContentAccessQuery.QueryContext(ctx)
					access, err := aQuery.Where(
						aTbl.TenantID.Eq(params.TenantID),
						aTbl.UserID.Eq(params.UserID),
						aTbl.ContentID.Eq(out.OrderItem.ContentID),
					).First()
					if err == nil {
						out.Access = access
					}
				}
				return &out, nil
			}
			if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return nil, err
			}
		}

		// 2) 若历史已回滚过该幂等请求，则稳定返回“失败+已回滚”（避免重复冻结/重复扣款）。
		{
			tbl, query := models.TenantLedgerQuery.QueryContext(ctx)
			_, err := query.Where(
				tbl.TenantID.Eq(params.TenantID),
				tbl.UserID.Eq(params.UserID),
				tbl.IdempotencyKey.Eq(rollbackKey),
			).First()
			if err == nil {
				return nil, errorx.ErrOperationFailed.WithMsg("失败+已回滚")
			}
			if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return nil, err
			}
		}

		// 查询内容与价格：放在事务外简化逻辑；后续以订单事务为准。
		var content models.Content
		{
			tbl, query := models.ContentQuery.QueryContext(ctx)
			m, err := query.Where(
				tbl.TenantID.Eq(params.TenantID),
				tbl.ID.Eq(params.ContentID),
				tbl.DeletedAt.IsNull(),
			).First()
			if err != nil {
				return nil, err
			}
			content = *m
		}
		if content.Status != consts.ContentStatusPublished {
			return nil, errorx.ErrPreconditionFailed.WithMsg("content not published")
		}

		// 作者自购：直接授予权益（不走余额冻结/扣款）。
		if content.UserID == params.UserID {
			err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
				if err := s.grantAccess(ctx, tx, params.TenantID, params.UserID, params.ContentID, 0, now); err != nil {
					return err
				}
				var access models.ContentAccess
				if err := tx.Where("tenant_id = ? AND user_id = ? AND content_id = ?", params.TenantID, params.UserID, params.ContentID).First(&access).Error; err != nil {
					return err
				}
				out.AmountPaid = 0
				out.Access = &access
				return nil
			})
			if err != nil {
				return nil, err
			}
			return &out, nil
		}

		priceAmount := int64(0)
		var price models.ContentPrice
		{
			tbl, query := models.ContentPriceQuery.QueryContext(ctx)
			m, err := query.Where(
				tbl.TenantID.Eq(params.TenantID),
				tbl.ContentID.Eq(params.ContentID),
			).First()
			if err == nil {
				price = *m
				priceAmount = m.PriceAmount
			} else if !errors.Is(err, gorm.ErrRecordNotFound) {
				return nil, err
			}
		}

		amountPaid := s.computeFinalPrice(priceAmount, &price, now)
		out.AmountPaid = amountPaid

		discountType := price.DiscountType
		if discountType == "" {
			discountType = consts.DiscountTypeNone
		}
		var discountStartAt *time.Time
		if !price.DiscountStartAt.IsZero() {
			t := price.DiscountStartAt
			discountStartAt = &t
		}
		var discountEndAt *time.Time
		if !price.DiscountEndAt.IsZero() {
			t := price.DiscountEndAt
			discountEndAt = &t
		}

		purchaseSnapshot := marshalSnapshot(&PurchaseOrderSnapshot{
			ContentID:           content.ID,
			ContentTitle:        content.Title,
			ContentUserID:       content.UserID,
			ContentVisibility:   content.Visibility,
			PreviewSeconds:      content.PreviewSeconds,
			PreviewDownloadable: content.PreviewDownloadable,
			Currency:            consts.CurrencyCNY,
			PriceAmount:         priceAmount,
			DiscountType:        discountType,
			DiscountValue:       price.DiscountValue,
			DiscountStartAt:     discountStartAt,
			DiscountEndAt:       discountEndAt,
			AmountOriginal:      priceAmount,
			AmountDiscount:      priceAmount - amountPaid,
			AmountPaid:          amountPaid,
			PurchaseAt:          now,
			PurchaseIdempotency: params.IdempotencyKey,
		})
		itemSnapshot := marshalSnapshot(&OrderItemSnapshot{
			ContentID:     content.ID,
			ContentTitle:  content.Title,
			ContentUserID: content.UserID,
			AmountPaid:    amountPaid,
		})

		// 免费内容：无需冻结，保持单事务写订单+权益。
		if amountPaid == 0 {
			err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
				orderModel := &models.Order{
					TenantID:       params.TenantID,
					UserID:         params.UserID,
					Type:           consts.OrderTypeContentPurchase,
					Status:         consts.OrderStatusPaid,
					Currency:       consts.CurrencyCNY,
					AmountOriginal: priceAmount,
					AmountDiscount: priceAmount - amountPaid,
					AmountPaid:     amountPaid,
					Snapshot:       purchaseSnapshot,
					IdempotencyKey: params.IdempotencyKey,
					PaidAt:         now,
					CreatedAt:      now,
					UpdatedAt:      now,
				}
				if err := tx.Create(orderModel).Error; err != nil {
					return err
				}
				item := &models.OrderItem{
					TenantID:      params.TenantID,
					UserID:        params.UserID,
					OrderID:       orderModel.ID,
					ContentID:     params.ContentID,
					ContentUserID: content.UserID,
					AmountPaid:    amountPaid,
					Snapshot:      itemSnapshot,
					CreatedAt:     now,
					UpdatedAt:     now,
				}
				if err := tx.Create(item).Error; err != nil {
					return err
				}
				if err := s.grantAccess(ctx, tx, params.TenantID, params.UserID, params.ContentID, orderModel.ID, now); err != nil {
					return err
				}
				var access models.ContentAccess
				if err := tx.Where("tenant_id = ? AND user_id = ? AND content_id = ?", params.TenantID, params.UserID, params.ContentID).First(&access).Error; err != nil {
					return err
				}
				out.Order = orderModel
				out.OrderItem = item
				out.Access = &access
				return nil
			})
			if err != nil {
				return nil, pkgerrors.Wrap(err, "purchase content failed")
			}
			return &out, nil
		}

		// 3) 独立事务冻结余额：便于后续在订单事务失败时做补偿解冻。
		if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
			_, err := s.ledger.FreezeTx(ctx, tx, params.TenantID, params.UserID, 0, amountPaid, freezeKey, "purchase freeze", now)
			return err
		}); err != nil {
			return nil, pkgerrors.Wrap(err, "purchase freeze failed")
		}

		// 4) 单事务完成：落订单 → 账本扣款（消耗冻结）→ 更新订单 paid → 授予权益。
		if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
			orderModel := &models.Order{
				TenantID:       params.TenantID,
				UserID:         params.UserID,
				Type:           consts.OrderTypeContentPurchase,
				Status:         consts.OrderStatusCreated,
				Currency:       consts.CurrencyCNY,
				AmountOriginal: priceAmount,
				AmountDiscount: priceAmount - amountPaid,
				AmountPaid:     amountPaid,
				Snapshot:       purchaseSnapshot,
				IdempotencyKey: params.IdempotencyKey,
				CreatedAt:      now,
				UpdatedAt:      now,
			}
			if err := tx.Create(orderModel).Error; err != nil {
				return err
			}
			item := &models.OrderItem{
				TenantID:      params.TenantID,
				UserID:        params.UserID,
				OrderID:       orderModel.ID,
				ContentID:     params.ContentID,
				ContentUserID: content.UserID,
				AmountPaid:    amountPaid,
				Snapshot:      itemSnapshot,
				CreatedAt:     now,
				UpdatedAt:     now,
			}
			if err := tx.Create(item).Error; err != nil {
				return err
			}
			if _, err := s.ledger.DebitPurchaseTx(ctx, tx, params.TenantID, params.UserID, orderModel.ID, amountPaid, debitKey, "purchase debit", now); err != nil {
				return err
			}
			if err := tx.Model(&models.Order{}).
				Where("id = ?", orderModel.ID).
				Updates(map[string]any{
					"status":     consts.OrderStatusPaid,
					"paid_at":    now,
					"updated_at": now,
				}).Error; err != nil {
				return err
			}
			if err := s.grantAccess(ctx, tx, params.TenantID, params.UserID, params.ContentID, orderModel.ID, now); err != nil {
				return err
			}

			var access models.ContentAccess
			if err := tx.Where("tenant_id = ? AND user_id = ? AND content_id = ?", params.TenantID, params.UserID, params.ContentID).First(&access).Error; err != nil {
				return err
			}

			out.Order = orderModel
			out.OrderItem = item
			out.Access = &access
			return nil
		}); err != nil {
			// 5) 补偿：订单事务失败时，必须解冻，并写入回滚标记，保证后续幂等重试稳定返回失败。
			_ = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
				_, e1 := s.ledger.UnfreezeTx(
					ctx,
					tx,
					params.TenantID,
					params.UserID,
					0,
					amountPaid,
					rollbackKey,
					"purchase rollback",
					now,
				)
				return e1
			})
			logrus.WithFields(logrus.Fields{
				"tenant_id":       params.TenantID,
				"user_id":         params.UserID,
				"content_id":      params.ContentID,
				"idempotency_key": params.IdempotencyKey,
			}).WithError(err).Warn("services.order.purchase_content.rollback")
			return nil, errorx.ErrOperationFailed.WithMsg("失败+已回滚")
		}

		logrus.WithFields(logrus.Fields{
			"tenant_id":       params.TenantID,
			"user_id":         params.UserID,
			"content_id":      params.ContentID,
			"order_id":        loID(out.Order),
			"amount_paid":     out.AmountPaid,
			"idempotency_key": params.IdempotencyKey,
		}).Info("services.order.purchase_content.ok")

		return &out, nil
	}

	// 非幂等请求走“单事务”旧流程：冻结 + 落单 + 扣款 + 授权全部在一个事务内完成（失败整体回滚）。
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var content models.Content
		if err := tx.
			Where("tenant_id = ? AND id = ? AND deleted_at IS NULL", params.TenantID, params.ContentID).
			First(&content).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errorx.ErrRecordNotFound.WithMsg("content not found")
			}
			return err
		}
		if content.Status != consts.ContentStatusPublished {
			return errorx.ErrPreconditionFailed.WithMsg("content not published")
		}

		var accessExisting models.ContentAccess
		if err := tx.
			Where("tenant_id = ? AND user_id = ? AND content_id = ?", params.TenantID, params.UserID, params.ContentID).
			First(&accessExisting).Error; err == nil {
			if accessExisting.Status == consts.ContentAccessStatusActive {
				out.Access = &accessExisting
				return nil
			}
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}

		var price models.ContentPrice
		priceAmount := int64(0)
		if err := tx.Where("tenant_id = ? AND content_id = ?", params.TenantID, params.ContentID).First(&price).Error; err == nil {
			priceAmount = price.PriceAmount
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}

		amountPaid := s.computeFinalPrice(priceAmount, &price, now)
		out.AmountPaid = amountPaid

		discountType := price.DiscountType
		if discountType == "" {
			discountType = consts.DiscountTypeNone
		}
		var discountStartAt *time.Time
		if !price.DiscountStartAt.IsZero() {
			t := price.DiscountStartAt
			discountStartAt = &t
		}
		var discountEndAt *time.Time
		if !price.DiscountEndAt.IsZero() {
			t := price.DiscountEndAt
			discountEndAt = &t
		}
		purchaseSnapshot := marshalSnapshot(&PurchaseOrderSnapshot{
			ContentID:           content.ID,
			ContentTitle:        content.Title,
			ContentUserID:       content.UserID,
			ContentVisibility:   content.Visibility,
			PreviewSeconds:      content.PreviewSeconds,
			PreviewDownloadable: content.PreviewDownloadable,
			Currency:            consts.CurrencyCNY,
			PriceAmount:         priceAmount,
			DiscountType:        discountType,
			DiscountValue:       price.DiscountValue,
			DiscountStartAt:     discountStartAt,
			DiscountEndAt:       discountEndAt,
			AmountOriginal:      priceAmount,
			AmountDiscount:      priceAmount - amountPaid,
			AmountPaid:          amountPaid,
			PurchaseAt:          now,
		})
		itemSnapshot := marshalSnapshot(&OrderItemSnapshot{
			ContentID:     content.ID,
			ContentTitle:  content.Title,
			ContentUserID: content.UserID,
			AmountPaid:    amountPaid,
		})

		if amountPaid == 0 {
			orderModel := &models.Order{
				TenantID:       params.TenantID,
				UserID:         params.UserID,
				Type:           consts.OrderTypeContentPurchase,
				Status:         consts.OrderStatusPaid,
				Currency:       consts.CurrencyCNY,
				AmountOriginal: priceAmount,
				AmountDiscount: priceAmount - amountPaid,
				AmountPaid:     amountPaid,
				Snapshot:       purchaseSnapshot,
				IdempotencyKey: "",
				PaidAt:         now,
				CreatedAt:      now,
				UpdatedAt:      now,
			}
			if err := tx.Create(orderModel).Error; err != nil {
				return err
			}
			item := &models.OrderItem{
				TenantID:      params.TenantID,
				UserID:        params.UserID,
				OrderID:       orderModel.ID,
				ContentID:     params.ContentID,
				ContentUserID: content.UserID,
				AmountPaid:    amountPaid,
				Snapshot:      itemSnapshot,
				CreatedAt:     now,
				UpdatedAt:     now,
			}
			if err := tx.Create(item).Error; err != nil {
				return err
			}
			if err := s.grantAccess(ctx, tx, params.TenantID, params.UserID, params.ContentID, orderModel.ID, now); err != nil {
				return err
			}
			var access models.ContentAccess
			if err := tx.Where("tenant_id = ? AND user_id = ? AND content_id = ?", params.TenantID, params.UserID, params.ContentID).First(&access).Error; err != nil {
				return err
			}
			out.Order = orderModel
			out.OrderItem = item
			out.Access = &access
			return nil
		}

		orderModel := &models.Order{
			TenantID:       params.TenantID,
			UserID:         params.UserID,
			Type:           consts.OrderTypeContentPurchase,
			Status:         consts.OrderStatusCreated,
			Currency:       consts.CurrencyCNY,
			AmountOriginal: priceAmount,
			AmountDiscount: priceAmount - amountPaid,
			AmountPaid:     amountPaid,
			Snapshot:       purchaseSnapshot,
			IdempotencyKey: "",
			CreatedAt:      now,
			UpdatedAt:      now,
		}

		if _, err := s.ledger.FreezeTx(ctx, tx, params.TenantID, params.UserID, 0, amountPaid, "", "purchase freeze", now); err != nil {
			return err
		}

		if err := tx.Create(orderModel).Error; err != nil {
			return err
		}

		item := &models.OrderItem{
			TenantID:      params.TenantID,
			UserID:        params.UserID,
			OrderID:       orderModel.ID,
			ContentID:     params.ContentID,
			ContentUserID: content.UserID,
			AmountPaid:    amountPaid,
			Snapshot:      itemSnapshot,
			CreatedAt:     now,
			UpdatedAt:     now,
		}
		if err := tx.Create(item).Error; err != nil {
			return err
		}

		if _, err := s.ledger.DebitPurchaseTx(ctx, tx, params.TenantID, params.UserID, orderModel.ID, amountPaid, "", "purchase debit", now); err != nil {
			return err
		}

		if err := tx.Model(&models.Order{}).
			Where("id = ?", orderModel.ID).
			Updates(map[string]any{
				"status":     consts.OrderStatusPaid,
				"paid_at":    now,
				"updated_at": now,
			}).Error; err != nil {
			return err
		}

		if err := s.grantAccess(ctx, tx, params.TenantID, params.UserID, params.ContentID, orderModel.ID, now); err != nil {
			return err
		}

		var access models.ContentAccess
		if err := tx.Where("tenant_id = ? AND user_id = ? AND content_id = ?", params.TenantID, params.UserID, params.ContentID).First(&access).Error; err != nil {
			return err
		}

		out.Order = orderModel
		out.OrderItem = item
		out.Access = &access
		return nil
	})
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"tenant_id":       params.TenantID,
			"user_id":         params.UserID,
			"content_id":      params.ContentID,
			"idempotency_key": params.IdempotencyKey,
		}).WithError(err).Warn("services.order.purchase_content.failed")
		return nil, pkgerrors.Wrap(err, "purchase content failed")
	}

	logrus.WithFields(logrus.Fields{
		"tenant_id":       params.TenantID,
		"user_id":         params.UserID,
		"content_id":      params.ContentID,
		"order_id":        loID(out.Order),
		"amount_paid":     out.AmountPaid,
		"idempotency_key": params.IdempotencyKey,
	}).Info("services.order.purchase_content.ok")

	return &out, nil
}

func (s *order) computeFinalPrice(priceAmount int64, price *models.ContentPrice, now time.Time) int64 {
	// 价格计算：按折扣策略与生效时间窗口计算最终实付金额（单位：分）。
	if priceAmount <= 0 || price == nil {
		return 0
	}

	discountType := price.DiscountType
	if discountType == "" {
		discountType = consts.DiscountTypeNone
	}

	if !price.DiscountStartAt.IsZero() && now.Before(price.DiscountStartAt) {
		return priceAmount
	}
	if !price.DiscountEndAt.IsZero() && now.After(price.DiscountEndAt) {
		return priceAmount
	}

	switch discountType {
	case consts.DiscountTypePercent:
		percent := price.DiscountValue
		if percent <= 0 {
			return priceAmount
		}
		if percent >= 100 {
			return 0
		}
		return priceAmount * (100 - percent) / 100
	case consts.DiscountTypeAmount:
		amount := price.DiscountValue
		if amount <= 0 {
			return priceAmount
		}
		if amount >= priceAmount {
			return 0
		}
		return priceAmount - amount
	default:
		return priceAmount
	}
}

func (s *order) grantAccess(
	ctx context.Context,
	tx *gorm.DB,
	tenantID, userID, contentID, orderID int64,
	now time.Time,
) error {
	// 权益写入策略：按 (tenant_id,user_id,content_id) upsert，确保重复购买/重试时权益最终为 active。
	insert := map[string]any{
		"tenant_id":  tenantID,
		"user_id":    userID,
		"content_id": contentID,
		"order_id":   orderID,
		"status":     consts.ContentAccessStatusActive,
		"revoked_at": nil,
		"created_at": now,
		"updated_at": now,
	}

	if err := tx.Table(models.TableNameContentAccess).
		Clauses(clause.OnConflict{
			Columns: []clause.Column{{Name: "tenant_id"}, {Name: "user_id"}, {Name: "content_id"}},
			DoUpdates: clause.Assignments(map[string]any{
				"order_id":   orderID,
				"status":     consts.ContentAccessStatusActive,
				"revoked_at": nil,
				"updated_at": now,
			}),
		}).
		Create(insert).Error; err != nil {
		return err
	}
	return nil
}

func loID(m *models.Order) int64 {
	if m == nil {
		return 0
	}
	return m.ID
}
