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/config"
	"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/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/stretchr/testify/suite"
)

var closeModel = &creditriskcontrol.TenantCloseModel{}

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

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

func TestTenantCreditRiskCloseSuite(t *testing.T) {
	suite.Run(t, new(TenantCreditRiskCloseSuite))
}

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

	creditRiskID := creditRiskInfo.CreditRiskLevelID
	w := tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/credit_risk_levels/%d/risk_close_policy", creditRiskID), &forms.TenantClosePolicyInput{
		ClosePolicy: forms.ClosePolicy{
			Actions: []*forms.CloseAction{
				{
					Action:           "test",
					ActionTimeInHour: 1,
				},
			},
		},
	})
	assert.Equal(http.StatusOK, w.Code)

	info, err := closeModel.GetTenantClosePolicyByCreditRiskLevel(creditRiskID)
	assert.NoError(err)
	assert.Len(info.ClosePolicy.Actions, 1)
	assert.Equal(info.ClosePolicy.Actions[0].Action, "test")
	assert.EqualValues(info.ClosePolicy.Actions[0].ActionTimeInHour, 1)
	assert.Nil(info.ClosePolicy.CancelAction)
}

func (suite *TenantCreditRiskCloseSuite) TestGetRiskPolicy() {
	assert := suite.Assert()
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)
	creditRiskID := creditRiskInfo.CreditRiskLevelID

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

	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, fmt.Sprintf("/credit_risk_levels/%d/risk_close_policy", creditRiskID))
	assert.Equal(http.StatusOK, w.Code)
	info := &forms.TenantClosePolicy{}
	err = json.Unmarshal(w.Body.Bytes(), info)
	assert.NoError(err)

	assert.Len(info.ClosePolicy.Actions, 1)
	assert.Equal(info.ClosePolicy.Actions[0].Action, "test")
	assert.EqualValues(info.ClosePolicy.Actions[0].ActionTimeInHour, 1)
	assert.Nil(info.ClosePolicy.CancelAction)
}

func (suite *TenantCreditRiskCloseSuite) TestGetTenantCreditRiskCloseInfo() {
	assert := suite.Assert()

	tenantID := uint(1)

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

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

	assert.NoError(err)

	err = creditRiskModel.SetTenantCreditRiskLevel(tenantID, creditRiskID)
	assert.NoError(err)

	err = closeModel.RunTenantClose(tenantID, entity.TenantCloseReason_Risky)
	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/%d/tenant_risk_close", tenantID))
	assert.Equal(http.StatusOK, w.Code)
	info := &forms.TenantCloseInfo{}
	err = json.Unmarshal(w.Body.Bytes(), info)
	assert.NoError(err)

	assert.Len(info.ClosePolicy.Actions, 1)
	assert.Equal(info.ClosePolicy.Actions[0].Action, "test")
	assert.EqualValues(info.ClosePolicy.Actions[0].ActionTimeInHour, 1)
	assert.Nil(info.ClosePolicy.CancelAction)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)
}

func (suite *TenantCreditRiskCloseSuite) TestTenantClosureCRUD() {
	assert := suite.Assert()
	tenantID := uint(1)
	creditCreditRiskLevel := &forms.CreditRiskLevel{
		Name: "test",
	}
	creditRiskInfo, err := creditRiskModel.CreateCreditRiskLevel(creditCreditRiskLevel)
	assert.NoError(err)
	creditRiskID := creditRiskInfo.CreditRiskLevelID
	config.Get().Set(config.DefaultCreditRiskLevel, creditRiskID)

	closePolicy := forms.ClosePolicy{
		Actions: []*forms.CloseAction{
			{
				Action:           "test",
				ActionTimeInHour: 1,
			},
		},
	}
	err = closeModel.CreateTenantRiskClosePolicy(creditRiskID, &forms.TenantClosePolicy{
		ClosePolicy: closePolicy,
	})
	assert.NoError(err)
	closePolicy.Actions = append(closePolicy.Actions, &forms.CloseAction{Action: "test2", ActionTimeInHour: 3})
	_, err = closeModel.CreateTenantClosePolicy(&forms.TenantClosePolicy{
		Reason:      entity.TenantCloseReason_Unpayable,
		ClosePolicy: closePolicy,
	})
	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/%d/closing/reasons", tenantID))
	assert.Equal(http.StatusOK, w.Code)
	var reasons []string
	err = json.Unmarshal(w.Body.Bytes(), &reasons)
	assert.NoError(err)
	assert.Len(reasons, 2)
	assert.Equal(reasons[0], entity.TenantCloseReason_Risky)
	assert.Equal(reasons[1], entity.TenantCloseReason_Unpayable)

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/%d/closing/reasons/%s/policies", tenantID, entity.TenantCloseReason_Unpayable))
	assert.Equal(http.StatusOK, w.Code)
	var policy forms.TenantClosePolicy
	err = json.Unmarshal(w.Body.Bytes(), &policy)
	assert.NoError(err)
	assert.Equal(entity.TenantCloseReason_Unpayable, policy.Reason)
	assert.Equal(closePolicy, policy.ClosePolicy)

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/%d/closing", tenantID))
	assert.Equal(http.StatusOK, w.Code)
	var info forms.TenantCloseInfo
	err = json.Unmarshal(w.Body.Bytes(), &info)
	assert.NoError(err)
	assert.Equal(info.Status, entity.TenantCloseStatus_NOTSTARTED)

	w = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/closing/reasons/%s/actions/run", tenantID, entity.TenantCloseReason_Unpayable), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/%d/closing", tenantID))
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &info)
	assert.NoError(err)
	assert.Equal(info.Status, entity.TenantCloseStatus_RUNNING)
	assert.Equal(info.ClosePolicy, closePolicy)

	w = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/closing/actions/cancel", tenantID), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/%d/closing", tenantID))
	assert.Equal(http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &info)
	assert.NoError(err)
	assert.Equal(info.Status, entity.TenantCloseStatus_CANCELING)
}
