package tests

import (
	"encoding/json"
	"net/http"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/handler/sqsmeasurehandler"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/panicIf"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
)

type MeasureTestSuite struct {
	tests.BillingTestSuite
	router *gin.Engine
}

func (suite *MeasureTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	datainit.CreatePaymentMock(uint(1))
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	api.InitializeMeasureRouter(ginutils.MyRoute{Route: suite.router})
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test-unit",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	panicIf.Error(err)
}

func newCreditsPackageWhihTime(tenantID uint, credits int64, date time.Time, generalUnitFilter *forms.UnitFilter) (ret *forms.CreditsPackage, unitFilter *forms.UnitFilter, err error) {
	if nil == generalUnitFilter {
		generalUnitFilter, err = (&unit_filter.Model{}).CreateUnitFilter(&forms.UnitFilter{
			UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
			General:        true,
		})
	}
	creditsPackage := &forms.CreditsPackage{
		StartDate:       timeutils.GetMonthDate(date),
		ExpiredDate:     date.AddDate(0, 1, 0),
		TotalCredits:    decimal.NewFromInt(credits),
		Usage:           entity.CreditsPackageType_General,
		UnitFilters:     []*forms.UnitFilterSlim{{TypeID: generalUnitFilter.FilterID}},
		DiscountEnabled: true,
	}
	creditsPackage, err = creditsPackageModel.CreateCreditsPackage(tenantID, creditsPackage)
	return creditsPackage, generalUnitFilter, err

}
func newCreditsPackage(tenantID uint, billingConfigID uint, credits int64) (ret *forms.CreditsPackage, err error) {
	generalUnitFilter, err := (&unit_filter.Model{}).CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	now := timeutils.GetNow()
	creditsPackage := &forms.CreditsPackage{
		StartDate:             timeutils.GetMonthDate(now),
		ExpiredDate:           now.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(credits),
		Usage:                 entity.CreditsPackageType_General,
		UnitFilters:           []*forms.UnitFilterSlim{{TypeID: generalUnitFilter.FilterID}},
		DiscountEnabled:       true,
		BindedBillingConfigID: &billingConfigID,
	}
	creditsPackage, err = creditsPackageModel.CreateCreditsPackage(tenantID, creditsPackage)
	return creditsPackage, err

}
func TestMeasureSuite(t *testing.T) {
	suite.Run(t, new(MeasureTestSuite))
}

func (suite *MeasureTestSuite) TestMeasureCost() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	newCreditsPackage(tenantID, 1, 100)
	unitCosts := forms.UnitCosts{
		Unit:          "test-unit",
		CostTimeStamp: timeutils.GetNow().Unix(),
		Cost:          1,
	}
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/measures", unitCosts)
	assert.Equal(http.StatusOK, w.Code)
}

func (suite *MeasureTestSuite) TestMeasureCostDryrun() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

	creditsPackage, _ := newCreditsPackage(tenantID, 1, 100)

	unitCosts := forms.UnitCosts{
		Unit:          "test-unit",
		CostTimeStamp: timeutils.GetNow().Unix(),
		Cost:          1,
	}
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/measures/dryrun", unitCosts)
	assert.Equal(http.StatusOK, w.Code)
	res := forms.UnitCosts{}
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &res))
	assert.Equal(res.CostPrice, decimal.NewFromInt(1))
	bindedInfo, err := (&billing.BindedPackageModel{}).GetBindedPackage(*creditsPackage.BindedPackageID)
	assert.NoError(err)
	assert.Equal(bindedInfo.RemainingCredits, decimal.NewFromInt(100))
}

func (suite *MeasureTestSuite) TestMeasureCostToSQS() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	_, err := newCreditsPackage(tenantID, 1, 100)
	assert.NoError(err)
	unitCosts := forms.UnitCosts{
		Unit:          "test-unit",
		CostTimeStamp: timeutils.GetNow().Unix(),
		Cost:          1,
	}
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/measures", unitCosts)
	assert.Equal(http.StatusOK, w.Code)
	cnt, err := entity.NewUserUnitCostQuerySet(db.GetDB()).Count()
	assert.NoError(err)
	assert.Equal(int64(1), cnt)
	h := sqsmeasurehandler.GetMeasureSender().(*sqsmeasurehandler.MeasureSQSMock)
	assert.Len(h.Queue, 0)
	config.Get().Set(config.HTTPMeasuresToQueue, true)
	defer func() {
		config.Get().Set(config.HTTPMeasuresToQueue, false)
	}()
	unitCosts.CostID = uuid.NewString()
	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/measures", unitCosts)
	assert.Equal(http.StatusOK, w.Code)
	assert.Len(h.Queue, 1)
	assert.Equal(unitCosts.CostID, h.Queue[0].CostID)
	cnt, err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).Count()
	assert.NoError(err)
	assert.Equal(int64(1), cnt)
	config.Get().Set(config.HTTPMeasuresToQueue, false)
	unitCosts.CostID = uuid.NewString()
	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/measures", unitCosts)
	assert.Equal(http.StatusOK, w.Code)
	assert.Len(h.Queue, 1)
	cnt, err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).Count()
	assert.NoError(err)
	assert.Equal(int64(2), cnt)
}
