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/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"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/infrahandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"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/mock"
	"github.com/stretchr/testify/suite"
)

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

func TestBudgetTestSuite(t *testing.T) {
	suite.Run(t, new(BudgetTestSuite))
}

func (suite *BudgetTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	api.InitializeBudgetRouter(ginutils.MyRoute{Route: suite.router})
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
}

func (suite *BudgetTestSuite) TestBudget() {
	assert := suite.Assert()
	tenantID := uint(1)
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/budgets", &forms.Budget{
		SpendLimitInfo: forms.SpendLimitInfo{
			SpendLimitSetting: forms.SpendLimitSetting{
				Interval:  entity.SpendLimitInterval_Monthly,
				BizType:   entity.SpendLimitBizType_Normal,
				CostType:  entity.SpendLimitCostType_Total,
				RangeType: entity.SpendLimitRangeType_Tenant,
				RangeID:   tenantID,
				Limit:     decimal.NewFromInt(1),
			},
		},
		Thresholds: []*forms.SpendLimitThreshold{{Proportion: decimal.RequireFromString("0.3")}, {Proportion: decimal.RequireFromString("0.8")}, {Proportion: decimal.RequireFromString("1")}},
	})
	assert.Equal(http.StatusOK, w.Code)
	response := forms.Budget{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitBizType_Normal, response.BizType)
	assert.Equal(entity.SpendLimitCostType_Total, response.CostType)

	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/budgets", nil)
	assert.Equal(http.StatusOK, w.Code)
	var budgetList []forms.Budget
	err = json.Unmarshal(w.Body.Bytes(), &budgetList)
	assert.NoError(err)
	assert.Len(budgetList, 1)
	assert.Equal(budgetList[0], response)
	assert.Len(budgetList[0].Thresholds, 3)

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), &forms.Budget{
		SpendLimitInfo: forms.SpendLimitInfo{
			SpendLimitSetting: forms.SpendLimitSetting{
				Limit: decimal.NewFromInt(10),
			},
		},
	})
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), response.Limit)
	bizData, err := spendLimitModel.UnmarshallNormalBizData(response.BizData)
	assert.NoError(err)
	assert.False(bizData.EnableEmailNotification)
	assert.False(bizData.EnableInAppNotification)
	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), &forms.Budget{
		SpendLimitInfo: forms.SpendLimitInfo{
			SpendLimitSetting: forms.SpendLimitSetting{
				BizData: `{"enable_email":true}`,
			},
		},
	})
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)
	bizData, err = spendLimitModel.UnmarshallNormalBizData(response.BizData)
	assert.NoError(err)
	assert.True(bizData.EnableEmailNotification)
	assert.False(bizData.EnableInAppNotification)

	w = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/1/budgets/%d/thresholds", response.SpendLimitSettingID), &forms.SpendLimitThreshold{Proportion: decimal.RequireFromString("0.53")})
	assert.Equal(http.StatusOK, w.Code)
	var threshold forms.SpendLimitThreshold
	err = json.Unmarshal(w.Body.Bytes(), &threshold)
	assert.NoError(err)
	assert.Equal(decimal.RequireFromString("0.53"), threshold.Proportion)

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

	w = tests.PerformRequest(suite.router, "GET", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), nil)
	assert.Equal(http.StatusOK, w.Code)
	var budget forms.Budget
	err = json.Unmarshal(w.Body.Bytes(), &budget)
	assert.NoError(err)
	assert.Len(budget.Thresholds, 3)

	params := struct {
		Thresholds []*forms.SpendLimitThreshold `json:"thresholds"`
	}{Thresholds: []*forms.SpendLimitThreshold{{Proportion: decimal.RequireFromString("0.1")}, {Proportion: decimal.RequireFromString("0.3")}, {Proportion: decimal.RequireFromString("0.8")}, {Proportion: decimal.RequireFromString("1")}}}
	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), params)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &budget)
	assert.NoError(err)
	assert.Len(budget.Thresholds, 4)

	params.Thresholds = []*forms.SpendLimitThreshold{{Proportion: decimal.RequireFromString("0.2")}, {Proportion: decimal.RequireFromString("1")}}
	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), params)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &budget)
	assert.NoError(err)
	assert.Len(budget.Thresholds, 2)
	params.Thresholds = []*forms.SpendLimitThreshold{{Proportion: decimal.RequireFromString("0.2")}, {Proportion: decimal.RequireFromString("0.4")}, {Proportion: decimal.RequireFromString("0.5")}, {Proportion: decimal.RequireFromString("0.6")}, {Proportion: decimal.RequireFromString("0.2")}, {Proportion: decimal.RequireFromString("1")}}

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), &forms.Budget{
		SpendLimitInfo: forms.SpendLimitInfo{
			SpendLimitSetting: forms.SpendLimitSetting{
				Name:       "2333",
				BizData:    `{"enable_email":true}`,
				RangeType:  entity.SpendLimitRangeType_Project,
				RangeIDStr: 2222,
			},
		},
	})
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &budget)
	fmt.Println(w.Body.String())
	assert.NoError(err)
	assert.Equal("2333", budget.Name)
	assert.Equal(entity.SpendLimitRangeType_Project, budget.RangeType)
	assert.Equal(uint(2222), budget.RangeID)

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), params)
	assert.Equal(http.StatusBadRequest, w.Code)

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

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", response.SpendLimitSettingID), params)
	assert.Equal(http.StatusNotFound, w.Code)

	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/budgets", nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &budgetList)
	assert.NoError(err)
	assert.Len(budgetList, 0)
}

type MockBudgetInfraHandlerImpl struct {
	mock.Mock
	infrahandler.InfraHandlerImpl
}

func (m *MockBudgetInfraHandlerImpl) GetCluster(tenantID, clusterID uint) (*forms.Cluster, error) {
	return &forms.Cluster{
		ID:       clusterID,
		Name:     constant.UnknownClusterName,
		TenantID: tenantID,
	}, nil
}

func (suite *BudgetTestSuite) TestViewServerlessBudget() {
	assert := suite.Assert()
	infrahandler.Set(&MockBudgetInfraHandlerImpl{})
	_, 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)
	err = (&entity.SpendLimitAlertThreshold{Proportion: decimal.NewFromInt(1), BizType: entity.SpendLimitBizType_ServerlessCluster}).Create(db.GetDB())
	assert.NoError(err)
	tenantID := uint(1)
	freeClusterID := uint(1)
	paidClusterID := uint(2)
	freeSpendingLimit, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Cluster,
		RangeID:   freeClusterID,
		Limit:     decimal.NewFromInt(1),
		BizType:   entity.SpendLimitBizType_ServerlessCluster,
		BizData:   "{\"free_credits\": \"5\", \"invoice_limit\": \"0\"}",
	})
	assert.NoError(err)
	paidSpendingLimit, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Cluster,
		RangeID:   paidClusterID,
		Limit:     decimal.NewFromInt(1),
		BizType:   entity.SpendLimitBizType_ServerlessCluster,
		BizData:   "{\"free_credits\": \"5\", \"invoice_limit\": \"5\"}",
	})
	assert.NoError(err)
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/budgets", &forms.Budget{
		SpendLimitInfo: forms.SpendLimitInfo{
			SpendLimitSetting: forms.SpendLimitSetting{
				Interval:  entity.SpendLimitInterval_Monthly,
				BizType:   entity.SpendLimitBizType_Normal,
				CostType:  entity.SpendLimitCostType_Total,
				RangeType: entity.SpendLimitRangeType_Tenant,
				RangeID:   tenantID,
				Limit:     decimal.NewFromInt(1),
			},
		},
		Thresholds: []*forms.SpendLimitThreshold{{Proportion: decimal.RequireFromString("0.3")}, {Proportion: decimal.RequireFromString("0.8")}, {Proportion: decimal.RequireFromString("1")}},
	})
	normalBudget := forms.Budget{}
	err = json.Unmarshal(w.Body.Bytes(), &normalBudget)
	assert.NoError(err)
	var budgetList []forms.Budget
	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/budgets", nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &budgetList)
	assert.NoError(err)
	assert.Len(budgetList, 2)
	assert.Equal(entity.SpendLimitBizType_Normal, budgetList[0].BizType)
	assert.Equal(entity.SpendLimitBizType_ServerlessCluster, budgetList[1].BizType)
	for _, b := range budgetList {
		assert.NotEqual(b.SpendLimitSettingID, freeSpendingLimit.SpendLimitSettingID)
		switch b.BizType {
		case entity.SpendLimitBizType_ServerlessCluster:
			assert.Len(b.Thresholds, 1)
			assert.Equal(b.Thresholds[0].Proportion, decimal.NewFromInt(1))
		}
	}

	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/budgets", forms.SpendLimitSetting{
		BizType: entity.SpendLimitBizType_ServerlessCluster,
	})
	assert.Equal(http.StatusBadRequest, w.Code)

	paidSpendingLimit2, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		RangeType: entity.SpendLimitRangeType_Cluster,
		RangeID:   paidClusterID + 1,
		Limit:     decimal.NewFromInt(1),
		BizType:   entity.SpendLimitBizType_ServerlessCluster,
		BizData:   "{\"free_credits\": \"5\", \"invoice_limit\": \"5\"}",
	})
	assert.NoError(err)
	assert.NoError(err)
	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/budgets", nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &budgetList)
	assert.NoError(err)
	assert.Len(budgetList, 3)
	assert.Equal(entity.SpendLimitBizType_Normal, budgetList[0].BizType)
	assert.Equal(paidSpendingLimit2.SpendLimitSettingID, budgetList[1].SpendLimitSettingID)
	assert.Equal(paidSpendingLimit.SpendLimitSettingID, budgetList[2].SpendLimitSettingID)

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/budgets/%d", paidSpendingLimit.SpendLimitSettingID), forms.SpendLimitSetting{})
	assert.Equal(http.StatusBadRequest, w.Code)

	w = tests.PerformRequest(suite.router, "DELETE", fmt.Sprintf("/tenants/1/budgets/%d", paidSpendingLimit.SpendLimitSettingID), nil)
	assert.Equal(http.StatusBadRequest, w.Code)
}
