package tests

import (
	"fmt"
	"testing"
	"time"

	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/log"
	"github.com/shopspring/decimal"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/suite"
)

type CostExplorerSuite struct {
	tests.BillingTestSuite
}

func TestCostExplorerSuite(t *testing.T) {
	suite.Run(t, new(CostExplorerSuite))
}

func newPackageCredits(price, credits int64) *forms.PackageInfo {
	return &forms.PackageInfo{
		UnitPrices: forms.UnitPrices{
			"test_unit": {Price: decimal.NewFromInt(price)},
		},
		Discount:      decimal.NewFromInt(1),
		CreditsEnable: true,
		CreditsOnly:   true,
		Credits:       decimal.NewFromInt(credits),
	}
}

func (suite *CostExplorerSuite) TestCostExplorer() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)
	packageInfo := newPackageCredits(2, 2)

	info1, _ := (&billing.PackageModel{}).CreatePackage(packageInfo)

	bindedInfo1, _ := (&billing.BindedPackageModel{}).BindPackage(tenantID, info1.PackageID)
	bindedID1 := bindedInfo1.BindedPackageID
	_ = bindedID1
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_unit",
		Cost:          1,
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	costExplorerModel := &payment.CostExplorerModel{}
	res, err := costExplorerModel.CalculateCost(tenantID, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
	})
	assert.NoError(err)
	assert.Equal(res.CreditsCosts.CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.UnbindedInvoiceCosts.CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.BindedInvoiceCosts.CostPrice, decimal.NewFromInt(1))
}

func (suite *CostExplorerSuite) TestCostExplorerByMonth() {
	assert := suite.Assert()

	viper.Set(config.EnableCache, true)
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)
	packageInfo := newPackageCredits(2, 2)

	info1, _ := (&billing.PackageModel{}).CreatePackage(packageInfo)

	bindedInfo1, _ := (&billing.BindedPackageModel{}).BindPackage(tenantID, info1.PackageID)
	bindedID1 := bindedInfo1.BindedPackageID
	_ = bindedID1
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_unit",
		Cost:          1,
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	costExplorerModel := &payment.CostExplorerModel{}
	res, err := costExplorerModel.GetMonthlyCost(tenantID, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.Equal(res.CreditsCosts.CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.UnbindedInvoiceCosts.CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.BindedInvoiceCosts.CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(2))

	log.Info("Cache Begin")

	// should match cache
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	res, err = costExplorerModel.GetMonthlyCost(tenantID, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(2))
}

func (suite *CostExplorerSuite) TestCostExplorerPrecision() {
	assert := suite.Assert()
	fmt.Print(viper.GetBool(config.EnableCache))
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:  "test_unitA",
		Category: "category_A",
	})
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:  "test_unitB",
		Category: "category_B",
	})
	assert.NoError(err)

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	packageInfo := &forms.PackageInfo{
		UnitPrices: forms.UnitPrices{
			"test_unitA": {Price: decimal.RequireFromString("0.001")},
			"test_unitB": {Price: decimal.RequireFromString("0.001")},
		},
		Discount:      decimal.NewFromInt(1),
		CreditsEnable: true,
		CreditsOnly:   true,
		Credits:       decimal.NewFromInt(10),
	}
	info1, err := (&billing.PackageModel{}).CreatePackage(packageInfo)
	assert.NoError(err)

	bindedInfo1, _ := (&billing.BindedPackageModel{}).BindPackage(tenantID, info1.PackageID)
	bindedID1 := bindedInfo1.BindedPackageID
	_ = bindedID1
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_unitA",
		Cost:          1004,
		Labels:        map[string]string{},
	}

	unitCosts.Labels["project"] = "A"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "B"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "C"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	unitCosts.Unit = "test_unitB"

	unitCosts.Labels["project"] = "A"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "B"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "C"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &payment.CostExplorerModel{}
	res, err := costExplorerModel.GetMonthlyCost(tenantID, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	// actual value should be 6.024 , but we truncate mid level data first, and sum up to avoid precision error
	assert.Equal(res.CreditsCosts.CostPrice, decimal.NewFromInt(6))
	assert.Equal(res.CreditsCosts.CategoryUnitsCosts["category_A"].CostPrice, decimal.NewFromInt(3))
	assert.Equal(res.CreditsCosts.CategoryUnitsCosts["category_B"].CostPrice, decimal.NewFromInt(3))

	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["A"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["B"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["C"].CostPrice, decimal.NewFromInt(2))

	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["A"].CategoryUnitsCosts["category_A"].CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["A"].CategoryUnitsCosts["category_B"].CostPrice, decimal.NewFromInt(1))

}
