package tests

import (
	"encoding/json"
	"fmt"
	"math"
	"net/http"
	"testing"

	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"

	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler/impl"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/pp"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/stretchr/testify/suite"
)

var contractModel = &billing.ContractModel{}

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

func (suite *ContractTestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	suite.router = gin.New()
	suite.router.Use(ginutils.ErrorMiddleWare)
	tests.MockCentralHandler()
	api.InitializeContractRouter(ginutils.MyRoute{Route: suite.router})
}

func TestContractSuite(t *testing.T) {
	suite.Run(t, new(ContractTestSuite))
}

func (suite *ContractTestSuite) assertContractEq(first *forms.Contract, second *forms.Contract) {
	assert := suite.Assert()
	assert.Equal(first.ContractID, second.ContractID)
	assert.Equal(first.ContractType, second.ContractType)
	assert.Equal(first.ContactEmail, second.ContactEmail)
	assert.Equal(first.ReasonForLastOperation, second.ReasonForLastOperation)
	assert.Equal(first.FilePath, second.FilePath)
	assert.Equal(first.Status, second.Status)
	assert.Equal(first.LastOperatorID, second.LastOperatorID)
	assert.LessOrEqual(math.Abs(float64(first.CreatedAt.Unix()-second.CreatedAt.Unix())), 1.0)
}

func (suite *ContractTestSuite) TestCreateContract() {
	assert := suite.Assert()

	contract := &forms.ContractInput{
		ContractID:   "US-OD-27364981",
		FilePath:     "file_path",
		ContactEmail: "contract@email",
	}
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)
	r := tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts", tenantID), contract)
	assert.Equal(http.StatusBadRequest, r.Code)
	contract.ContractType = entity.ContractTypeSavingPlanOrderForm
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts", tenantID), contract)
	assert.Equal(http.StatusOK, r.Code)
	response := &forms.Contract{}
	err := json.Unmarshal(r.Body.Bytes(), response)
	assert.NoError(err)

	info, err := contractModel.GetContract(tenantID, response.ID)
	assert.NoError(err)
	suite.assertContractEq(info, response)
	paymenthandler.SetPaymentHandler(&impl.PaymentHandlerImpl{})
	billingConfigID = 2
	_, err = (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(
		100,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           entity.PaymentType_MSP,
			BindedBillingConfigID: &billingConfigID,
		},
	)
	assert.NoError(err)
	datainit.CreateBillingConfig(100, entity.BillingConfigType_MSPCustomer)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts", 100), contract)
	assert.Equal(http.StatusBadRequest, r.Code)
	ress := gin.H{}
	json.Unmarshal(r.Body.Bytes(), &ress)
	pp.Print(ress)
	assert.NotNil(ress["error"])
	assert.Equal(ress["error"], "Invalid Input Error: MSP Customer  don't support contract.")

}

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

	input := &forms.ContractInput{
		ContractID:   "US-OD-27364981",
		ContractType: entity.ContractTypeSavingPlanOrderForm,
		FilePath:     "file_path",
		ContactEmail: "contract@email",
	}
	tenantID := uint(1)
	info, err := contractModel.CreateContract(tenantID, 1, input)
	assert.NoError(err)

	contractID := info.ID
	r := tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	response := &forms.Contract{}
	err = json.Unmarshal(r.Body.Bytes(), response)
	assert.NoError(err)
	suite.assertContractEq(info, response)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/contracts")
	listResponse := &forms.ContractsResponse{}
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, len(*listResponse.Contracts))
	assert.Equal(1, listResponse.TotalResults)

	r = tests.PerformGetRequest(suite.router, "/tenants/1/contracts?page=2")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(0, len(*listResponse.Contracts))

	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusNotFound, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(0, listResponse.TotalResults)

	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusNotFound, r.Code)

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusSubmit).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, len(*listResponse.Contracts))
	assert.Equal(1, listResponse.TotalResults)

	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts?page=2")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(0, len(*listResponse.Contracts))

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusSubmit).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, listResponse.TotalResults)

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusActive).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, listResponse.TotalResults)

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusRejected).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, listResponse.TotalResults)

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusTerminated).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, listResponse.TotalResults)

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusCanceled).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusOK, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(1, listResponse.TotalResults)

	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusDeleted).Update()
	assert.NoError(err)
	r = tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/users/contracts/%d", contractID))
	assert.Equal(http.StatusNotFound, r.Code)
	r = tests.PerformGetRequest(suite.router, "/tenants/1/users/contracts")
	err = json.Unmarshal(r.Body.Bytes(), listResponse)
	assert.NoError(err)
	assert.Equal(0, listResponse.TotalResults)
}

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

	input := &forms.ContractInput{
		ContractID:   "US-OD-27364981",
		ContractType: entity.ContractTypeSavingPlanOrderForm,
		FilePath:     "file_path",
		ContactEmail: "contract@email",
	}
	tenantID := uint(1)
	info, err := contractModel.CreateContract(tenantID, 1, input)
	assert.NoError(err)

	contractID := info.ID
	contract := &entity.Contract{}
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).One(contract)
	assert.NoError(err)
	assert.Equal(entity.ContractStatusDraft, contract.Status)
	r := tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/users/contracts/%d/actions/%s", tenantID, contractID, entity.ContractStatusSubmit), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionUpdate), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/users/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionUpdate), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionCancel), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractStatusSubmit), nil)
	assert.Equal(http.StatusOK, r.Code)
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).One(contract)
	assert.NoError(err)
	assert.Equal(entity.ContractStatusSubmit, contract.Status)

	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/users/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionCancel), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionCancel), nil)
	assert.Equal(http.StatusOK, r.Code)
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).One(contract)
	assert.NoError(err)
	assert.Equal(entity.ContractStatusCanceled, contract.Status)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionSubmit), nil)
	assert.Equal(http.StatusOK, r.Code)

	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionAccept), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionReject), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/users/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionAccept), nil)
	assert.Equal(http.StatusOK, r.Code)
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).One(contract)
	assert.NoError(err)
	assert.Equal(entity.ContractStatusActive, contract.Status)

	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/users/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionTerminate), nil)
	assert.Equal(http.StatusForbidden, r.Code)
	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionTerminate), nil)
	assert.Equal(http.StatusOK, r.Code)
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).One(contract)
	assert.NoError(err)
	assert.Equal(entity.ContractStatusTerminated, contract.Status)
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).GetUpdater().SetStatus(entity.ContractStatusSubmit).Update()
	assert.NoError(err)

	r = tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/%d/users/contracts/%d/actions/%s", tenantID, contractID, entity.ContractActionReject), nil)
	assert.Equal(http.StatusOK, r.Code)
	err = entity.NewContractQuerySet(db.GetDB()).IDEq(contractID).One(contract)
	assert.NoError(err)
	assert.Equal(entity.ContractStatusRejected, contract.Status)
}

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

	input := &forms.ContractInput{
		ContractID:   "US-OD-27364981",
		ContractType: entity.ContractTypeSavingPlanOrderForm,
		FilePath:     "file_path",
		ContactEmail: "contract@email",
	}
	tenantID := uint(1)
	info, err := contractModel.CreateContract(tenantID, 1, input)
	assert.NoError(err)

	contractID := info.ID
	input.FilePath += "1"
	input.ContactEmail += "1"
	r := tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/contracts/%d", contractID), input)
	assert.Equal(http.StatusOK, r.Code)
	response := &forms.Contract{}
	err = json.Unmarshal(r.Body.Bytes(), response)
	assert.NoError(err)
	assert.Equal(input.FilePath, response.FilePath)
	assert.Equal(input.ContactEmail, response.ContactEmail)

	op := &entity.ContractOperation{}
	err = entity.NewContractOperationQuerySet(db.GetDB()).ContractIDEq(info.ID).ActionEq(entity.ContractActionUpdate).One(op)
	assert.NoError(err)
	assert.Equal(input.FilePath, op.FilePath)
	assert.Equal(input.ContactEmail, op.ContactEmail)
}

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

	input := &forms.ContractInput{
		ContractID:   "US-OD-27364981",
		FilePath:     "abc.pdf",
		ContractType: entity.ContractTypePocAgreement,
		ContactEmail: "contract_email",
	}
	tenantID := uint(1)
	info, err := contractModel.CreateContract(tenantID, 1, input)
	assert.NoError(err)

	r := tests.PerformRequest(suite.router, "GET", fmt.Sprintf("/tenants/1/contracts/%d/download", info.ID), nil)
	assert.Equal(http.StatusOK, r.Code)
	assert.EqualValues(r.Body.String(), "mock_file_content")
	assert.EqualValues(r.Header()["Content-Type"][0], "application/octet-stream")
	assert.Regexp("attachment.*", r.Header()["Content-Disposition"][0])
}
