package tests

import (
	"testing"

	"github.com/google/uuid"
	"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"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/stretchr/testify/suite"
)

type MockCentralHandlerImpl struct {
	centralhandler.CentralHandler
}

func (*MockCentralHandlerImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	switch tenantID {
	case 100:
		return &forms.Tenant{
			TenantID:                  tenantID,
			ThirdPartyAccountProvider: constant.MSPAccountProvider,
			Status:                    centralhandler.TenantStatusActive,
		}, nil
	case 200:
		return &forms.Tenant{
			TenantID:                  tenantID,
			ThirdPartyAccountProvider: constant.GCPAccountProvider,
			Status:                    centralhandler.TenantStatusActive,
		}, nil
	default:
		return &forms.Tenant{
			TenantID: tenantID,
			Status:   centralhandler.TenantStatusActive,
		}, nil
	}
}

func (*MockCentralHandlerImpl) GetProjectInfo(projectID uint) (*forms.Project, error) {
	return &forms.Project{
		ID:   projectID,
		Name: "project",
	}, nil
}

func MockCentralHandler() {
	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})
}

type MockTaxStripeHandler struct {
	paymenthandler.StripePaymentHandler
	paymenthandler.PaymentProvider
}

func (*MockTaxStripeHandler) Setup(tenant *forms.Tenant) (userCode string, err error) {
	return uuid.NewString(), nil
}

func (*MockTaxStripeHandler) Payable(uint, string) (bool, error) {
	return false, nil
}

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

func (*MockTaxStripeHandler) InactiveTaxRate(id string) error {
	return nil
}

type MockTaxPaymentHandlerImpl struct {
	paymenthandler.PaymentHandler
}

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

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

type BillingInformationSuite struct {
	tests.BillingTestSuite
}

func TestBillingInformationSuite(t *testing.T) {
	suite.Run(t, new(BillingInformationSuite))
}

var billingInfoModel = &paymentsub.BillingInformationModel{}

func (s *BillingInformationSuite) SetupTest() {
	s.BillingTestSuite.SetupTest()
	MockCentralHandler()
	MockPaymentHandler()
}

func (s *BillingInformationSuite) TestCRUDBillingInformation() {
	assert := s.Assert()
	tenantID := uint(1)
	err := datainit.CreateCompany()
	assert.NoError(err)

	info, err := billingInfoModel.CreateDefaultBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "test_code",
		},
	})
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_code")
	info, err = billingInfoModel.GetBillingInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_code")

	info, err = billingInfoModel.CreateOrUpdateBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "test_code1",
		},
		TaxInfo: forms.TaxInfo{
			TaxID: "test_vat",
		},
	})
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_code1")
	assert.Equal(info.TaxID, "test_vat")
	info, err = billingInfoModel.GetBillingInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_code1")
	assert.Equal(info.TaxID, "test_vat")

	info, err = billingInfoModel.CreateDefaultBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "test_country_code",
		},
	})
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_code1")
	assert.Equal(info.TaxID, "test_vat")
	info, err = billingInfoModel.GetBillingInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.CountryCode, "test_code1")
	assert.Equal(info.TaxID, "test_vat")
}

func (s *BillingInformationSuite) TestCRUDTenantBillingCompany() {
	assert := s.Assert()
	tenantID := uint(1)

	compaies, err := billingInfoModel.ListBillingCompany()
	assert.NoError(err)
	assert.Len(compaies, 0)

	companyEntity := entity.BillingCompany{
		CompanyKey:     "test_company_key1",
		Name:           "TestCompany",
		PaymentSubCode: "test_payment_subcode",
	}

	err = companyEntity.Create(db.GetDB())
	assert.NoError(err)

	company, err := billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, config.Get().GetString(config.DefaultBillingCompanyKey))

	info, err := billingInfoModel.UpdateTenantBillingCompany(tenantID, &forms.TenantBillingCompany{
		CompanyKey: "test_company_key1",
	})
	assert.NoError(err)
	assert.Equal(info.CompanyKey, "test_company_key1")

	info, err = billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(info.CompanyKey, "test_company_key1")

	subCode, err := billingInfoModel.GetTenantPaymentSubCode(tenantID)
	assert.NoError(err)
	assert.Equal(subCode, "test_payment_subcode")

	_, err = billingInfoModel.UpdateTenantBillingCompany(tenantID, &forms.TenantBillingCompany{
		CompanyKey: "not_exists",
	})
	assert.Error(err)
	assert.ErrorIs(err, billing_errors.ObjectNotExistsError)
}

func (s *BillingInformationSuite) TestBillingInfoWithCompany() {
	assert := s.Assert()
	tenantID := uint(1)

	config.Get().SetDefault(config.DefaultBillingCompanyKey, "test_company_key2")

	companyEntity := entity.BillingCompany{
		CompanyKey:     "test_company_key1",
		Name:           "TestCompany1",
		PaymentSubCode: "test_payment_subcode1",
	}

	err := companyEntity.Create(db.GetDB())
	assert.NoError(err)

	companyEntity = entity.BillingCompany{
		CompanyKey:     "test_company_key2",
		Name:           "TestCompany2",
		PaymentSubCode: "test_payment_subcode2",
	}

	err = companyEntity.Create(db.GetDB())
	assert.NoError(err)

	countryCompanyMap := entity.CountryCodeCompanyMap{
		CountryCode: "test_country_code",
		CompanyKey:  "test_company_key1",
	}

	err = countryCompanyMap.Create(db.GetDB())
	assert.NoError(err)

	company, err := billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, "test_company_key2")

	info, err := billingInfoModel.CreateDefaultBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "test_country_code",
		},
	})
	assert.NoError(err)

	assert.Equal(info.CountryCode, "test_country_code")

	company, err = billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, "test_company_key1")

	_, err = billingInfoModel.UpdateTenantBillingCompany(tenantID, &forms.TenantBillingCompany{
		CompanyKey: "test_company_key2",
	})
	assert.NoError(err)
	company, err = billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, "test_company_key2")

	_, err = billingInfoModel.UpdateTenantBillingCompany(tenantID, &forms.TenantBillingCompany{
		CompanyKey: "test_company_key1",
	})
	assert.NoError(err)
	company, err = billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, "test_company_key1")

	_, err = billingInfoModel.CreateOrUpdateBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "not_exists",
		},
	})
	assert.NoError(err)
	company, err = billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, "test_company_key2")
	_, err = billingInfoModel.CreateDefaultBillingInfo(tenantID, &forms.BillingInformation{
		BillingAddress: forms.BillingAddress{
			CountryCode: "test_country_code",
		},
	})
	assert.NoError(err)

	company, err = billingInfoModel.GetTenantBillingCompany(tenantID)
	assert.NoError(err)
	assert.Equal(company.CompanyKey, "test_company_key2")
}

func (s *BillingInformationSuite) TestCreateDefaultStripePayment() {
	assert := s.Assert()
	var tenantID uint = 1
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	payment, err := paymentModel.CreateDefaultStripePaymentForBillingInformation(tenantID)
	assert.NoError(err)
	assert.Equal(payment.Status, entity.PaymentMethodStatus_Active)
	tenantID = 100
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	payment, err = paymentModel.CreateDefaultStripePaymentForBillingInformation(tenantID)
	assert.NoError(err)
	assert.Equal(payment.Status, entity.PaymentMethodStatus_InActive)
	tenantID = 200
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	payment, err = paymentModel.CreateDefaultStripePaymentForBillingInformation(tenantID)
	assert.NoError(err)
	assert.Equal(payment.Status, entity.PaymentMethodStatus_InActive)
}
