package tests

import (
	"fmt"
	"testing"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/creditriskcontrol"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
)

type TenantCloseModelSuite struct {
	tests.BillingTestSuite
}

func TestTenantCloseModelSuite(t *testing.T) {
	suite.Run(t, new(TenantCloseModelSuite))
}

var closeModel = &creditriskcontrol.TenantCloseModel{}

func (suite *TenantCloseModelSuite) TestCRUDPolicy() {
	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	policy := &forms.TenantClosePolicy{}

	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	policyInfo, err := closeModel.GetTenantClosePolicyByCreditRiskLevel(creditRiskID)
	assert.NoError(err)
	assert.Equal(policyInfo.CreditRiskLevelID, creditRiskID)

	err = closeModel.UpdateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	err = closeModel.DeleteTenantRiskClosePolicy(creditRiskID)
	assert.NoError(err)

	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)
}

func (suite *TenantCloseModelSuite) TestCreatePolicyInvalid() {
	assert := suite.Assert()
	policy := &forms.TenantClosePolicy{}

	err := closeModel.CreateTenantRiskClosePolicy(1, policy)
	assert.Error(err)

	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.Error(err)

	assert.ErrorIs(err, billing_errors.ObjectAlreadyExists)
}

func (suite *TenantCloseModelSuite) TestRunTenantClose() {
	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	fmt.Printf("%+v", err)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	policy := &forms.TenantClosePolicy{}
	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	tenantID := uint(1)
	err = creditRiskLevelModel.SetTenantCreditRiskLevel(tenantID, creditRiskID)
	assert.NoError(err)

	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)
	info, err := closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)
}

func (suite *TenantCloseModelSuite) TestCacnelTenantCloseAndRerun() {
	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	policy := &forms.TenantClosePolicy{}
	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	tenantID := uint(1)
	err = creditRiskLevelModel.SetTenantCreditRiskLevel(tenantID, creditRiskID)
	assert.NoError(err)

	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)
	info, err := closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)

	err = closeModel.CancelTenantClose(tenantID)
	assert.NoError(err)

	info, err = closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_CANCELING)

	err = closeModel.ProcessTenantClose(tenantID)
	assert.NoError(err)

	info, err = closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.Status, entity.TenantCloseStatus_CANCELED)

	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)
	info, err = closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)
}

type TestRunner struct {
	mock.Mock
}

func (m *TestRunner) Run(tenantID uint, parms *creditriskcontrol.ActionParams) error {
	return m.Called(tenantID).Error(0)
}

func (suite *TenantCloseModelSuite) TestProcessTenantClose() {
	testRunner := TestRunner{}
	creditriskcontrol.RegisterCloseAction("test", testRunner.Run)

	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	policy := &forms.TenantClosePolicy{
		ClosePolicy: forms.ClosePolicy{
			Actions: []*forms.CloseAction{
				{
					Action:           "test",
					ActionTimeInHour: 0,
				},
			},
		},
	}
	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	tenantID := uint(1)
	err = creditRiskLevelModel.SetTenantCreditRiskLevel(tenantID, creditRiskID)
	assert.NoError(err)

	timeutils.SetMockNow(datainit.BillingDate)
	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)
	info, err := closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)

	testRunner.On("Run", tenantID).Return(nil)
	err = closeModel.ProcessTenantClose(tenantID)
	assert.NoError(err)

	info, err = closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_DONE)
	assert.Equal(info.CloseActionsStatus.Status["test"], entity.TenantCloseStatus_DONE)

	assert.Len(testRunner.Calls, 1)
}

func (suite *TenantCloseModelSuite) TestProcessTenantCloseRunnerFailed() {
	testRunner := TestRunner{}
	creditriskcontrol.RegisterCloseAction("test", testRunner.Run)

	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	policy := &forms.TenantClosePolicy{
		ClosePolicy: forms.ClosePolicy{
			Actions: []*forms.CloseAction{
				{
					Action:           "test",
					ActionTimeInHour: 0,
				},
			},
		},
	}
	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	tenantID := uint(1)
	err = creditRiskLevelModel.SetTenantCreditRiskLevel(tenantID, creditRiskID)
	assert.NoError(err)

	timeutils.SetMockNow(datainit.BillingDate)
	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)
	info, err := closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)

	testRunner.On("Run", tenantID).Return(billing_errors.SystemError)
	err = closeModel.ProcessTenantClose(tenantID)
	assert.Error(err)
	assert.ErrorIs(err, billing_errors.SystemError)

	info, err = closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)
	assert.Equal(info.CloseActionsStatus.Status["test"], "")

	assert.Len(testRunner.Calls, 1)
}

func (suite *TenantCloseModelSuite) TestProcessTenantCloseRunnerTimeNotMatch() {
	testRunner := TestRunner{}
	creditriskcontrol.RegisterCloseAction("test", testRunner.Run)

	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskLevelModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)

	creditRiskID := creditRiskInfo.CreditRiskLevelID

	policy := &forms.TenantClosePolicy{
		ClosePolicy: forms.ClosePolicy{
			Actions: []*forms.CloseAction{
				{
					Action:           "test",
					ActionTimeInHour: 1,
				},
			},
		},
	}
	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, policy)
	assert.NoError(err)

	tenantID := uint(1)
	err = creditRiskLevelModel.SetTenantCreditRiskLevel(tenantID, creditRiskID)
	assert.NoError(err)

	timeutils.SetMockNow(datainit.BillingDate)
	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)
	info, err := closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)

	err = closeModel.ProcessTenantClose(tenantID)
	assert.NoError(err)

	info, err = closeModel.GetTenantCloseInfo(tenantID)
	assert.NoError(err)
	assert.Equal(info.TenantID, tenantID)
	assert.Equal(info.CreditRiskLevelID, creditRiskID)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)
	assert.Equal(info.CloseActionsStatus.Status["test"], "")

	assert.Len(testRunner.Calls, 0)
}
