package user

import (
	"context"
	"errors"
	"time"

	"bindbox-game/internal/repository/mysql/dao"
	"bindbox-game/internal/repository/mysql/model"

	"gorm.io/gorm/clause"
)

// ConsumePointsFor 扣减用户积分（通用）
// 功能：按有效期优先扣减用户积分，并记录积分流水到 `user_points_ledger`
// 参数：
//   - ctx：上下文
//   - userID：用户ID
//   - points：扣减积分数量
//   - refTable：业务关联表名（例如 products、system_coupons）
//   - refID：业务关联ID字符串（例如 product_id、coupon_id）
//   - remark：备注信息
//   - action：流水动作标识（例如 redeem_product、redeem_coupon）
//
// 返回：ledgerID（积分流水ID），error（不足或写入失败时）
func (s *service) ConsumePointsFor(ctx context.Context, userID int64, points int64, refTable string, refID string, remark string, action string) (int64, error) {
	if points <= 0 {
		return 0, nil
	}
	var ledgerID int64
	err := s.writeDB.Transaction(func(tx *dao.Query) error {
		rows, err := tx.UserPoints.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).Order(tx.UserPoints.ValidEnd.Asc()).Find()
		if err != nil {
			return err
		}
		remain := points
		now := time.Now()
		for _, r := range rows {
			if !r.ValidEnd.IsZero() && r.ValidEnd.Before(now) {
				continue
			}
			if remain <= 0 {
				break
			}
			use := r.Points
			if use > remain {
				use = remain
			}
			_, err := tx.UserPoints.WithContext(ctx).Where(tx.UserPoints.ID.Eq(r.ID)).Updates(map[string]any{"points": r.Points - use})
			if err != nil {
				return err
			}
			remain -= use
		}
		if remain > 0 {
			return errors.New("insufficient_points")
		}
		led := &model.UserPointsLedger{UserID: userID, Action: action, Points: -points, RefTable: refTable, RefID: refID, Remark: remark}
		if err := tx.UserPointsLedger.WithContext(ctx).Create(led); err != nil {
			return err
		}
		ledgerID = led.ID
		return nil
	})
	return ledgerID, err
}

// ConsumePointsForRefund 扣减用户积分（退款回收场景，支持扣完为止）
// 返回：ledgerID（流水ID）, actualConsumed（实际扣除数量）, error
func (s *service) ConsumePointsForRefund(ctx context.Context, userID int64, points int64, refTable string, refID string, remark string) (int64, int64, error) {
	if points <= 0 {
		return 0, 0, nil
	}
	var ledgerID int64
	var actualConsumed int64

	err := s.writeDB.Transaction(func(tx *dao.Query) error {
		rows, err := tx.UserPoints.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).Order(tx.UserPoints.ValidEnd.Asc()).Find()
		if err != nil {
			return err
		}
		remain := points
		now := time.Now()
		for _, r := range rows {
			if !r.ValidEnd.IsZero() && r.ValidEnd.Before(now) {
				continue
			}
			if remain <= 0 {
				break
			}
			use := r.Points
			if use > remain {
				use = remain
			}
			_, err := tx.UserPoints.WithContext(ctx).Where(tx.UserPoints.ID.Eq(r.ID)).Updates(map[string]any{"points": r.Points - use})
			if err != nil {
				return err
			}
			remain -= use
		}

		actualConsumed = points - remain
		if actualConsumed > 0 {
			led := &model.UserPointsLedger{
				UserID:   userID,
				Action:   "refund_reclaim",
				Points:   -actualConsumed,
				RefTable: refTable,
				RefID:    refID,
				Remark:   remark,
			}
			if err := tx.UserPointsLedger.WithContext(ctx).Create(led); err != nil {
				return err
			}
			ledgerID = led.ID
		}
		return nil
	})

	return ledgerID, actualConsumed, err
}
