package workers

import (
	"errors"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"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/model/payment/paymentsub"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
	"go.uber.org/zap"
)

func createInvoice(tenant *forms.Tenant, paymentMethod *forms.PaymentMethod) {
	var invoice *forms.Invoice
	var err error
	invoiceModel := &payment.InvoiceModel{}

	now := getNowDate()

	invoiceDay := viper.GetInt(config.InvoiceDay)

	switch paymentMethod.PaymentType {
	case entity.PaymentType_AWS, entity.PaymentType_GCP:
		// for 24 hour ago, wait network and backup costs
		invoice, err = invoiceModel.CreateAutoConfirmedHourlyInvoice(tenant.TenantID, now.Add(-time.Hour*24))
		if err != nil {
			if !errors.Is(err, billing_errors.InvoiceMonthlyNoCosts) {
				log.Error("failed to create invoice in worker", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
			} else {
				log.Info("skip create no costs invoice", zap.Uint("TenantID", tenant.TenantID))
			}
			return
		}
		log.Info("created tenant hourly invoice", zap.Uint("TenantID", tenant.TenantID), zap.Time("invoice date", invoice.StartDate))
		invoice, err = invoiceModel.CreateAutoConfirmedMontlyInvoice(tenant.TenantID, now.AddDate(0, -1, -invoiceDay))
		if err != nil {
			if !errors.Is(err, billing_errors.InvoiceMonthlyNoCosts) {
				log.Error("failed to create invoice in worker", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
			}
			return
		}
		log.Info("created tenant monthly invoice", zap.Uint("TenantID", tenant.TenantID), zap.Time("invoice date", invoice.StartDate))

	default:
		invoice, err = invoiceModel.CreateMonthlyInvoice(tenant.TenantID, now.AddDate(0, -1, -invoiceDay))
		if err != nil {
			if !errors.Is(err, billing_errors.InvoiceMonthlyNoCosts) {
				log.Error("failed to create invoice in worker", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
			}
			return
		}
		log.Info("created tenant monthly invoice", zap.Uint("TenantID", tenant.TenantID), zap.Time("invoice date", invoice.StartDate))
	}
}

func sendInvoice(tenant *forms.Tenant) {
	invoiceModel := &payment.InvoiceModel{}
	invoices, err := invoiceModel.ListAllInvoices(tenant.TenantID)
	if err != nil {
		log.Error("failed to query invoices")
		return
	}

	for _, invoice := range invoices {
		if invoice.Status == entity.InvoiceStatus_CONFIRMED {
			_, err := invoiceModel.SendInvoice(invoice.InvoiceID)
			if err != nil {
				log.Error("failed to send invoice", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
				continue
			}
		}
	}
}

func CreateInvoiceWorkerFunc() {
	if !viper.GetBool(config.EnableInvoiceWorker) {
		return
	}
	paymentModel := &paymentsub.PaymentModel{}
	tenants, err := centralhandler.GetCentralHandler().GetAllTenants()
	if err != nil {
		log.Error("failed to get tenants in worker.", zap.Error(err))
		return
	}
	for _, tenant := range tenants {
		payable, err := paymentModel.Payable(tenant.TenantID)
		if err != nil {
			log.Error("failed to query payable when create invoice in worker", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
			continue
		}
		if !payable {
			log.Info("tenant not payable, skip tenant", zap.Uint("TenantID", tenant.TenantID))
			continue
		}
		payment, err := paymentModel.GetPaymentMethod(tenant.TenantID)
		if err != nil {
			log.Error("failed to query payment when create invoice in worker", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
			continue
		}
		createInvoice(&tenant, payment)
		sendInvoice(&tenant)
	}
}

var CreateInvoiceWorker = CreateInvoiceWorkerFunc

var getNowDate = func() time.Time {
	return time.Now()
}

func SyncTenantPayableWorker() {
	if !viper.GetBool(config.EnableSyncPayableWorker) {
		return
	}
	paymentModel := &paymentsub.PaymentModel{}
	tenants, err := centralhandler.GetCentralHandler().GetAllTenants()
	if err != nil {
		log.Error("failed to get tenants in worker.", zap.Error(err))
	}
	for _, tenant := range tenants {
		err := paymentModel.SyncPayable(tenant.TenantID)
		if err != nil {
			log.Error("failed to sync payable in worker", zap.Uint("TenantID", tenant.TenantID), zap.Error(err))
		}
	}
}
