package services

import (
	"context"
	"errors"
	"strings"
	"time"

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

	"go.ipao.vip/gen/field"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// @provider
type coupon struct{}

func (s *coupon) ListUserCoupons(
	ctx context.Context,
	tenantID int64,
	userID int64,
	status string,
) ([]coupon_dto.UserCouponItem, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	statusFilter := strings.TrimSpace(status)
	if statusFilter != "" && statusFilter != "all" {
		if _, err := consts.ParseUserCouponStatus(statusFilter); err != nil {
			return nil, errorx.ErrInvalidParameter.WithMsg("状态参数无效")
		}
	}

	tbl, q := models.UserCouponQuery.QueryContext(ctx)
	q = q.Where(tbl.UserID.Eq(userID))

	list, err := q.Order(tbl.CreatedAt.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	if len(list) == 0 {
		return []coupon_dto.UserCouponItem{}, nil
	}

	couponMap, err := s.fetchCouponMap(ctx, tenantID, list)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	res := make([]coupon_dto.UserCouponItem, 0, len(list))
	for _, v := range list {
		if v == nil {
			continue
		}
		c, ok := couponMap[v.CouponID]
		if !ok {
			continue
		}

		finalStatus := v.Status
		if s.isCouponExpired(c, now) && v.Status == consts.UserCouponStatusUnused {
			if err := s.markUserCouponExpired(ctx, v.ID); err != nil {
				return nil, err
			}
			finalStatus = consts.UserCouponStatusExpired
		}

		if statusFilter != "" && statusFilter != "all" && string(finalStatus) != statusFilter {
			continue
		}

		res = append(res, s.composeUserCouponItem(v, c, finalStatus))
	}
	return res, nil
}

func (s *coupon) ListAvailable(
	ctx context.Context,
	tenantID int64,
	userID int64,
	amount int64,
) ([]coupon_dto.UserCouponItem, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	if amount <= 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("订单金额无效")
	}

	tbl, q := models.UserCouponQuery.QueryContext(ctx)
	q = q.Where(tbl.UserID.Eq(userID), tbl.Status.Eq(consts.UserCouponStatusUnused))

	list, err := q.Order(tbl.CreatedAt.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	if len(list) == 0 {
		return []coupon_dto.UserCouponItem{}, nil
	}

	couponMap, err := s.fetchCouponMap(ctx, tenantID, list)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	res := make([]coupon_dto.UserCouponItem, 0, len(list))
	for _, v := range list {
		if v == nil {
			continue
		}
		c, ok := couponMap[v.CouponID]
		if !ok {
			continue
		}

		if !s.isCouponActive(c, now) {
			if s.isCouponExpired(c, now) && v.Status == consts.UserCouponStatusUnused {
				if err := s.markUserCouponExpired(ctx, v.ID); err != nil {
					return nil, err
				}
			}
			continue
		}

		if amount < c.MinOrderAmount {
			continue
		}

		res = append(res, s.composeUserCouponItem(v, c, consts.UserCouponStatusUnused))
	}

	return res, nil
}

func (s *coupon) Receive(
	ctx context.Context,
	tenantID int64,
	userID int64,
	couponID int64,
) (*coupon_dto.UserCouponItem, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	if couponID == 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("优惠券不存在")
	}

	var result *coupon_dto.UserCouponItem
	err := models.Q.Transaction(func(tx *models.Query) error {
		// 锁定优惠券行，防止并发超发。
		tbl, q := tx.Coupon.QueryContext(ctx)
		q = q.Where(tbl.ID.Eq(couponID))
		if tenantID > 0 {
			q = q.Where(tbl.TenantID.Eq(tenantID))
		}
		coupon, err := q.Clauses(clause.Locking{Strength: "UPDATE"}).First()
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
			}
			return errorx.ErrDatabaseError.WithCause(err)
		}
		if tenantID > 0 && coupon.TenantID != tenantID {
			return errorx.ErrForbidden.WithMsg("优惠券租户不匹配")
		}

		now := time.Now()
		if !coupon.StartAt.IsZero() && now.Before(coupon.StartAt) {
			return errorx.ErrBusinessLogic.WithMsg("优惠券尚未生效")
		}
		if !coupon.EndAt.IsZero() && now.After(coupon.EndAt) {
			return errorx.ErrBusinessLogic.WithMsg("优惠券已过期")
		}

		existing, err := tx.UserCoupon.WithContext(ctx).
			Where(tx.UserCoupon.UserID.Eq(userID), tx.UserCoupon.CouponID.Eq(coupon.ID)).
			First()
		if err == nil {
			item := s.composeUserCouponItem(existing, coupon, existing.Status)
			result = &item
			return nil
		}
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return errorx.ErrDatabaseError.WithCause(err)
		}

		if coupon.TotalQuantity > 0 {
			count, err := tx.UserCoupon.WithContext(ctx).
				Where(tx.UserCoupon.CouponID.Eq(coupon.ID)).
				Count()
			if err != nil {
				return errorx.ErrDatabaseError.WithCause(err)
			}
			if int32(count) >= coupon.TotalQuantity {
				return errorx.ErrBusinessLogic.WithMsg("优惠券已领完")
			}
		}

		uc := &models.UserCoupon{
			UserID:   userID,
			CouponID: coupon.ID,
			Status:   consts.UserCouponStatusUnused,
		}
		if err := tx.UserCoupon.WithContext(ctx).Create(uc); err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}

		item := s.composeUserCouponItem(uc, coupon, consts.UserCouponStatusUnused)
		result = &item
		return nil
	})
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (s *coupon) Create(
	ctx context.Context,
	tenantID int64,
	userID int64,
	form *coupon_dto.CouponCreateForm,
) (*coupon_dto.CouponItem, error) {
	if tenantID == 0 {
		return nil, errorx.ErrForbidden.WithMsg("租户未绑定")
	}
	if form == nil {
		return nil, errorx.ErrInvalidParameter.WithMsg("参数无效")
	}
	_ = userID

	title := strings.TrimSpace(form.Title)
	if title == "" {
		return nil, errorx.ErrInvalidParameter.WithMsg("优惠券标题不能为空")
	}

	couponType, err := s.parseCouponType(form.Type)
	if err != nil {
		return nil, err
	}
	if err := s.validateCouponValue(couponType, form.Value, form.MaxDiscount); err != nil {
		return nil, err
	}
	if form.MinOrderAmount < 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("使用门槛无效")
	}
	if form.TotalQuantity < 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("发行数量无效")
	}

	startAt, err := s.parseTime(form.StartAt)
	if err != nil {
		return nil, err
	}
	endAt, err := s.parseTime(form.EndAt)
	if err != nil {
		return nil, err
	}
	if !startAt.IsZero() && !endAt.IsZero() && startAt.After(endAt) {
		return nil, errorx.ErrInvalidParameter.WithMsg("生效时间不能晚于过期时间")
	}

	coupon := &models.Coupon{
		TenantID:       tenantID,
		Title:          title,
		Description:    strings.TrimSpace(form.Description),
		Type:           couponType,
		Value:          form.Value,
		MinOrderAmount: form.MinOrderAmount,
		MaxDiscount:    form.MaxDiscount,
		TotalQuantity:  form.TotalQuantity,
		UsedQuantity:   0,
		StartAt:        startAt,
		EndAt:          endAt,
	}

	if err := models.CouponQuery.WithContext(ctx).Create(coupon); err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	item := s.composeCouponItem(coupon)
	return &item, nil
}

func (s *coupon) Update(
	ctx context.Context,
	tenantID int64,
	userID int64,
	id int64,
	form *coupon_dto.CouponUpdateForm,
) (*coupon_dto.CouponItem, error) {
	if tenantID == 0 {
		return nil, errorx.ErrForbidden.WithMsg("租户未绑定")
	}
	if id == 0 || form == nil {
		return nil, errorx.ErrInvalidParameter.WithMsg("参数无效")
	}
	_ = userID

	var result *coupon_dto.CouponItem
	err := models.Q.Transaction(func(tx *models.Query) error {
		tbl, q := tx.Coupon.QueryContext(ctx)
		q = q.Where(tbl.ID.Eq(id), tbl.TenantID.Eq(tenantID))
		coupon, err := q.Clauses(clause.Locking{Strength: "UPDATE"}).First()
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
			}
			return errorx.ErrDatabaseError.WithCause(err)
		}

		now := time.Now()
		claimedCount, err := tx.UserCoupon.WithContext(ctx).
			Where(tx.UserCoupon.CouponID.Eq(coupon.ID)).
			Count()
		if err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}
		if claimedCount > 0 || (!coupon.StartAt.IsZero() && now.After(coupon.StartAt)) {
			return errorx.ErrBusinessLogic.WithMsg("优惠券已领取或已生效，禁止修改")
		}

		updates := make([]field.AssignExpr, 0)
		nextType := coupon.Type
		nextValue := coupon.Value
		nextMaxDiscount := coupon.MaxDiscount
		startAt := coupon.StartAt
		endAt := coupon.EndAt

		if form.Title != nil {
			value := strings.TrimSpace(*form.Title)
			if value == "" {
				return errorx.ErrInvalidParameter.WithMsg("优惠券标题不能为空")
			}
			updates = append(updates, tbl.Title.Value(value))
		}
		if form.Description != nil {
			updates = append(updates, tbl.Description.Value(strings.TrimSpace(*form.Description)))
		}
		if form.Type != nil {
			parsed, err := s.parseCouponType(*form.Type)
			if err != nil {
				return err
			}
			nextType = parsed
			updates = append(updates, tbl.Type.Value(parsed))
		}
		if form.Value != nil {
			nextValue = *form.Value
			updates = append(updates, tbl.Value.Value(*form.Value))
		}
		if form.MinOrderAmount != nil {
			if *form.MinOrderAmount < 0 {
				return errorx.ErrInvalidParameter.WithMsg("使用门槛无效")
			}
			updates = append(updates, tbl.MinOrderAmount.Value(*form.MinOrderAmount))
		}
		if form.MaxDiscount != nil {
			nextMaxDiscount = *form.MaxDiscount
			updates = append(updates, tbl.MaxDiscount.Value(*form.MaxDiscount))
		}
		if form.TotalQuantity != nil {
			if *form.TotalQuantity < 0 {
				return errorx.ErrInvalidParameter.WithMsg("发行数量无效")
			}
			if *form.TotalQuantity > 0 && int32(claimedCount) > *form.TotalQuantity {
				return errorx.ErrBusinessLogic.WithMsg("发行数量不足以覆盖已领取数量")
			}
			updates = append(updates, tbl.TotalQuantity.Value(*form.TotalQuantity))
		}
		if form.StartAt != nil {
			parsed, err := s.parseTime(*form.StartAt)
			if err != nil {
				return err
			}
			startAt = parsed
			updates = append(updates, tbl.StartAt.Value(parsed))
		}
		if form.EndAt != nil {
			parsed, err := s.parseTime(*form.EndAt)
			if err != nil {
				return err
			}
			endAt = parsed
			updates = append(updates, tbl.EndAt.Value(parsed))
		}

		if err := s.validateCouponValue(nextType, nextValue, nextMaxDiscount); err != nil {
			return err
		}
		if !startAt.IsZero() && !endAt.IsZero() && startAt.After(endAt) {
			return errorx.ErrInvalidParameter.WithMsg("生效时间不能晚于过期时间")
		}

		if len(updates) == 0 {
			resultItem := s.composeCouponItem(coupon)
			result = &resultItem
			return nil
		}

		updates = append(updates, tbl.UpdatedAt.Value(time.Now()))
		if _, err := tx.Coupon.WithContext(ctx).Where(tbl.ID.Eq(coupon.ID)).UpdateSimple(updates...); err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}
		updated, err := tx.Coupon.WithContext(ctx).Where(tbl.ID.Eq(coupon.ID)).First()
		if err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}
		resultItem := s.composeCouponItem(updated)
		result = &resultItem
		return nil
	})
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (s *coupon) Get(
	ctx context.Context,
	tenantID int64,
	id int64,
) (*coupon_dto.CouponItem, error) {
	if id == 0 {
		return nil, errorx.ErrInvalidParameter.WithMsg("优惠券不存在")
	}
	query := models.CouponQuery.WithContext(ctx).Where(models.CouponQuery.ID.Eq(id))
	if tenantID > 0 {
		query = query.Where(models.CouponQuery.TenantID.Eq(tenantID))
	}
	coupon, err := query.First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	item := s.composeCouponItem(coupon)
	return &item, nil
}

func (s *coupon) List(
	ctx context.Context,
	tenantID int64,
	filter *coupon_dto.CouponListFilter,
) (*requests.Pager, error) {
	if tenantID == 0 {
		return nil, errorx.ErrForbidden.WithMsg("租户未绑定")
	}
	if filter == nil {
		filter = &coupon_dto.CouponListFilter{}
	}

	tbl, q := models.CouponQuery.QueryContext(ctx)
	q = q.Where(tbl.TenantID.Eq(tenantID))

	if filter.Type != nil && strings.TrimSpace(*filter.Type) != "" {
		parsed, err := s.parseCouponType(*filter.Type)
		if err != nil {
			return nil, err
		}
		q = q.Where(tbl.Type.Eq(parsed))
	}
	if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
		keyword := strings.TrimSpace(*filter.Keyword)
		q = q.Where(field.Or(
			tbl.Title.Like("%"+keyword+"%"),
			tbl.Description.Like("%"+keyword+"%"),
		))
	}
	if filter.Status != nil && strings.TrimSpace(*filter.Status) != "" {
		status := strings.TrimSpace(*filter.Status)
		now := time.Now()
		switch status {
		case "active":
			q = q.Where(field.Or(tbl.StartAt.Lte(now), tbl.StartAt.IsNull()))
			q = q.Where(field.Or(tbl.EndAt.Gte(now), tbl.EndAt.IsNull()))
		case "expired":
			q = q.Where(tbl.EndAt.IsNotNull(), tbl.EndAt.Lt(now))
		default:
			return nil, errorx.ErrInvalidParameter.WithMsg("状态参数无效")
		}
	}

	p := requests.Pagination{Page: filter.Page, Limit: filter.Limit}
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Order(tbl.CreatedAt.Desc()).Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	items := make([]coupon_dto.CouponItem, 0, len(list))
	for _, c := range list {
		if c == nil {
			continue
		}
		items = append(items, s.composeCouponItem(c))
	}

	return &requests.Pager{
		Pagination: requests.Pagination{Page: p.Page, Limit: p.Limit},
		Total:      total,
		Items:      items,
	}, nil
}

func (s *coupon) Grant(
	ctx context.Context,
	tenantID int64,
	couponID int64,
	userIDs []int64,
) (int, error) {
	if tenantID == 0 {
		return 0, errorx.ErrForbidden.WithMsg("租户未绑定")
	}
	if couponID == 0 {
		return 0, errorx.ErrInvalidParameter.WithMsg("优惠券不存在")
	}
	if len(userIDs) == 0 {
		return 0, errorx.ErrInvalidParameter.WithMsg("用户列表为空")
	}

	uniq := make(map[int64]struct{}, len(userIDs))
	cleanIDs := make([]int64, 0, len(userIDs))
	for _, id := range userIDs {
		if id <= 0 {
			continue
		}
		if _, ok := uniq[id]; ok {
			continue
		}
		uniq[id] = struct{}{}
		cleanIDs = append(cleanIDs, id)
	}
	if len(cleanIDs) == 0 {
		return 0, errorx.ErrInvalidParameter.WithMsg("用户列表为空")
	}

	var granted int
	err := models.Q.Transaction(func(tx *models.Query) error {
		// 锁定优惠券行，防止并发超发。
		tbl, q := tx.Coupon.QueryContext(ctx)
		q = q.Where(tbl.ID.Eq(couponID), tbl.TenantID.Eq(tenantID))
		coupon, err := q.Clauses(clause.Locking{Strength: "UPDATE"}).First()
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
			}
			return errorx.ErrDatabaseError.WithCause(err)
		}

		now := time.Now()
		if !coupon.StartAt.IsZero() && now.Before(coupon.StartAt) {
			return errorx.ErrBusinessLogic.WithMsg("优惠券尚未生效")
		}
		if !coupon.EndAt.IsZero() && now.After(coupon.EndAt) {
			return errorx.ErrBusinessLogic.WithMsg("优惠券已过期")
		}

		existingList, err := tx.UserCoupon.WithContext(ctx).
			Where(tx.UserCoupon.CouponID.Eq(coupon.ID), tx.UserCoupon.UserID.In(cleanIDs...)).
			Find()
		if err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}
		existMap := make(map[int64]struct{}, len(existingList))
		for _, item := range existingList {
			existMap[item.UserID] = struct{}{}
		}

		newIDs := make([]int64, 0, len(cleanIDs))
		for _, id := range cleanIDs {
			if _, ok := existMap[id]; ok {
				continue
			}
			newIDs = append(newIDs, id)
		}
		if len(newIDs) == 0 {
			return nil
		}

		if coupon.TotalQuantity > 0 {
			count, err := tx.UserCoupon.WithContext(ctx).
				Where(tx.UserCoupon.CouponID.Eq(coupon.ID)).
				Count()
			if err != nil {
				return errorx.ErrDatabaseError.WithCause(err)
			}
			if int32(count)+int32(len(newIDs)) > coupon.TotalQuantity {
				return errorx.ErrBusinessLogic.WithMsg("优惠券库存不足")
			}
		}

		for _, id := range newIDs {
			uc := &models.UserCoupon{
				UserID:   id,
				CouponID: coupon.ID,
				Status:   consts.UserCouponStatusUnused,
			}
			if err := tx.UserCoupon.WithContext(ctx).Create(uc); err != nil {
				return errorx.ErrDatabaseError.WithCause(err)
			}
			granted++
		}
		return nil
	})
	if err != nil {
		return 0, err
	}
	return granted, nil
}

// Validate checks if a coupon can be used for an order and returns the discount amount
func (s *coupon) Validate(ctx context.Context, tenantID, userID, userCouponID, amount int64) (int64, error) {
	uc, err := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(userCouponID)).First()
	if err != nil {
		return 0, errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
	}
	if uc.UserID != userID {
		return 0, errorx.ErrUnauthorized.WithMsg("无权使用该优惠券")
	}
	if uc.Status != consts.UserCouponStatusUnused {
		return 0, errorx.ErrBusinessLogic.WithMsg("优惠券已使用或失效")
	}

	c, err := models.CouponQuery.WithContext(ctx).Where(models.CouponQuery.ID.Eq(uc.CouponID)).First()
	if err != nil {
		return 0, errorx.ErrRecordNotFound.WithMsg("优惠券信息缺失")
	}
	if tenantID > 0 && c.TenantID != tenantID {
		return 0, errorx.ErrForbidden.WithMsg("优惠券租户不匹配")
	}

	now := time.Now()
	if !c.StartAt.IsZero() && now.Before(c.StartAt) {
		return 0, errorx.ErrBusinessLogic.WithMsg("优惠券尚未生效")
	}
	if !c.EndAt.IsZero() && now.After(c.EndAt) {
		if err := s.markUserCouponExpired(ctx, uc.ID); err != nil {
			return 0, err
		}
		return 0, errorx.ErrBusinessLogic.WithMsg("优惠券已过期")
	}

	if amount < c.MinOrderAmount {
		return 0, errorx.ErrBusinessLogic.WithMsg("未达到优惠券使用门槛")
	}

	var discount int64
	switch c.Type {
	case consts.CouponTypeFixAmount:
		discount = c.Value
	case consts.CouponTypeDiscount:
		discount = (amount * c.Value) / 100
		if c.MaxDiscount > 0 && discount > c.MaxDiscount {
			discount = c.MaxDiscount
		}
	default:
		return 0, errorx.ErrDataCorrupted.WithMsg("优惠券类型异常")
	}

	// Discount cannot exceed order amount
	if discount > amount {
		discount = amount
	}

	return discount, nil
}

// MarkUsed marks a user coupon as used (intended to be called inside a transaction)
func (s *coupon) MarkUsed(ctx context.Context, tx *models.Query, tenantID, userCouponID, orderID int64) error {
	uc, err := tx.UserCoupon.WithContext(ctx).Where(tx.UserCoupon.ID.Eq(userCouponID)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
		}
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if uc.Status != consts.UserCouponStatusUnused {
		return errorx.ErrBusinessLogic.WithMsg("优惠券核销失败")
	}

	c, err := tx.Coupon.WithContext(ctx).Where(tx.Coupon.ID.Eq(uc.CouponID)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorx.ErrRecordNotFound.WithMsg("优惠券信息缺失")
		}
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if tenantID > 0 && c.TenantID != tenantID {
		return errorx.ErrForbidden.WithMsg("优惠券租户不匹配")
	}

	now := time.Now()
	// Update User Coupon
	info, err := tx.UserCoupon.WithContext(ctx).
		Where(tx.UserCoupon.ID.Eq(userCouponID), tx.UserCoupon.Status.Eq(consts.UserCouponStatusUnused)).
		Updates(&models.UserCoupon{
			Status:  consts.UserCouponStatusUsed,
			OrderID: orderID,
			UsedAt:  now,
		})
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if info.RowsAffected == 0 {
		return errorx.ErrBusinessLogic.WithMsg("优惠券核销失败")
	}

	// Update Coupon used quantity (Optional, but good for stats)
	_, _ = tx.Coupon.WithContext(ctx).Where(tx.Coupon.ID.Eq(uc.CouponID)).UpdateSimple(tx.Coupon.UsedQuantity.Add(1))

	return nil
}

func (s *coupon) fetchCouponMap(ctx context.Context, tenantID int64, list []*models.UserCoupon) (map[int64]*models.Coupon, error) {
	couponIDSet := make(map[int64]struct{}, len(list))
	couponIDs := make([]int64, 0, len(list))
	for _, v := range list {
		if v == nil {
			continue
		}
		if _, ok := couponIDSet[v.CouponID]; ok {
			continue
		}
		couponIDs = append(couponIDs, v.CouponID)
		couponIDSet[v.CouponID] = struct{}{}
	}

	cTbl, cQ := models.CouponQuery.QueryContext(ctx)
	cQ = cQ.Where(cTbl.ID.In(couponIDs...))
	if tenantID > 0 {
		cQ = cQ.Where(cTbl.TenantID.Eq(tenantID))
	}
	coupons, err := cQ.Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	couponMap := make(map[int64]*models.Coupon, len(coupons))
	for _, c := range coupons {
		couponMap[c.ID] = c
	}
	return couponMap, nil
}

func (s *coupon) composeUserCouponItem(uc *models.UserCoupon, c *models.Coupon, status consts.UserCouponStatus) coupon_dto.UserCouponItem {
	item := coupon_dto.UserCouponItem{
		ID:       uc.ID,
		CouponID: uc.CouponID,
		Status:   string(status),
	}
	if c != nil {
		item.Title = c.Title
		item.Description = c.Description
		item.Type = string(c.Type)
		item.Value = c.Value
		item.MinOrderAmount = c.MinOrderAmount
		item.StartAt = s.formatTime(c.StartAt)
		item.EndAt = s.formatTime(c.EndAt)
	}
	return item
}

func (s *coupon) composeCouponItem(c *models.Coupon) coupon_dto.CouponItem {
	return coupon_dto.CouponItem{
		ID:             c.ID,
		Title:          c.Title,
		Description:    c.Description,
		Type:           string(c.Type),
		Value:          c.Value,
		MinOrderAmount: c.MinOrderAmount,
		MaxDiscount:    c.MaxDiscount,
		TotalQuantity:  c.TotalQuantity,
		UsedQuantity:   c.UsedQuantity,
		StartAt:        s.formatTime(c.StartAt),
		EndAt:          s.formatTime(c.EndAt),
		CreatedAt:      s.formatTime(c.CreatedAt),
		UpdatedAt:      s.formatTime(c.UpdatedAt),
	}
}

func (s *coupon) markUserCouponExpired(ctx context.Context, userCouponID int64) error {
	if userCouponID == 0 {
		return nil
	}
	_, err := models.UserCouponQuery.WithContext(ctx).
		Where(models.UserCouponQuery.ID.Eq(userCouponID), models.UserCouponQuery.Status.Eq(consts.UserCouponStatusUnused)).
		UpdateSimple(models.UserCouponQuery.Status.Value(consts.UserCouponStatusExpired))
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *coupon) isCouponExpired(c *models.Coupon, now time.Time) bool {
	return !c.EndAt.IsZero() && now.After(c.EndAt)
}

func (s *coupon) isCouponActive(c *models.Coupon, now time.Time) bool {
	if !c.StartAt.IsZero() && now.Before(c.StartAt) {
		return false
	}
	if !c.EndAt.IsZero() && now.After(c.EndAt) {
		return false
	}
	return true
}

func (s *coupon) parseTime(val string) (time.Time, error) {
	value := strings.TrimSpace(val)
	if value == "" {
		return time.Time{}, nil
	}
	tm, err := time.Parse(time.RFC3339, value)
	if err != nil {
		return time.Time{}, errorx.ErrInvalidFormat.WithMsg("时间格式无效")
	}
	return tm, nil
}

func (s *coupon) parseCouponType(val string) (consts.CouponType, error) {
	value := strings.TrimSpace(val)
	if value == "" {
		return "", errorx.ErrInvalidParameter.WithMsg("优惠券类型不能为空")
	}
	couponType, err := consts.ParseCouponType(value)
	if err != nil {
		return "", errorx.ErrInvalidParameter.WithMsg("优惠券类型无效")
	}
	return couponType, nil
}

func (s *coupon) validateCouponValue(typ consts.CouponType, value, maxDiscount int64) error {
	switch typ {
	case consts.CouponTypeFixAmount:
		if value <= 0 {
			return errorx.ErrInvalidParameter.WithMsg("优惠券面值无效")
		}
	case consts.CouponTypeDiscount:
		if value <= 0 || value > 100 {
			return errorx.ErrInvalidParameter.WithMsg("折扣比例无效")
		}
		if maxDiscount < 0 {
			return errorx.ErrInvalidParameter.WithMsg("最高抵扣金额无效")
		}
	default:
		return errorx.ErrInvalidParameter.WithMsg("优惠券类型无效")
	}
	return nil
}

func (s *coupon) formatTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}
	return t.Format(time.RFC3339)
}
