package tests

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

	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler/impl"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/pp"

	"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/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/marketing/discount"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"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"
)

var discountModel = &discount.Model{}

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

func (suite *TenantDiscountTestSuite) SetupTest() {
	assert := suite.Assert()

	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	api.RegisterRouter(ginutils.MyRoute{Route: suite.router})
	tenantID := uint(1)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	packageModel := &billing.PackageModel{}

	packageInfo, err := packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices:           forms.UnitPrices{},
			Discount:             decimal.NewFromInt(1),
			TenantMaxBindedTimes: 1,
		})
	assert.NoError(err)
	config.Get().Set(config.OnDemandPackage, packageInfo.PackageID)

	generalUnitFilter, err := (&unit_filter.Model{}).CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	assert.NoError(err)
	config.Get().Set(config.GeneralCreditTypeID, generalUnitFilter.FilterID)
	tests.MockCentralHandler()

}

func TestTenantDiscountTestSuiteSuite(t *testing.T) {
	suite.Run(t, new(TenantDiscountTestSuite))
}

func (suite *TenantDiscountTestSuite) TestGetGlobalDiscounts() {
	assert := suite.Assert()

	w := tests.PerformGetRequest(suite.router, "/plans/ondemand/global-discounts")
	assert.Equal(http.StatusOK, w.Code)
	response := forms.Discount{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)

	assert.Equal(response.Value, decimal.RequireFromString("1.0"))
}

func (suite *TenantDiscountTestSuite) TestUpdateGlobalDiscounts() {
	assert := suite.Assert()

	w := tests.PerformRequest(suite.router, "PUT", "/plans/ondemand/global-discounts", forms.Discount{
		Value: decimal.RequireFromString("0.5"),
	})
	assert.Equal(http.StatusBadRequest, w.Code)
}

func (suite *TenantDiscountTestSuite) TestGetTenantDiscounts() {
	assert := suite.Assert()
	tenantID := uint(1)
	d := decimal.RequireFromString("0.5")
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)

	_, err := discountModel.SetDiscount(&forms.ChangeBindedDiscountInput{
		BindedDiscount:        d,
		ChangerTenantID:       tenantID,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, "/tenants/1/plans/ondemand/discounts")
	assert.Equal(http.StatusOK, w.Code)
	response := forms.Discount{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)

	assert.Equal(response.Value, decimal.RequireFromString("0.5"))
}

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

	w := tests.PerformRequest(suite.router, "PUT", "/tenants/1/plans/ondemand/discounts", forms.Discount{
		Value: decimal.RequireFromString("0.5"),
	})
	assert.Equal(http.StatusOK, w.Code)

	res, err := discountModel.GetTenantOnDemandDiscount(tenantID, 1)
	assert.NoError(err)
	assert.Equal(res.Value, decimal.RequireFromString("0.5"))
}

func (suite *TenantDiscountTestSuite) TestGetTenantEffectiveDiscounts() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)

	d := decimal.RequireFromString("0.8")

	_, err := discountModel.SetDiscount(&forms.ChangeBindedDiscountInput{
		BindedDiscount:        d,
		ChangerTenantID:       tenantID,
		BindedBillingConfigID: &billingConfigID,
	})

	w := tests.PerformGetRequest(suite.router, "/tenants/1/plans/ondemand/effective-discounts")
	assert.Equal(http.StatusOK, w.Code)
	response := forms.Discount{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(err)

	assert.Equal(response.Value, decimal.RequireFromString("0.8"))
}

func (suite *TenantDiscountTestSuite) TestDiscountsCRUD() {
	var err error
	var w *httptest.ResponseRecorder
	assert := suite.Assert()
	timeutils.SetMockNow(datainit.BillingDate)
	now := timeutils.GetNow()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

	tenantDiscount := decimal.RequireFromString("0.77")
	req := &forms.Discount{
		Value:       tenantDiscount,
		StartedAt:   now,
		ExpiredAt:   now.AddDate(-1, 0, 0),
		UnitFilters: []*forms.UnitFilterSlim{{FilterID: config.Get().GetUint(config.GeneralCreditTypeID)}},
	}
	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/discounts", req)
	assert.Equal(http.StatusBadRequest, w.Code)
	req.ExpiredAt = now.AddDate(1, 0, 0)
	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/discounts", req)
	assert.Equal(http.StatusOK, w.Code)
	td := forms.Discount{}
	err = json.Unmarshal(w.Body.Bytes(), &td)
	assert.NoError(err)
	assert.Equal(tenantDiscount, td.Value)
	assert.Equal(uint(1), td.TenantID)

	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/discounts", nil)
	assert.Equal(http.StatusOK, w.Code)
	listResp := []forms.Discount{}
	err = json.Unmarshal(w.Body.Bytes(), &listResp)
	assert.NoError(err)
	assert.Len(listResp, 1)
	assert.Equal(tenantID, listResp[0].TenantID)

	w = tests.PerformRequest(suite.router, "GET", "/global-discounts", nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &listResp)
	assert.NoError(err)
	assert.Len(listResp, 0)

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

	req.Value = decimal.Zero
	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprint("/tenants/1/discounts/", td.DiscountID), req)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &td)
	assert.NoError(err)
	assert.Equal(decimal.Zero, td.Value)

	req.Value = decimal.Zero
	w = tests.PerformRequest(suite.router, "DELETE", fmt.Sprint("/tenants/1/discounts/", td.DiscountID), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/users/discounts", nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &listResp)
	assert.NoError(err)
	assert.Len(listResp, 1)
	timeutils.SetMockNow(timeutils.GetNow().Add(config.Get().GetDuration(config.DiscountInactiveDelayHour) * time.Hour))
	rows, err := discountModel.InactiveTimeoutDiscount()
	assert.NoError(err)
	assert.Equal(int64(1), rows)
	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/users/discounts", nil)
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &listResp)
	assert.NoError(err)
	assert.Len(listResp, 1)
	for _, d := range listResp {
		assert.Equal(constant.DiscountStatus_Reclaimed, d.Status)
	}

	paymenthandler.SetPaymentHandler(&impl.PaymentHandlerImpl{})
	billingConfigID := uint(2)
	datainit.CreateBillingConfig(100, entity.BillingConfigType_MSP)

	_, err = (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(
		100,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           entity.PaymentType_MSP,
			BindedBillingConfigID: &billingConfigID,
		},
	)
	r := tests.PerformRequest(suite.router, "POST", "/tenants/100/discounts", req)
	assert.Equal(http.StatusBadRequest, r.Code)
	ress := gin.H{}
	json.Unmarshal(r.Body.Bytes(), &ress)
	pp.Print(ress)
	assert.NotNil(ress["error"])
	assert.Equal(ress["error"], "Invalid Input Error: MSP Customer  don't support discount.")

}
