package tests

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/payment/spend_limit"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"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 SpendLimitTestSuite struct {
	tests.BillingTestSuite
	router *gin.Engine
}

func TestSpendLimitTestSuite(t *testing.T) {
	suite.Run(t, new(SpendLimitTestSuite))
}

var spendLimitModel = &spend_limit.SpendLimitModel{}

func (suite *SpendLimitTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	assert := suite.Assert()
	api.InitializeSpendLimitRouter(ginutils.MyRoute{Route: suite.router})
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey: datainit.TestUnit,
	})
	assert.NoError(err)
	packageInfo, err := (&billing.PackageModel{}).CreatePackage(&forms.PackageInfo{
		UnitPrices: forms.UnitPrices{
			datainit.TestUnit: {Price: decimal.NewFromInt(1)},
		},
		Discount:      decimal.NewFromInt(1),
		CreditsEnable: true,
		CreditsOnly:   true,
	})
	assert.NoError(err)
	config.Get().Set(config.CreditsPricePackage, packageInfo.PackageID)
	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate
	}
	config.Get().Set(config.RechargeCreditsBeforeSeconds, 10800)
}

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

	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/spend_limits", &forms.SpendLimitSettingInput{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
		Limit:     decimal.NewFromInt(1),
	})
	assert.Equal(http.StatusOK, w.Code)
	response := forms.SpendLimitSetting{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)

	assert.Equal(response.Limit, decimal.NewFromInt(1))

	res, err := spendLimitModel.GetSpendLimitSetting(tenantID, response.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(res.Interval, entity.SpendLimitInterval_Monthly)

	clusterID := uint(23)
	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/spend_limits/%d", response.SpendLimitSettingID), &forms.SpendLimitSettingInput{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Cluster,
		RangeID:   clusterID,
		Limit:     decimal.NewFromInt(2),
	})
	assert.Equal(http.StatusOK, w.Code)
	response = forms.SpendLimitSetting{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)

	assert.Equal(decimal.NewFromInt(2), response.Limit)
	assert.Equal(entity.SpendLimitRangeType_Cluster, response.RangeType)

	w = tests.PerformRequest(suite.router, "DELETE", fmt.Sprintf("/tenants/1/spend_limits/%d", response.SpendLimitSettingID), nil)
	assert.Equal(http.StatusOK, w.Code)

	_, err = spendLimitModel.GetSpendLimitSetting(tenantID, response.SpendLimitSettingID)
	assert.ErrorIs(err, billing_errors.ObjectNotExistsError)
}

func (suite *SpendLimitTestSuite) TestListSpendLimitSetting() {
	var err error
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate
	}

	_, err = spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
		Limit:     decimal.NewFromInt(1),
	})
	assert.NoError(err)

	clusterID := uint(1)
	settings, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Cluster,
		RangeID:   clusterID,
		Limit:     decimal.NewFromInt(1),
		BizType:   entity.SpendLimitBizType_ServerlessCluster,
		BizData:   "{\"free_credits\": \"5\", \"invoice_limit\": \"0\"}",
	})
	assert.NoError(err)
	bizData, err := spendLimitModel.UnmarshallServerlessBizData(settings.BizData)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), bizData.ActiveTotalCredits)
	assert.Equal(bizData.FreeCredits, bizData.ActiveTotalCredits)

	w := tests.PerformGetRequest(suite.router, "/tenants/1/spend_limits")
	assert.Equal(http.StatusOK, w.Code)
	response := []forms.SpendLimitSetting{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	assert.Len(response, 2)

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/spend_limits?biz_type=%s", entity.SpendLimitBizType_ServerlessCluster))
	assert.Equal(http.StatusOK, w.Code)
	response = []forms.SpendLimitSetting{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	assert.Len(response, 1)

	err = datainit.CreateMeasures(tenantID)
	assert.NoError(err)
	unitCosts := forms.UnitCosts{
		CostTimeStamp: datainit.BillingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
		Labels:        map[string]string{constant.ClusterLabelKey: fmt.Sprint(clusterID)},
	}
	_, err = (&billing.MeasureModel{}).CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/spend_limits/info?biz_type=%s", entity.SpendLimitBizType_ServerlessCluster))
	assert.Equal(http.StatusOK, w.Code)
	infoListResponse := []forms.SpendLimitInfo{}
	err = json.Unmarshal(w.Body.Bytes(), &infoListResponse)
	assert.NoError(err)
	assert.Len(response, 1)
	assert.Equal(decimal.NewFromInt(1), infoListResponse[0].Costs)
	assert.Equal(datainit.BillingDate.Year(), infoListResponse[0].StartDate.Year())
	assert.Equal(datainit.BillingDate.Month(), infoListResponse[0].StartDate.Month())

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/spend_limits/%d/info", infoListResponse[0].SpendLimitSettingID))
	assert.Equal(http.StatusOK, w.Code)
	infoResponse := forms.SpendLimitInfo{}
	err = json.Unmarshal(w.Body.Bytes(), &infoResponse)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(1), infoResponse.Costs)
	assert.Equal(datainit.BillingDate.Year(), infoResponse.StartDate.Year())
	assert.Equal(datainit.BillingDate.Month(), infoResponse.StartDate.Month())

	w = tests.PerformGetRequest(suite.router, "/spend_limits")
	assert.Equal(http.StatusOK, w.Code)
	response = []forms.SpendLimitSetting{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	assert.Len(response, 2)

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/spend_limits?biz_type=%s", entity.SpendLimitBizType_ServerlessCluster))
	assert.Equal(http.StatusOK, w.Code)
	response = []forms.SpendLimitSetting{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	assert.Len(response, 1)
}

func (suite *SpendLimitTestSuite) TestGetSpendLimitSetting() {
	assert := suite.Assert()
	tenantID := uint(1)

	info, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
		Limit:     decimal.NewFromInt(1),
	})

	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/spend_limits/%d", info.SpendLimitSettingID))
	assert.Equal(http.StatusOK, w.Code)
	response := forms.SpendLimitSetting{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)

	assert.Equal(response.Limit, decimal.NewFromInt(1))
}
