package tests

import (
	"errors"
	"testing"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"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/model/payment"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/shopspring/decimal"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/suite"
)

var invoiceModel = &payment.InvoiceModel{}

type InvoiceSuite struct {
	tests.BillingTestSuite
}

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

func (suite *InvoiceSuite) TestListAllInvoices() {
	assert := suite.Assert()
	const tenantID uint = 1
	infos, err := invoiceModel.ListAllInvoices(tenantID)
	assert.NoError(err)
	assert.Len(infos, 0)

	_, err = datainit.CreateInvoice(uint(1))
	assert.NoError(err)

	infos, err = invoiceModel.ListAllInvoices(tenantID)
	assert.Nil(err)
	assert.Len(infos, 1)

	assert.Len(infos[0].CategoryUnitsCosts, 1)
}

func (suite *InvoiceSuite) TestConfirmInvoice() {
	assert := suite.Assert()
	const tenantID uint = 1

	invoiceEntity := entity.Invoice{
		TenantID:    tenantID,
		Status:      entity.InvoiceStatus_GENERATED,
		Type:        entity.InvoiceType_MONTHLY,
		InvoiceDate: time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC),
	}
	assert.Nil(invoiceEntity.Create(db.GetDB()))
	info, err := invoiceModel.ConfirmInvoice(invoiceEntity.ID)
	assert.Nil(err)
	assert.Equal(info.InvoiceID, invoiceEntity.ID)
	assert.Equal(info.Status, entity.InvoiceStatus_CONFIRMED)
}

func (suite *InvoiceSuite) TestConfirmInvoiceNegative() {
	assert := suite.Assert()
	const tenantID uint = 1

	invoiceEntity := entity.Invoice{
		TenantID:    tenantID,
		Status:      entity.InvoiceStatus_INITED,
		Type:        entity.InvoiceType_MONTHLY,
		InvoiceDate: time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC),
	}
	assert.Nil(invoiceEntity.Create(db.GetDB()))
	_, err := invoiceModel.ConfirmInvoice(invoiceEntity.ID)
	assert.NotNil(err)

	invoiceEntity = entity.Invoice{
		TenantID:    tenantID,
		Status:      entity.InvoiceStatus_SENT,
		Type:        entity.InvoiceType_MONTHLY,
		InvoiceDate: time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC),
	}
	assert.Nil(invoiceEntity.Create(db.GetDB()))
	info, err := invoiceModel.ConfirmInvoice(invoiceEntity.ID)
	assert.Nil(err)
	assert.Equal(info.Status, entity.InvoiceStatus_SENT)
}

func (suite *InvoiceSuite) TestCreateMonthlyInvoice() {
	assert := suite.Assert()
	invoice, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)
	assert.Zero(invoice.CostPrice.Cmp(decimal.NewFromInt(1)))
	assert.Len(invoice.CategoryUnitsCosts, 1)
	assert.Equal(invoice.CategoryUnitsCosts[viper.GetString(config.DefaultUnitCategory)].CostPrice, decimal.NewFromInt(1))
	assert.Equal(invoice.CategoryUnitsCosts[viper.GetString(config.KeeyShowingCategory)].CostPrice, decimal.Zero)
	assert.Equal(invoice.StartDate, datainit.BillingDate)
	assert.Equal(invoice.Type, entity.InvoiceType_MONTHLY)
	assert.Equal(invoice.EndDate, datainit.BillingDate.AddDate(0, 1, 0))
}

func (suite *InvoiceSuite) TestCreateMontlyNoCost() {
	assert := suite.Assert()
	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	_, err := invoiceModel.CreateMonthlyInvoice(tenantID, billingDate)
	assert.True(errors.Is(err, billing_errors.InvoiceMonthlyNoCosts))
}

func (suite *InvoiceSuite) TestCreateAutoConfirmedHourlyInvoice() {
	assert := suite.Assert()

	// not normal time to test all logic
	measureTime := datainit.BillingDate.AddDate(0, 0, 10).Add(time.Hour * 10)
	err := datainit.CreateMeasuresWithTime(uint(1), measureTime)
	assert.NoError(err)
	invoice, err := invoiceModel.CreateAutoConfirmedHourlyInvoice(uint(1), measureTime)
	assert.NoError(err)
	assert.Equal(invoice.Status, entity.InvoiceStatus_CONFIRMED)
	assert.Zero(invoice.CostPrice.Cmp(decimal.NewFromInt(1)))
	assert.Len(invoice.CategoryUnitsCosts, 1)
	assert.Equal(invoice.CategoryUnitsCosts[viper.GetString(config.DefaultUnitCategory)].CostPrice, decimal.NewFromInt(1))
	assert.Equal(invoice.StartDate, measureTime)
	assert.Equal(invoice.EndDate, measureTime.Add(time.Hour))
}

func (suite *InvoiceSuite) TestCreateMontlyDuplicate() {
	assert := suite.Assert()
	const tenantID uint = 1
	datainit.CreatePaymentMock(tenantID)
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	err := datainit.CreatePricePackageAndBind(tenantID)
	assert.Nil(err)
	measureModel := &billing.MeasureModel{}
	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
	})
	assert.NoError(err)
	invoice, err := invoiceModel.CreateMonthlyInvoice(tenantID, billingDate)
	assert.NoError(err)
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)

	_, err = invoiceModel.CreateMonthlyInvoice(tenantID, billingDate)
	assert.NoError(err)
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)
}

type MockPaymentHandler struct {
}

func (*MockPaymentHandler) Pay(*forms.PaymentMethod, *forms.Invoice) error {
	// do nothing
	return nil
}

func (suite *InvoiceSuite) TestSendInvoice() {
	tests.MockPaymentHandler()
	tests.MockCentralHandler()
	assert := suite.Assert()
	const tenantID uint = 1

	paymentModel := &paymentsub.PaymentModel{}
	paymentModel.CreatePaymentMethod(
		tenantID,
		&forms.PaymentMethod{
			PaymentType: "mock",
		},
	)

	invoiceEntity := entity.Invoice{
		TenantID:    tenantID,
		Status:      entity.InvoiceStatus_CONFIRMED,
		Type:        entity.InvoiceType_MONTHLY,
		InvoiceDate: time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC),
		CostPrice:   decimal.NewFromInt(1),
	}

	assert.NoError(invoiceEntity.Create(db.GetDB()))
	_, err := invoiceModel.SendInvoice(invoiceEntity.ID)
	assert.NoError(err)
}

func (suite *InvoiceSuite) TestDeleteInvoice() {
	assert := suite.Assert()
	invoice, err := datainit.CreateInvoice(uint(1))
	assert.NoError(err)
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)
	assert.Zero(invoice.CostPrice.Cmp(decimal.NewFromInt(1)))
	err = invoiceModel.DeleteInvoice(invoice.InvoiceID)
	assert.NoError(err)
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	_, err = invoiceModel.GetInvoice(invoice.InvoiceID)
	assert.ErrorIs(err, billing_errors.ObjectNotExistsError)
	info, err := invoiceModel.CreateMonthlyInvoice(uint(1), billingDate)
	assert.NoError(err)
	assert.Equal(info.Status, entity.InvoiceStatus_GENERATED)
	assert.Zero(info.CostPrice.Cmp(decimal.NewFromInt(1)))
}

func (suite *InvoiceSuite) TestCreateCommitmentInvoice() {
	assert := suite.Assert()
	invoice, err := invoiceModel.CreateCommitmentInvoice(0, &forms.CommitmentInvoiceInput{
		Amount: decimal.RequireFromString("1234.56"),
		Date:   datainit.BillingDate,
		Owner:  "test@pingcap.com",
	})
	assert.NoError(err)

	assert.Equal(invoice.CostPrice, decimal.RequireFromString("1234.56"))
	assert.Equal(invoice.RawPrice, decimal.RequireFromString("1234.56"))
	assert.Equal(invoice.StartDate, datainit.BillingDate)
	assert.Equal(invoice.EndDate, datainit.BillingDate)
	assert.Equal(invoice.Type, entity.InvoiceType_COMMITMENT)
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)

	record := &entity.CommitmentRecords{}
	err = entity.NewCommitmentRecordsQuerySet(db.GetDB()).One(record)
	assert.NoError(err)
	assert.Equal(record.Owner, "test@pingcap.com")
	assert.Equal(record.Date, datainit.BillingDate)
	assert.Equal(record.Amount, decimal.RequireFromString("1234.56"))

	invoice, err = invoiceModel.GetInvoice(invoice.InvoiceID)
	assert.NoError(err)

	assert.Equal(invoice.CostPrice, decimal.RequireFromString("1234.56"))
	assert.Equal(invoice.RawPrice, decimal.RequireFromString("1234.56"))
	assert.Equal(invoice.StartDate, datainit.BillingDate)
	assert.Equal(invoice.EndDate, datainit.BillingDate)
	assert.Equal(invoice.Type, entity.InvoiceType_COMMITMENT)
	assert.Equal(invoice.Status, entity.InvoiceStatus_GENERATED)
}
