package services

import (
	"context"
	"time"

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

	"github.com/spf13/cast"
)

// @provider
type coupon struct{}

func (s *coupon) ListUserCoupons(
	ctx context.Context,
	userID int64,
	status string,
) ([]coupon_dto.UserCouponItem, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.UserCouponQuery.QueryContext(ctx)
	q = q.Where(tbl.UserID.Eq(uid))
	if status != "" {
		q = q.Where(tbl.Status.Eq(status))
	}

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

	var res []coupon_dto.UserCouponItem
	for _, v := range list {
		c, _ := models.CouponQuery.WithContext(ctx).Where(models.CouponQuery.ID.Eq(v.CouponID)).First()

		item := coupon_dto.UserCouponItem{
			ID:       cast.ToString(v.ID),
			CouponID: cast.ToString(v.CouponID),
			Status:   v.Status,
		}
		if c != nil {
			item.Title = c.Title
			item.Description = c.Description
			item.Type = c.Type
			item.Value = c.Value
			item.MinOrderAmount = c.MinOrderAmount
			if !c.StartAt.IsZero() {
				item.StartAt = c.StartAt.Format(time.RFC3339)
			}
			if !c.EndAt.IsZero() {
				item.EndAt = c.EndAt.Format(time.RFC3339)
			}
		}
		res = append(res, item)
	}
	return res, nil
}

// Validate checks if a coupon can be used for an order and returns the discount amount
func (s *coupon) Validate(ctx context.Context, 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 != "unused" {
		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("优惠券信息缺失")
	}

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

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

	var discount int64
	if c.Type == "fix_amount" {
		discount = c.Value
	} else if c.Type == "discount" {
		discount = (amount * c.Value) / 100
		if c.MaxDiscount > 0 && discount > c.MaxDiscount {
			discount = c.MaxDiscount
		}
	}

	// 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, userCouponID, orderID int64) error {
	now := time.Now()
	// Update User Coupon
	info, err := tx.UserCoupon.WithContext(ctx).
		Where(tx.UserCoupon.ID.Eq(userCouponID), tx.UserCoupon.Status.Eq("unused")).
		Updates(&models.UserCoupon{
			Status:  "used",
			OrderID: orderID,
			UsedAt:  now,
		})
	if err != nil {
		return err
	}
	if info.RowsAffected == 0 {
		return errorx.ErrBusinessLogic.WithMsg("优惠券核销失败")
	}

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

	return nil
}
