package tests

import (
	"testing"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"golang.org/x/exp/rand"

	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler/impl"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/panicIf"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
)

type MspTestSuit struct {
	tests.BillingTestSuite
}

func (suite *MspTestSuit) SetupTest() {
	suite.BillingTestSuite.SetupTest()

}

func TestMspTestSuit(t *testing.T) {
	suite.Run(t, new(MspTestSuit))
}

type MockMSPCentralHandlerImpl struct {
	centralhandler.CentralHandler
}

func (*MockMSPCentralHandlerImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	if tenantID == 2 {
		return &forms.Tenant{
			TenantID:                  tenantID,
			ThirdPartyAccountProvider: constant.MSPAccountProvider,
			ThirdPartyAccountCode:     "211",
			Status:                    centralhandler.TenantStatusActive,
		}, nil
	}
	return &forms.Tenant{
		TenantID:                  tenantID,
		ThirdPartyAccountProvider: constant.GCPAccountProvider,
		Status:                    centralhandler.TenantStatusActive,
	}, nil
}

func (*MockMSPCentralHandlerImpl) GetProjectInfo(projectID uint) (*forms.Project, error) {
	return &forms.Project{
		ID:   projectID,
		Name: "project",
	}, nil
}

func (*MockMSPCentralHandlerImpl) GetMspCustomerScheduleInfo(customerID uint) (*forms.MspCustomerScheduleInfo, error) {
	if customerID == 2 {
		return &forms.MspCustomerScheduleInfo{
			CustomerTenantID: uint64(customerID),
			MspTenantID:      211,
			//EffectedAt:       BillingDate.AddDate(0, 1, 0),
		}, nil
	}
	return nil, billing_errors.ObjectNotExistsError
}

func mspAndSku() {
	var err error
	(&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test-sku-1",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	(&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test-sku-2",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	panicIf.Error(datainit.CreatePaymentMock(uint(211)))
	_, err = datainit.CreateBillingConfig(uint(211), entity.BillingConfigType_MSP)
	billingConfigID := uint(1)

	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(211, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       true,
		Internal:              false,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})
	panicIf.Error(err)
	centralhandler.SetCentralHandler(&MockMSPCentralHandlerImpl{})
}

func (suite *MspTestSuit) TestPayNoLabels() {
	assert := suite.Assert()

	mspAndSku()
	unit1 := "test-sku-1"
	unit2 := "test-sku-2"
	billingConfigID := uint(2)
	invoice := &forms.Invoice{
		InvoiceID:             1,
		TenantID:              2,
		StartDate:             timeutils.GetNow(),
		BindedBillingConfigID: &billingConfigID,
	}
	tenantID := uint(2)
	paymenthandler.SetPaymentHandler(&impl.PaymentHandlerImpl{})
	handler, err := paymenthandler.GetPaymentHandler().GetProvider(entity.PaymentType_MSP)
	assert.NoError(err)
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_MSPCustomer)
	assert.NoError(err)
	payment, err := (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(
		tenantID,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           entity.PaymentType_MSP,
			BindedBillingConfigID: &billingConfigID,
		},
	)
	//1.no data

	assert.NoError(err)
	_, err = handler.Pay(payment, nil, invoice)
	assert.NoError(err)
	//----------
	//labels is null,one costs ,one sku

	costEntity := &entity.UserUnitCost{
		CostID:                "test-1-1",
		TenantID:              2,
		CostTimeStamp:         timeutils.GetNow().Unix() + 1,
		Unit:                  unit1,
		Cost:                  20,
		Status:                entity.UserUnitCostStatus_DONE,
		InvoiceID:             1,
		BindedBillingConfigID: billingConfigID,
	}
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	_, err = handler.Pay(payment, nil, invoice)
	assert.NoError(err)
	var allRawEntities []entity.RawUserUnitCost
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allRawEntities)
	assert.NoError(err)
	assert.Equal(unit1, allRawEntities[0].Unit)
	assert.Equal(1, len(allRawEntities))
	var allCostEntities []entity.UserUnitCost
	err = entity.NewUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allCostEntities)
	assert.NoError(err)
	assert.Equal(1, len(allCostEntities))
	assert.Equal(allCostEntities[0].CostPrice, decimal.NewFromInt(20))

	//-----
	//labels is null ,two costs ,one sku
	invoice.InvoiceID = 2
	costEntity.CostID = "test-2-1"
	costEntity.InvoiceID = 2
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	costEntity.CostID = "test-2-2"
	costEntity.InvoiceID = 2
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	_, err = handler.Pay(payment, nil, invoice)
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allRawEntities)
	assert.NoError(err)
	assert.Equal(unit1, allRawEntities[1].Unit)
	assert.Equal(2, len(allRawEntities))
	assert.Equal(allRawEntities[1].CostPrice, decimal.NewFromInt(40))
	err = entity.NewUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allCostEntities)
	assert.NoError(err)
	assert.Equal(1, len(allCostEntities))
	assert.Equal(allCostEntities[0].CostPrice, decimal.NewFromInt(60))

	//-----
	//labels is null ,two costs ,two sku
	invoice.InvoiceID = 3
	costEntity.CostID = "test-3-1"
	costEntity.InvoiceID = 3
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	costEntity.CostID = "test-3-2"
	costEntity.InvoiceID = 3
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	costEntity.CostID = "test-3-3"
	costEntity.Unit = unit2
	costEntity.InvoiceID = 3
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	_, err = handler.Pay(payment, nil, invoice)
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).OrderAscByCreatedAt().All(&allRawEntities)
	assert.NoError(err)

	assert.Equal(unit1, allRawEntities[2].Unit)
	assert.Equal(unit2, allRawEntities[3].Unit)

	assert.Equal(4, len(allRawEntities))
	assert.Equal(allRawEntities[2].CostPrice, decimal.NewFromInt(40))
	assert.Equal(allRawEntities[3].CostPrice, decimal.NewFromInt(20))

	err = entity.NewUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).OrderAscByCreatedAt().All(&allCostEntities)
	assert.NoError(err)
	assert.Equal(2, len(allCostEntities))
	assert.Equal(allCostEntities[0].CostPrice, decimal.NewFromInt(100))
	assert.Equal(unit1, allCostEntities[0].Unit)
	assert.Equal(allCostEntities[1].CostPrice, decimal.NewFromInt(20))
	assert.Equal(unit2, allCostEntities[1].Unit)
}

func (suite *MspTestSuit) TestPayWhitLabels() {
	assert := suite.Assert()
	mspAndSku()
	timeutils.SetMockNow(datainit.BillingDate)
	unit1 := "test-sku-1"
	//unit2 := "test-sku-2"
	tenantID := uint(2)
	billingConfigID := uint(2)

	invoice := &forms.Invoice{
		InvoiceID:             1,
		TenantID:              2,
		StartDate:             timeutils.GetNow(),
		BindedBillingConfigID: &billingConfigID,
	}
	var err error
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_MSPCustomer)
	assert.NoError(err)
	paymenthandler.SetPaymentHandler(&impl.PaymentHandlerImpl{})
	handler, err := paymenthandler.GetPaymentHandler().GetProvider(entity.PaymentType_MSP)
	assert.NoError(err)
	payment, err := (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(
		tenantID,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           entity.PaymentType_MSP,
			BindedBillingConfigID: &billingConfigID,
		},
	)
	assert.NoError(err)
	//1.no data
	_, err = handler.Pay(payment, nil, invoice)
	assert.NoError(err)
	//----------
	//one labels ,two costs ,one sku
	costEntity := &entity.UserUnitCost{
		CostID:        "test-1-1",
		TenantID:      2,
		CostTimeStamp: timeutils.GetNow().Unix() + rand.Int63n(86400),
		Unit:          unit1,
		Cost:          20,
		Status:        entity.UserUnitCostStatus_DONE,
		InvoiceID:     1,
	}
	costEntity.Labels = make(map[string]string, 32)
	costEntity.Labels["test"] = "test"
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)
	costEntity.CostID = "test-1-2"
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	_, err = handler.Pay(payment, nil, invoice)
	assert.NoError(err)
	var allEntities []entity.RawUserUnitCost
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allEntities)
	assert.NoError(err)

	assert.Equal("1-ef33ee57eab7f1477e0aad02f608905f", allEntities[0].CostID)
	assert.Equal(unit1, allEntities[0].Unit)
	assert.Equal(allEntities[0].CostPrice, decimal.NewFromInt(40))
	assert.Equal(1, len(allEntities))

	var allCostEntities []entity.UserUnitCost
	err = entity.NewUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allCostEntities)
	assert.NoError(err)
	assert.Equal(1, len(allCostEntities))
	assert.Equal(allCostEntities[0].CostPrice, decimal.NewFromInt(40))
	assert.Equal(timeutils.GetNow().Unix(), allCostEntities[0].CostTimeStamp)

	//----------
	//two labels ,two costs ,one sku
	invoice.InvoiceID = 2
	costEntity.CostTimeStamp = timeutils.GetNow().AddDate(0, 0, 1).Unix() + rand.Int63n(86400)
	costEntity.CostID = "test-2-1"
	costEntity.InvoiceID = 2
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)
	costEntity.CostID = "test-2-2"
	costEntity.CostTimeStamp = timeutils.GetNow().AddDate(0, 0, 2).Unix() + rand.Int63n(86400)
	costEntity.InvoiceID = 2
	costEntity.Labels["test-2"] = "2"
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)
	_, err = handler.Pay(payment, nil, invoice)
	assert.NoError(err)
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allEntities)
	assert.NoError(err)
	assert.Equal(unit1, allEntities[1].Unit)
	assert.Equal(unit1, allEntities[2].Unit)
	assert.Equal(allEntities[1].CostPrice, decimal.NewFromInt(20))
	assert.Equal(allEntities[2].CostPrice, decimal.NewFromInt(20))
	assert.Equal(3, len(allEntities))

	_, err = handler.Pay(payment, nil, invoice)
	assert.NoError(err)
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allEntities)
	assert.NoError(err)
	assert.Equal(3, len(allEntities))

	err = entity.NewUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).OrderAscByCostTimeStamp().All(&allCostEntities)
	assert.NoError(err)
	assert.Equal(3, len(allCostEntities))
	assert.Equal(allCostEntities[0].CostPrice, decimal.NewFromInt(40))
	assert.Equal(map[string]string{
		"MSPCustomerID": "2",
		"test":          "test",
	}, map[string]string(allCostEntities[0].Labels))
	assert.Equal(timeutils.GetNow().AddDate(0, 0, 1).Unix(), allCostEntities[1].CostTimeStamp)
	assert.Equal(allCostEntities[1].CostPrice, decimal.NewFromInt(20))
	assert.Equal(map[string]string{
		"MSPCustomerID": "2",
		"test":          "test",
	}, map[string]string(allCostEntities[1].Labels))
	assert.Equal(timeutils.GetNow().AddDate(0, 0, 2).Unix(), allCostEntities[2].CostTimeStamp)
	assert.Equal(allCostEntities[2].CostPrice, decimal.NewFromInt(20))
	assert.Equal(map[string]string{
		"MSPCustomerID": "2",
		"test":          "test",
		"test-2":        "2",
	}, map[string]string(allCostEntities[2].Labels))

	//----------
	//two labels ,one costs ,one sku
	invoice.InvoiceID = 3
	costEntity.CostTimeStamp = timeutils.GetNow().AddDate(0, 0, 3).Unix() + rand.Int63n(86400)
	costEntity.CostID = "test-3-1"
	costEntity.InvoiceID = 3
	costEntity.Labels[constant.ClusterLabelKey] = "2212"
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	_, err = handler.Pay(payment, nil, invoice)
	err = entity.NewRawUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).All(&allEntities)
	assert.NoError(err)
	assert.Equal(unit1, allEntities[1].Unit)
	assert.Equal(unit1, allEntities[2].Unit)
	assert.Equal(allEntities[1].CostPrice, decimal.NewFromInt(20))
	assert.Equal(allEntities[2].CostPrice, decimal.NewFromInt(20))
	assert.Equal(4, len(allEntities))

	err = entity.NewUserUnitCostQuerySet(db.GetDB()).TenantIDEq(211).OrderAscByCostTimeStamp().All(&allCostEntities)
	assert.NoError(err)
	assert.Equal(4, len(allCostEntities))
	assert.Equal(timeutils.GetNow().AddDate(0, 0, 3).Unix(), allCostEntities[3].CostTimeStamp)
	assert.Equal(allCostEntities[3].CostPrice, decimal.NewFromInt(20))
	assert.Equal(map[string]string{
		"MSPCustomerID":          "2",
		"test":                   "test",
		"test-2":                 "2",
		constant.ClusterLabelKey: "2212",
	}, map[string]string(allCostEntities[3].Labels))
}
