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/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
)

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

var invoiceModel = &payment.InvoiceModel{}

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

func TestInvoiceSuite(t *testing.T) {
	suite.Run(t, new(InvoiceTestSuite))
}

func (suite *InvoiceTestSuite) TestListAllInvoice() {
	assert := suite.Assert()

	w := tests.PerformGetRequest(suite.router, "/tenants/1/invoices")
	assert.Equal(http.StatusOK, w.Code)
	invoices := []forms.Invoice{}
	err := json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 0)
}

func (suite *InvoiceTestSuite) TestListAllInvoiceWithHourlyPayment() {
	assert := suite.Assert()

	invoiceModel.CreateCommitmentInvoice(1, &forms.CommitmentInvoiceInput{
		Amount: decimal.RequireFromString("1234.56"),
		Owner:  "test@pingcap.com",
		Date:   datainit.BillingDate,
	})

	datainit.CreateInvoice(1)
	err := entity.NewPaymentMethodQuerySet(db.GetDB()).TenantIDEq(1).GetUpdater().SetPaymentType(entity.PaymentType_AWS).Update()
	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, "/tenants/1/invoices")
	assert.Equal(http.StatusOK, w.Code)
	invoices := []forms.Invoice{}
	err = json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 1)
	assert.Equal(invoices[0].Type, entity.InvoiceType_COMMITMENT)
}

func (suite *InvoiceTestSuite) TestListAllInvoiceWithFilter() {
	assert := suite.Assert()
	_, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)

	w := tests.PerformGetRequest(suite.router, "/tenants/1/invoices")
	assert.Equal(http.StatusOK, w.Code)
	invoices := []forms.Invoice{}
	err = json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 1)

	w = tests.PerformGetRequest(suite.router, "/tenants/1/invoices?status=sent")
	assert.Equal(http.StatusOK, w.Code)
	invoices = []forms.Invoice{}
	err = json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 0)

	w = tests.PerformGetRequest(suite.router, "/tenants/1/invoices?status=generated")
	assert.Equal(http.StatusOK, w.Code)
	invoices = []forms.Invoice{}
	err = json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 1)
}

func (suite *InvoiceTestSuite) TestGetInvoice() {
	assert := suite.Assert()
	invoice, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)
	w := tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/invoices/%d", invoice.InvoiceID))
	assert.Equal(http.StatusOK, w.Code)
	ret := forms.Invoice{}
	err = json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Equal(ret.InvoiceID, invoice.InvoiceID)
	assert.Equal(ret.Status, invoice.Status)
}

func (suite *InvoiceTestSuite) TestConfirmInvoice() {
	assert := suite.Assert()
	invoice, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)
	w := tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/invoices/%d/confirm", invoice.InvoiceID), nil)
	assert.Equal(http.StatusOK, w.Code)
}

func (suite *InvoiceTestSuite) TestSendInvoice() {
	assert := suite.Assert()
	invoice, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)
	tests.MockPaymentHandler()
	tests.MockCentralHandler()

	paymentModel.CreatePaymentMethod(uint(1), &forms.PaymentMethod{
		PaymentType: "mock",
	})
	invoiceModel.ConfirmInvoice(invoice.InvoiceID)

	w := tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/invoices/%d/send", invoice.InvoiceID), nil)
	assert.Equal(http.StatusOK, w.Code)
}

func (suite *InvoiceTestSuite) TestCreateInvoice() {
	assert := suite.Assert()
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/invoices", &forms.CreateInvoiceReq{
		StartDate: datainit.BillingDate,
		Type:      entity.InvoiceType_MONTHLY,
	})
	assert.Equal(http.StatusOK, w.Code)
}

func (suite *InvoiceTestSuite) TestCreateCommitmentInvoice() {
	assert := suite.Assert()
	w := tests.PerformRequest(suite.router, "POST", "/tenants/1/commitment_invoices", &forms.CommitmentInvoiceInput{
		Amount: decimal.RequireFromString("1234.56"),
		Owner:  "test@pingcap.com",
		Date:   datainit.BillingDate,
	})
	assert.Equal(http.StatusOK, w.Code)
	invoice := &forms.Invoice{}
	err := json.NewDecoder(w.Body).Decode(&invoice)
	assert.NoError(err)

	assert.Equal(invoice.CostPrice, decimal.RequireFromString("1234.56"))
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)
}
