package services

import (
	"database/sql"
	"testing"

	"quyun/v2/app/commands/testx"
	order_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/database"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/suite"
	"go.ipao.vip/atom/contracts"
	"go.uber.org/dig"
)

type CouponTestSuiteInjectParams struct {
	dig.In

	DB       *sql.DB
	Initials []contracts.Initial `group:"initials"`
}

type CouponTestSuite struct {
	suite.Suite
	CouponTestSuiteInjectParams
}

func Test_Coupon(t *testing.T) {
	providers := testx.Default().With(Provide)

	testx.Serve(providers, t, func(p CouponTestSuiteInjectParams) {
		suite.Run(t, &CouponTestSuite{CouponTestSuiteInjectParams: p})
	})
}

func (s *CouponTestSuite) Test_CouponFlow() {
	Convey("Coupon Flow", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(1)
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameCoupon,
			models.TableNameUserCoupon,
			models.TableNameOrder,
			models.TableNameUser,
			models.TableNameContent,
			models.TableNameContentPrice,
		)

		user := &models.User{Username: "coupon_user", Phone: "13800000001"}
		models.UserQuery.WithContext(ctx).Create(user)

		// 1. Create Coupon (Fixed 5.00 CNY, Min 10.00 CNY)
		cp := &models.Coupon{
			TenantID:       tenantID,
			Title:          "Save 5",
			Type:           consts.CouponTypeFixAmount,
			Value:          500,
			MinOrderAmount: 1000,
		}
		models.CouponQuery.WithContext(ctx).Create(cp)

		// 2. Give to User
		uc := &models.UserCoupon{
			UserID:   user.ID,
			CouponID: cp.ID,
			Status:   consts.UserCouponStatusUnused,
		}
		models.UserCouponQuery.WithContext(ctx).Create(uc)

		Convey("should validate coupon successfully", func() {
			discount, err := Coupon.Validate(ctx, tenantID, user.ID, uc.ID, 1500)
			So(err, ShouldBeNil)
			So(discount, ShouldEqual, 500)
		})

		Convey("should fail if below min amount", func() {
			_, err := Coupon.Validate(ctx, tenantID, user.ID, uc.ID, 800)
			So(err, ShouldNotBeNil)
		})

		Convey("should apply in Order.Create", func() {
			// Setup Content
			c := &models.Content{TenantID: tenantID, UserID: 99, Title: "Test", Status: consts.ContentStatusPublished}
			models.ContentQuery.WithContext(ctx).Create(c)
			models.ContentPriceQuery.WithContext(ctx).Create(&models.ContentPrice{
				TenantID:    tenantID,
				ContentID:   c.ID,
				PriceAmount: 2000, // 20.00 CNY
				Currency:    consts.CurrencyCNY,
			})

			form := &order_dto.OrderCreateForm{
				ContentID:    c.ID,
				UserCouponID: uc.ID,
			}
			// Simulate Auth context for Order service
			res, err := Order.Create(ctx, tenantID, user.ID, form)
			So(err, ShouldBeNil)

			// Verify Order
			oid := res.OrderID
			o, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
			So(o.AmountOriginal, ShouldEqual, 2000)
			So(o.AmountDiscount, ShouldEqual, 500)
			So(o.AmountPaid, ShouldEqual, 1500)
			So(o.CouponID, ShouldEqual, cp.ID)

			// Verify Coupon Status
			ucReload, _ := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(uc.ID)).First()
			So(ucReload.Status, ShouldEqual, consts.UserCouponStatusUsed)
			So(ucReload.OrderID, ShouldEqual, oid)
		})
	})
}

func (s *CouponTestSuite) Test_Receive() {
	Convey("Receive", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(2)
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameCoupon,
			models.TableNameUserCoupon,
			models.TableNameUser,
		)

		user := &models.User{Username: "coupon_receive", Phone: "13800000002"}
		So(models.UserQuery.WithContext(ctx).Create(user), ShouldBeNil)

		cp := &models.Coupon{
			TenantID:       tenantID,
			Title:          "Receive Test",
			Type:           consts.CouponTypeFixAmount,
			Value:          300,
			MinOrderAmount: 0,
			TotalQuantity:  1,
		}
		So(models.CouponQuery.WithContext(ctx).Create(cp), ShouldBeNil)

		item, err := Coupon.Receive(ctx, tenantID, user.ID, cp.ID)
		So(err, ShouldBeNil)
		So(item, ShouldNotBeNil)
		So(item.CouponID, ShouldEqual, cp.ID)

		// second receive should return existing
		item2, err := Coupon.Receive(ctx, tenantID, user.ID, cp.ID)
		So(err, ShouldBeNil)
		So(item2.CouponID, ShouldEqual, cp.ID)
	})
}

func (s *CouponTestSuite) Test_ListAvailable() {
	Convey("ListAvailable", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(3)
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameCoupon,
			models.TableNameUserCoupon,
			models.TableNameUser,
		)

		user := &models.User{Username: "coupon_available", Phone: "13800000003"}
		So(models.UserQuery.WithContext(ctx).Create(user), ShouldBeNil)

		cp := &models.Coupon{
			TenantID:       tenantID,
			Title:          "Available Test",
			Type:           consts.CouponTypeFixAmount,
			Value:          500,
			MinOrderAmount: 1000,
		}
		So(models.CouponQuery.WithContext(ctx).Create(cp), ShouldBeNil)

		uc := &models.UserCoupon{
			UserID:   user.ID,
			CouponID: cp.ID,
			Status:   consts.UserCouponStatusUnused,
		}
		So(models.UserCouponQuery.WithContext(ctx).Create(uc), ShouldBeNil)

		list, err := Coupon.ListAvailable(ctx, tenantID, user.ID, 500)
		So(err, ShouldBeNil)
		So(len(list), ShouldEqual, 0)

		list, err = Coupon.ListAvailable(ctx, tenantID, user.ID, 1200)
		So(err, ShouldBeNil)
		So(len(list), ShouldEqual, 1)
		So(list[0].CouponID, ShouldEqual, cp.ID)
	})
}
