package tests

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/stretchr/testify/suite"
)

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

var paymentModel = &paymentsub.PaymentModel{}

func (suite *PaymentTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	api.InitializePaymentRouter(ginutils.MyRoute{Route: suite.router})
}

func TestPaymentSuite(t *testing.T) {
	suite.Run(t, new(PaymentTestSuite))
}

func (suite *PaymentTestSuite) TestCreateEndorsedPayment() {
	assert := suite.Assert()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	tests.MockCentralHandler()
	tests.MockPaymentHandler()
	w := tests.PerformRequest(suite.router, "GET", "/tenants/1/payments/types", nil)
	assert.Equal(http.StatusOK, w.Code)
	var ret1 []forms.PaymentMethodType
	err := json.NewDecoder(w.Body).Decode(&ret1)

	assert.NoError(err)
	assert.Len(ret1, 1)
	assert.Equal(ret1[0].PaymentType, entity.PaymentType_WireTransfer)
	assert.True(ret1[0].Payable)
	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/payments/setup/wire_transfer", nil)
	assert.Equal(http.StatusOK, w.Code)
	info, err := paymentModel.GetActivePaymentMethod(uint(1), 1)
	assert.NoError(err)
	assert.Equal(info.PaymentType, entity.PaymentType_WireTransfer)

	// wire_transfer to msp
	w = tests.PerformRequest(suite.router, "POST", "/tenants/1/payments/setup/msp", nil)
	assert.Equal(http.StatusBadRequest, w.Code)
	ress := gin.H{}
	json.Unmarshal(w.Body.Bytes(), &ress)
	assert.NotNil(ress["error"])
	assert.Equal(ress["error"], "Invalid Input Error: Not MSP Customer  don't support msp PaymentMethod.")

	//msp to wire_transfer
	datainit.CreateBillingConfig(100, entity.BillingConfigType_MSPCustomer)
	billingConfigID := uint(2)
	_, err = paymentModel.CreatePaymentMethodAndActive(
		100,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           entity.PaymentType_MSP,
			BindedBillingConfigID: &billingConfigID,
		},
	)
	assert.NoError(err)
	w = tests.PerformRequest(suite.router, "POST", "/tenants/100/payments/setup/wire_transfer", nil)
	assert.Equal(http.StatusBadRequest, w.Code)
	json.Unmarshal(w.Body.Bytes(), &ress)
	assert.NotNil(ress["error"])
	assert.Equal(ress["error"], "Invalid Input Error: MSP Customer PaymentMethod don't support modify.")
	info, err = paymentModel.GetActivePaymentMethod(uint(100), 2)

	assert.NoError(err)
	assert.Equal(info.PaymentType, entity.PaymentType_MSP)

	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/payments/types", nil)
	assert.Equal(http.StatusOK, w.Code)
	var ret2 []forms.PaymentMethodType
	err = json.NewDecoder(w.Body).Decode(&ret2)
	assert.NoError(err)
	assert.Len(ret2, 1)
	assert.Equal(ret2[0].PaymentType, entity.PaymentType_WireTransfer)
	assert.True(ret2[0].Payable)

}

func (suite *PaymentTestSuite) TestListPayments() {
	assert := suite.Assert()

	tests.MockCentralHandler()
	tests.MockPaymentHandler()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)
	_, err := paymentModel.CreatePaymentMethodAndActive(uint(1), &forms.PaymentMethodCreateQuery{
		PaymentType:           "mock",
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	w := tests.PerformGetRequest(suite.router, "/tenants/1/payments")
	assert.Equal(http.StatusOK, w.Code)
	ret := []forms.PaymentMethod{}
	err = json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Len(ret, 1)
	assert.Equal(ret[0].PaymentType, "mock")

	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/payments/types", nil)
	assert.Equal(http.StatusOK, w.Code)
	var ret1 []forms.PaymentMethodType
	err = json.NewDecoder(w.Body).Decode(&ret1)
	assert.NoError(err)
	assert.Len(ret1, 2)
	assert.Equal(ret1[0].PaymentType, "mock")
	assert.True(ret1[0].Payable)
	assert.Equal(ret1[1].PaymentType, entity.PaymentType_WireTransfer)
	assert.True(ret1[1].Payable)
}

func (suite *PaymentTestSuite) TestActivePayments() {
	assert := suite.Assert()
	tests.MockCentralHandler()
	tests.MockPaymentHandler()
	var err error
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)
	mock, err := paymentModel.CreatePaymentMethodAndActive(tenantID, &forms.PaymentMethodCreateQuery{
		PaymentType:           "mock",
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	_, err = paymentModel.ActiveWireTransferPayment(tenantID, billingConfigID)
	assert.NoError(err)
	w := tests.PerformGetRequest(suite.router, "/tenants/1/payments")
	assert.Equal(http.StatusOK, w.Code)
	var ret []forms.PaymentMethod
	err = json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Len(ret, 2)

	w = tests.PerformGetRequest(suite.router, "/tenants/1/payments/active")
	assert.Equal(http.StatusOK, w.Code)
	payment := forms.PaymentMethod{}
	err = json.NewDecoder(w.Body).Decode(&payment)
	assert.NoError(err)
	assert.Equal(entity.PaymentType_WireTransfer, payment.PaymentType)

	w = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/1/payments/setup/%s", mock.PaymentType), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformGetRequest(suite.router, "/tenants/1/payments/active")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&payment)
	assert.NoError(err)
	assert.Equal("mock", payment.PaymentType)
}

func (suite *PaymentTestSuite) TestUpdatePaymentsOnOps() {
	assert := suite.Assert()
	tests.MockCentralHandler()
	tests.MockPaymentHandler()
	var err error
	tenantID := uint(1)
	billingConfig, err := datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	billingConfigID := uint(1)
	invoiceable := false
	mock, err := paymentModel.CreatePaymentMethodAndActive(tenantID, &forms.PaymentMethodCreateQuery{
		PaymentType:           "mock",
		Invoiceable:           &invoiceable,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.False(mock.Invoiceable)
	assert.NoError(err)
	subCode := "$sss"
	updateQuery := forms.PaymentCodeUpdateQuery{
		PaymentCode: "$111", PaymentSubCode: &subCode,
	}
	w := tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/%d/billing_config_id/%d/payments/mock/code", billingConfig.TenantID, billingConfig.BillingConfigID), updateQuery)
	assert.Equal(http.StatusOK, w.Code)
	var payment forms.PaymentMethod
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &payment))
	assert.Equal(updateQuery.PaymentCode, payment.PaymentCode)
	assert.Equal(updateQuery.PaymentSubCode, payment.PaymentSubCode)

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/%d/billing_config_id/%d/payments/mock/invoiceable", billingConfig.TenantID, billingConfig.BillingConfigID), updateQuery)
	assert.Equal(http.StatusOK, w.Code)
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &payment))
	assert.True(payment.Invoiceable)

	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/%d/billing_config_id/%d/payments/mock/code", billingConfig.TenantID, billingConfig.BillingConfigID), updateQuery)
	assert.Equal(http.StatusForbidden, w.Code)
}
