package tests

import (
	"context"
	"testing"
	"time"

	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/dbutil"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/db/tenant"
	"github.com/pingcap/dbaas/central/handler/billing2"
	"github.com/pingcap/dbaas/central/model/billing"
	"github.com/pingcap/dbaas/central/test"
	"github.com/pingcap/log"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
	mailv2 "github.com/tidbcloud/pkgv2/mail/v2"
	"github.com/tidbcloud/pkgv2/salesforce"
	"gorm.io/gorm"
)

type BillingPOCTestSuite struct {
	test.CentralTestSuite
	tenant         *entity.Tenant
	model          billing.Model
	context        billing.Context
	db             *gorm.DB
	billingHandler *MockBillingHandler
	mail           *MockMailHandler
}

func TestPOCSuite(t *testing.T) {
	suite.Run(t, new(BillingPOCTestSuite))
}

func (s *BillingPOCTestSuite) SetupTest() {
	s.CentralTestSuite.SetupTest()
	s.createTenantAndAdminUser()
	s.createModelAndContext()
}

type MockSalesforceHandler struct {
}

func (m *MockSalesforceHandler) UpdateAccount(account salesforce.Account) error {
	return nil
}

func (m *MockSalesforceHandler) SyncLeads(lead salesforce.Lead) error {
	return nil
}

type MockMailHandler struct {
	mock.Mock
}

func (m *MockMailHandler) SendMailWithTemplate(ctx context.Context, to []string, subject, templateName string, vars map[string]string, opts *mailv2.SendMailOpts) error {
	m.Called()
	return nil
}

type MockBillingHandler struct {
	billing2.BillingHandler
	mock.Mock
}

func (m *MockBillingHandler) BindPackage(tenantID, packageID uint64) (uint64, error) {
	return m.Called().Get(0).(uint64), nil
}

func (m *MockBillingHandler) GetCredits(tenantID, bindedPackageID uint64) (*decimal.Decimal, error) {
	return m.Called().Get(0).(*decimal.Decimal), nil
}

func (m *MockBillingHandler) GetCreditsHistory(tenantID, bindedPackageID uint64) (*billing2.CreditsChargeHistory, error) {
	return &billing2.CreditsChargeHistory{
		TotalAmounts: decimal.NewFromInt(100),
		History: []billing2.CreditsChargeInfo{
			{
				ChargeCount: decimal.NewFromInt(100),
				ChargeTime:  time.Now(),
			},
		},
	}, nil
}

func (m *MockBillingHandler) ChargeCredits(tenantID, bindedPackageID uint64, input *billing2.CreditsChargeInfoInput) error {
	_ = m.Called()
	return nil
}

func (s *BillingPOCTestSuite) createBillingHandler() {
	s.billingHandler = &MockBillingHandler{}
	s.billingHandler.On("GetCredits").Return(&decimal.Zero)
	s.billingHandler.On("BindPackage").Return(uint64(1), nil)
	billing2.SetHandler(s.billingHandler)

}

func (s *BillingPOCTestSuite) createModelAndContext() {
	mailHandler := &MockMailHandler{}
	mailHandler.On("SendMailWithTemplate").Return().Maybe()
	s.mail = mailHandler
	s.model = billing.NewModel(log.L(), s.mail, &MockSalesforceHandler{})
	s.context = s.model.User(&tenant.User{TenantID: s.tenant.ID})
	s.createBillingHandler()
}

func (s *BillingPOCTestSuite) createTenantAndAdminUser() {
	dbProvider := db.NewProvider()
	ctx := context.Background()
	s.db = dbProvider.WriteDB(ctx)
	tenant := &entity.Tenant{
		Name:          "unittest tenant",
		Email:         "unittest@pingcap.com",
		Country:       "CN",
		TimeZone:      "8",
		Status:        entity.TenantStatusActive,
		Plan:          entity.TenantPlanNewUser,
		AcceptService: false,
		ZendeskID:     0,
		Quotas:        nil,
		BillingEmail:  "",
	}
	err := tenant.Create(s.db)
	if err != nil {
		s.FailNow("failed to create tenant for test %s", err)
	}
	// init fake user
	dbUser := &entity.User{
		TenantID:  tenant.ID,
		FirstName: "FirstName",
		LastName:  "LastName",
		Email:     "unittest@pingcap.com",
		Status:    entity.UserStatusActive,
	}

	err = dbUser.Create(s.db)
	if err != nil {
		s.FailNow("failed to create tenant for user %s", err)
	}

	s.tenant = tenant
}

func (s *BillingPOCTestSuite) TestPOCIsEnabled() {
	assert := s.Assert()
	_ = assert
	enable, err := s.context.IsPOCEnabled()
	assert.NoError(err)
	assert.False(enable)

	s.tenant.Plan = entity.TenantPlanPOC
	assert.NoError(s.tenant.Update(s.db, entity.TenantDBSchema.Plan))
	assert.NoError(dbutil.AddPOCTrialRecord(context.Background(), s.db, *s.tenant))
	enable, err = s.context.IsPOCEnabled()
	assert.NoError(err)
	assert.True(enable)

	assert.NoError(dbutil.MarkPOCTrialAsEnd(context.Background(), s.db, *s.tenant))
	enable, err = s.context.IsPOCEnabled()
	assert.NoError(err)
	assert.False(enable)
}

func (s *BillingPOCTestSuite) TestEnablePOC() {
	assert := s.Assert()
	_ = assert
	assert.NoError(s.context.EnableOrgPOCPlan())
	s.billingHandler.AssertCalled(s.T(), "BindPackage")

	tenant, err := dbutil.GetTenantByID(s.db, s.tenant.ID)
	assert.NoError(err)
	assert.EqualValues(tenant.POCBindedPackageID, 1)

	enable, err := s.context.IsPOCEnabled()
	assert.NoError(err)
	assert.True(enable)
}

func (s *BillingPOCTestSuite) TestPOCAddCredits() {
	assert := s.Assert()
	_ = assert

	req := billing.AddPOCCreditReq{
		TenantID:     s.tenant.ID.Uint64(),
		CreditAmount: 10,
		Description:  "test_desc",
	}

	assert.Error(s.context.AddPOCCredits(&req))

	assert.NoError(s.context.EnableOrgPOCPlan())

	s.billingHandler.On("ChargeCredits").Return(nil).Once()
	assert.NoError(s.context.AddPOCCredits(&req))
	s.billingHandler.Mock.AssertExpectations(s.T())

	assert.NoError(s.context.DisableOrgPOCPlan())

	s.billingHandler.On("ChargeCredits").Return(nil).Once()
	assert.NoError(s.context.AddPOCCredits(&req))
	s.billingHandler.Mock.AssertExpectations(s.T())
	enable, err := s.context.IsPOCEnabled()
	assert.NoError(err)
	assert.True(enable)
}

func (s *BillingPOCTestSuite) TestGetPOCCredits() {
	assert := s.Assert()
	_ = assert
	_, err := s.context.GetPOCCredits()
	assert.Error(err)
	assert.NoError(s.context.EnableOrgPOCPlan())

	value, err := s.context.GetPOCCredits()
	assert.NoError(err)
	assert.Zero(value)

	credits := decimal.NewFromInt(10000)

	s.billingHandler.ExpectedCalls = nil
	s.billingHandler.On("GetCredits").Return(&credits)

	value, err = s.context.GetPOCCredits()
	assert.NoError(err)
	assert.EqualValues(1000000, value)
}

func (s *BillingPOCTestSuite) TestGetPOCCreditsHistory() {
	assert := s.Assert()
	_ = assert
	assert.NoError(s.context.EnableOrgPOCPlan())

	value, err := s.context.GetPOCCreditsHistory()
	assert.NoError(err)
	assert.Len(value.History, 1)

}

func (s *BillingPOCTestSuite) TestDisablePOC() {
	assert := s.Assert()
	_ = assert
	assert.NoError(s.context.EnableOrgPOCPlan())

	assert.NoError(s.context.DisableOrgPOCPlan())

	s.mail.AssertCalled(s.T(), "SendMailWithTemplate")
	s.mail.Calls = nil

	enable, err := s.context.IsPOCEnabled()
	assert.NoError(err)
	assert.False(enable)

	assert.NoError(s.context.DisableOrgPOCPlan())
	s.mail.AssertNotCalled(s.T(), "SendMailWithTemplate")
	s.mail.Calls = nil
}
