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/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/marketing/plan"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/panicIf"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
	"github.com/stripe/stripe-go/v76"
)

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

func (suite *StripePaymentTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	api.InitializePaymentRouter(ginutils.MyRoute{Route: suite.router})
	panicIf.Error(datainit.CreateCompany())

	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey: datainit.TestUnit,
	})
	panicIf.Error(err)
	info, err := packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices: forms.UnitPrices{
				datainit.TestUnit: {Price: decimal.NewFromInt(1)},
			},
			Discount: decimal.NewFromInt(1),
		})
	panicIf.Error(err)
	config.Get().Set(config.OnDemandPackage, info.PackageID)
	config.Get().Set(config.EnableTaxCalculation, false)
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
}

func TestStripePaymentSuite(t *testing.T) {
	suite.Run(t, new(StripePaymentTestSuite))
}

type MockStripeHandler struct {
	paymenthandler.StripePaymentHandler
	paymenthandler.PaymentProvider
}

func (*MockStripeHandler) SetupIntent(userCode string) (*forms.SetupIntent, error) {
	return &forms.SetupIntent{
		PublicKey:    "mock_key",
		ClientSecret: "mock_secret",
	}, nil
}

func (*MockStripeHandler) Setup(tenant *forms.Tenant) (string, error) {
	return fmt.Sprintf("mock_%d", tenant.TenantID), nil
}

func (*MockStripeHandler) Pay(paymentInfo *forms.PaymentMethod, billingCompany *forms.BillingCompany, invoice *forms.Invoice) (*forms.Invoice, error) {
	return invoice, nil
}

func (*MockStripeHandler) NeedMultiInvoiceSendOnce() bool {
	return false
}

func (*MockStripeHandler) UpdateCustomerBillingInfo(userCode string, billingInfo *forms.BillingInformation) (*forms.StripeCustomerInfo, error) {
	return &forms.StripeCustomerInfo{ID: userCode}, nil
}

func (*MockStripeHandler) Payable(tenantID uint, userCode string) (bool, error) {
	return true, nil
}

func (*MockStripeHandler) EnableTaxCalculation() bool {
	return config.Get().GetBool(config.EnableTaxCalculation)
}

func (*MockStripeHandler) ListPaymentMethods(userCode string) ([]forms.StripePaymentMethod, error) {
	return []forms.StripePaymentMethod{
		{ID: "mock_id"},
	}, nil
}

func (m *MockStripeHandler) GetCustomerPaymentMethod(userCode, methodId string) (*forms.StripePaymentMethod, error) {
	return m.GetPaymentMethod(methodId)
}

func (*MockStripeHandler) GetPaymentMethod(methodId string) (*forms.StripePaymentMethod, error) {
	return &forms.StripePaymentMethod{
		ID: "mock_id",
		Card: &forms.Card{
			Brand: string(stripe.CardBrandDiscover),
		},
		BillingDetails: &forms.BillingDetails{Address: &forms.Address{
			Line1:      "2469 Chicago Avenue",
			City:       "Fresno",
			State:      "CA",
			Country:    "US",
			PostalCode: "93721"}},
	}, nil
}

func (*MockStripeHandler) CalculateTax(billingInfo *forms.BillingInformation, subCode string, price decimal.Decimal) (*forms.StripeCalculateTaxResult, error) {
	return &forms.StripeCalculateTaxResult{}, nil
}

func (*MockStripeHandler) ListStripeInvoicesWithCache(string) ([]*forms.StripeInvoice, error) {
	return []*forms.StripeInvoice{
		{
			Status: "Paid",
		},
	}, nil
}

func (*MockStripeHandler) SetPaymentMethodDefault(userCode, methodId string) (*forms.StripePaymentMethod, error) {
	return &forms.StripePaymentMethod{
		ID: "mock_id",
		BillingDetails: &forms.BillingDetails{
			Address: &forms.Address{
				Country: "test_country",
			},
		},
	}, nil
}

func (*MockStripeHandler) Init3DSPaymentIntent(amount decimal.Decimal, paymentMethodID string, customerId string) (*stripe.PaymentIntent, error) {
	if paymentMethodID == "pm_fail_1000" {
		return nil, billing_errors.NewCardVerificationError(nil, "invalid_number")
	}

	if paymentMethodID == "pm_fail_2000" {
		return nil, billing_errors.NewConfirmPaymentIntentError(nil)
	}

	if paymentMethodID == "pm_fail_3000" {
		return nil, billing_errors.NewConfirmPaymentIntentFail(nil)
	}

	return nil, nil
}

type MockPaymentHandlerImpl struct {
}

func (*MockPaymentHandlerImpl) GetProvider(string) (paymenthandler.PaymentProvider, error) {
	return &MockStripeHandler{}, nil
}

func (m *MockPaymentHandlerImpl) AllProviders() []string {
	return []string{entity.PaymentType_STRIPE}
}

func MockPaymentHandler() {
	paymenthandler.SetPaymentHandler(&MockPaymentHandlerImpl{})
}

func (suite *StripePaymentTestSuite) TestSetupIntent() {
	assert := suite.Assert()

	tests.MockCentralHandler()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	MockPaymentHandler()
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/payments/stripe/setup_intent", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())
	ret := forms.SetupIntent{}
	err := json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Equal(ret.ClientSecret, "mock_secret")
	assert.Equal(ret.PublicKey, "mock_key")
}

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

	tests.MockCentralHandler()
	MockPaymentHandler()
	w := tests.PerformRequest(suite.router, "GET", "/tenants/1/payments/stripe/payment_methods", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())
	ret := []forms.StripePaymentMethod{}
	err := json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Equal(len(ret), 0)
	assert.NoError(api.CreateStripeIfNotExists(1, 1))
	w = tests.PerformRequest(suite.router, "GET", "/tenants/1/payments/stripe/payment_methods", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())
	ret = []forms.StripePaymentMethod{}
	err = json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Equal(len(ret), 1)
	assert.Equal(ret[0].ID, "mock_id")
}

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

	tests.MockCentralHandler()
	MockPaymentHandler()
	assert.NoError(api.CreateStripeIfNotExists(1, 1))
	w := tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/setup_intent/confirm", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())

	tenantPlan, err := (&plan.TenantPlanModel{}).GetTenantOnDemandPlanInfo(1, 1)
	assert.NoError(err)
	assert.Equal(entity.TenantPlanStatus_Active, tenantPlan.Status)
}

func (suite *StripePaymentTestSuite) TestSetDefaultPayment() {
	assert := suite.Assert()

	tests.MockCentralHandler()
	MockPaymentHandler()
	assert.NoError(api.CreateStripeIfNotExists(1, 1))
	w := tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/payment_methods/mock_id/default", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())
}

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

	tests.MockCentralHandler()
	MockPaymentHandler()
	assert.NoError(api.CreateStripeIfNotExists(1, 1))
	w := tests.PerformRequest(suite.router, "GET", "/tenants/1/payments/stripe/invoices", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())
	resp := []forms.StripeInvoice{}
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
	assert.Len(resp, 1)
	assert.Equal(resp[0].Status, "Paid")
}

func (suite *StripePaymentTestSuite) TestSetupIntentConfirmUpdateBillingInfo() {
	suite.T().Skip("do it on the frontend called")
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

	tests.MockCentralHandler()
	MockPaymentHandler()
	assert.NoError(api.CreateStripeIfNotExists(tenantID, 1))
	w := tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/setup_intent/confirm", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())
	billingInfoModel := &paymentsub.BillingInformationModel{}

	info, err := billingInfoModel.GetBillingInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_country")
}

func (suite *StripePaymentTestSuite) TestSetupIntentConfirmUpdateBillingInfoAfterSetted() {
	assert := suite.Assert()
	tenantID := uint(1)
	tests.MockCentralHandler()
	MockPaymentHandler()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	assert.NoError(api.CreateStripeIfNotExists(tenantID, 1))
	billingInfoModel := &paymentsub.BillingInformationModel{}
	_, err := billingInfoModel.CreateOrUpdateBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "country_code_setted",
		},
	})
	assert.NoError(err)

	w := tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/setup_intent/confirm", nil)
	assert.Equal(http.StatusOK, w.Code, w.Body.String())

	info, err := billingInfoModel.GetBillingInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.CountryCode, "country_code_setted")
}

func (suite *StripePaymentTestSuite) TestCreditCardValidate() {
	// init test env
	assert := suite.Assert()
	tests.MockCentralHandler()
	MockPaymentHandler()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	// create test stripe customer firstly
	assert.NoError(api.CreateStripeIfNotExists(1, 1))

	// mock http request: confirm PaymentIntent success
	w := tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/payment_methods/pm_suc/validate", nil)
	assert.Equal(http.StatusOK, w.Code)

	resp := map[string]string{}
	// mock http request: confirm PaymentIntent fail, error == billing_errors.NewCardVerificationError
	w = tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/payment_methods/pm_fail_1000/validate", nil)
	assert.Equal(http.StatusBadRequest, w.Code)
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
	assert.Equal(resp["code"], "invalid_number")

	// mock http request: confirm PaymentIntent error, error == billing_errors.NewConfirmPaymentIntentError
	w = tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/payment_methods/pm_fail_2000/validate", nil)
	assert.Equal(http.StatusBadRequest, w.Code)
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
	assert.Equal(resp["code"], "PaymentIntentConfirmError")

	// mock http request: confirm PaymentIntent fail, error == billing_errors.NewConfirmPaymentIntentFail
	w = tests.PerformRequest(suite.router, "PUT", "/tenants/1/payments/stripe/payment_methods/pm_fail_3000/validate", nil)
	assert.Equal(http.StatusBadRequest, w.Code)
	assert.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
	assert.Equal(resp["code"], "PaymentIntentConfirmFail")
}
