package tests

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"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/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
	"golang.org/x/exp/rand"
)

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})
	config.Get().Set(config.EnableTaxCalculation, false)
}

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

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

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

	w = tests.PerformGetRequest(suite.router, "/tenants/1/invoices")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 1)
	assert.Equal(invoices[0].CostPrice, decimal.NewFromInt(1))
	assert.Len(invoices[0].CategoryUnitsCosts, 1)

	w = tests.PerformGetRequest(suite.router, "/tenants/1/invoices?brief=true")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&invoices)
	assert.NoError(err)
	assert.Len(invoices, 1)
	assert.Equal(invoices[0].CostPrice, decimal.NewFromInt(1))
	assert.Len(invoices[0].CategoryUnitsCosts, 0)
}

func (suite *InvoiceTestSuite) TestListAllInvoiceWithHourlyPayment() {
	assert := suite.Assert()
	var err error
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)

	_, err = invoiceModel.CreateCommitmentInvoice(1, &forms.CommitmentInvoiceInput{
		Amount:                decimal.RequireFromString("1234.56"),
		Owner:                 "test@pingcap.com",
		Date:                  datainit.BillingDate,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	_, err = datainit.CreateInvoice(1)
	assert.NoError(err)
	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)

	w = tests.PerformGetRequest(suite.router, "/tenants/1/invoices?brief=true")
	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)
	assert.Nil(invoices[0].CategoryItemUnitsCosts)
}

func (suite *InvoiceTestSuite) TestListAllInvoiceWithFilter() {
	assert := suite.Assert()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	_, 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)
}

func (suite *InvoiceTestSuite) TestGetInvoice() {
	assert := suite.Assert()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	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()
	invoiceID := uint(1)
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	invoice, err := datainit.CreateInvoice(invoiceID)
	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) TestSuspendInvoice() {
	assert := suite.Assert()
	invoiceID := uint(1)
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

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

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

	paymentModel.CreatePaymentMethodAndActive(uint(1), &forms.PaymentMethodCreateQuery{
		PaymentType:           "mock",
		BindedBillingConfigID: &billingConfigID,
	})
	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)

	invoiceEntity := entity.Invoice{
		TenantID:              1,
		Status:                entity.InvoiceStatus_GENERATED,
		Type:                  entity.InvoiceType_COMMITMENT,
		InvoiceDate:           time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC),
		BindedBillingConfigID: billingConfigID,
	}
	assert.Nil(invoiceEntity.Create(db.GetDB()))

	err = entity.NewPaymentMethodQuerySet(db.GetDB()).TenantIDEq(1).GetUpdater().SetManagedBySales(true).Update()
	w = tests.PerformRequest(suite.router, "PUT", fmt.Sprintf("/tenants/1/invoices/%d/send", 2), nil)
	assert.Equal(http.StatusOK, w.Code)

}

func (suite *InvoiceTestSuite) TestCreateInvoice() {
	assert := suite.Assert()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	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) TestCreateCostQuotaInvoice() {
	assert := suite.Assert()
	datainit.CreateBillingConfig(3, entity.BillingConfigType_DirectCustomer)
	err := datainit.CreateMeasures(uint(3))
	assert.NoError(err)
	w := tests.PerformRequest(suite.router, "POST", "/tenants/3/invoices", &forms.CreateInvoiceReq{
		StartDate: datainit.BillingDate,
		Type:      entity.InvoiceType_COSTQUOTA,
	})
	assert.Equal(http.StatusOK, w.Code)

}

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

	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_Recal,
	})
	assert.Equal(http.StatusOK, w.Code)
}

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

	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)
}

func (suite *InvoiceTestSuite) TestReSendInvoice() {
	assert := suite.Assert()
	suite.TestConfirmInvoice()
	invoiceID := uint(1)
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	s := tests.PerformRequest(suite.router, "POST", fmt.Sprintf("/tenants/1/invoices/%d/resend", invoiceID), nil)
	assert.Equal(http.StatusOK, s.Code)

	w := tests.PerformGetRequest(suite.router, fmt.Sprintf("/tenants/1/invoices/%d", invoiceID))

	assert.Equal(http.StatusOK, w.Code)
	ret := forms.Invoice{}
	err := json.NewDecoder(w.Body).Decode(&ret)
	assert.NoError(err)
	assert.Equal(entity.InvoiceStatus_CONFIRMED, ret.Status)
}

func (suite *InvoiceTestSuite) TestDeleteInvoice() {
	assert := suite.Assert()
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	suite.TestConfirmInvoice()
	invoiceID := uint(1)
	err := entity.NewInvoiceQuerySet(db.GetDB()).IDEq(invoiceID).GetUpdater().SetStatus(entity.InvoiceStatus_SENT).Update()
	assert.NoError(err)

	s := tests.PerformRequest(suite.router, "DELETE", fmt.Sprintf("/tenants/1/invoices/%d", invoiceID), nil)
	assert.Equal(http.StatusBadRequest, s.Code)

	s = tests.PerformRequestWithHeaders(suite.router, "DELETE", fmt.Sprintf("/tenants/1/invoices/%d", invoiceID), nil, map[string]string{constant.TestHeaderKey: constant.TestHeaderValue})
	assert.Equal(http.StatusOK, s.Code)
}

func (suite *InvoiceTestSuite) TestListAllInvoice() {
	var err error
	assert := suite.Assert()
	t0, t1, t2, t3, t4 := uint(1), uint(2), uint(3), uint(4), uint(5)
	orgs := []forms.Tenant{{TenantID: t0}, {TenantID: t1}, {TenantID: t2}, {TenantID: t3}, {TenantID: t4}}
	central := &MockCentralHandlerImpl{}
	central.SetAllTenants(orgs)
	centralhandler.SetCentralHandler(central)
	statues := []string{entity.InvoiceStatus_CONFIRMED, entity.InvoiceStatus_GENERATED, entity.InvoiceStatus_SENT, entity.InvoiceStatus_SENT, entity.InvoiceStatus_SENT}
	invoiceTypes := []string{entity.InvoiceType_MONTHLY, entity.InvoiceType_COSTQUOTA, entity.InvoiceType_MONTHLY, entity.InvoiceType_COMMITMENT, entity.InvoiceType_INMONTH}
	paymentTypes := []string{entity.PaymentType_STRIPE, entity.PaymentType_STRIPE, entity.PaymentType_GCP, entity.PaymentType_AWS, entity.PaymentType_AWS}
	for i, t := range orgs {
		b, err := datainit.CreateBillingConfig(t.TenantID, entity.BillingConfigType_DirectCustomer)
		assert.NoError(err)
		p := entity.PaymentMethod{
			PaymentType:           paymentTypes[i],
			Status:                entity.PaymentMethodStatus_Active,
			BindedBillingConfigID: b.BillingConfigID,
			TenantID:              t.TenantID,
		}
		err = p.Create(db.GetDB())
		assert.NoError(err)
		inv := entity.Invoice{
			TenantID:              t.TenantID,
			BindedBillingConfigID: b.BillingConfigID,
			CostPrice:             decimal.NewFromInt(10),
			Status:                statues[i],
			Type:                  invoiceTypes[i],
			InvoiceDate:           datainit.BillingDate,
		}
		if i == 0 {
			inv.InvoiceDate = datainit.BillingDate.AddDate(0, 1, 0)
		}
		inv.CreatedAt = inv.InvoiceDate.AddDate(0, 1, 0).Add(time.Duration(rand.Intn(10)) * time.Hour)
		inv.PaymentMethodID = &p.ID
		err = inv.Create(db.GetDB())
		assert.NoError(err)
	}
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 2))
	var resp []forms.Invoice
	w := tests.PerformGetRequest(suite.router, "/invoices?brief=true")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Len(resp, 3)

	w = tests.PerformGetRequest(suite.router, "/invoices?brief=true&from=2021-11-01T00:00:00Z&to=2021-12-01T00:00:00Z")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Len(resp, 2)

	w = tests.PerformGetRequest(suite.router, "/invoices?brief=true&from=2021-12-01T00:00:00Z&to=2022-01-01T00:00:00Z")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Len(resp, 1)

	w = tests.PerformGetRequest(suite.router, "/invoices?brief=true&status=generated")
	assert.Equal(http.StatusOK, w.Code)
	err = json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Len(resp, 1)

	w = tests.PerformGetRequest(suite.router, "/invoices?brief=true&tenant_ids=1,,2")
	assert.Equal(http.StatusBadRequest, w.Code)
}
