package creditriskcontrol

import (
	"errors"
	"fmt"

	"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/costexplorer"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/errorutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"gorm.io/gorm/clause"
)

type CostQuotaModel struct {
}

func costQuotaFromEntity(costQuotaEntity *entity.CostQuota) *forms.CostQuota {
	return &forms.CostQuota{
		CostQuotaLimit: costQuotaEntity.CostQuotaLimit,
	}
}

func (m *CostQuotaModel) GetTenantCostQuota(tenantID uint, billingConfigID uint) (*forms.CostQuota, error) {
	costQuota := &entity.CostQuota{}
	paymentInfo, err := (&paymentsub.PaymentModel{}).GetActivePaymentMethod(tenantID, billingConfigID)
	if err == nil {
		if (paymentInfo.ManagedBySales) || (paymentInfo.PaymentType == entity.PaymentType_AWS) ||
			(paymentInfo.PaymentType == entity.PaymentType_GCP) ||
			(paymentInfo.PaymentType == entity.PaymentType_MSP) {
			return &forms.CostQuota{
				CostQuotaLimit: entity.NoLimitCostQuota,
			}, nil
		}
	} else {
		if !errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil, err
		}
	}

	err = errorutils.WrapSQLError(entity.NewCostQuotaQuerySet(db.GetDB()).TenantIDEq(tenantID).One(costQuota), fmt.Sprintf("Cost Quota Tenant: %d", tenantID))
	if err == nil {
		return costQuotaFromEntity(costQuota), nil
	}
	if err != nil {
		if !errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil, err
		}
	}
	creditCreditRiskLevel, err := (&CreditRiskLevelModel{}).GetTenantCreditRiskLevelID(tenantID)
	if err != nil {
		return nil, err
	}
	return m.GetCreditRiskLevelQuota(creditCreditRiskLevel)
}

func (*CostQuotaModel) SetTenantCostQuota(tenantID uint, costQuota *forms.CostQuota) (*forms.CostQuota, error) {
	costQuotaEntity := &entity.CostQuota{
		TenantID:       tenantID,
		CostQuotaLimit: costQuota.CostQuotaLimit,
	}
	tx := db.GetDB().Clauses(clause.OnConflict{
		UpdateAll: true,
	})
	err := costQuotaEntity.Create(tx)
	if err != nil {
		return nil, err
	}
	return costQuota, nil
}

func (*CostQuotaModel) SetCreditRiskLevelQuota(creditCreditRiskLevel uint, costQuota *forms.CostQuota) (*forms.CostQuota, error) {
	costQuotaCreditRiskLevelEntity := &entity.CostQuotaCreditRiskLevel{
		CreditRiskLevelID: creditCreditRiskLevel,
		CostQuotaLimit:    costQuota.CostQuotaLimit,
	}
	tx := db.GetDB().Clauses(clause.OnConflict{
		UpdateAll: true,
	})
	err := costQuotaCreditRiskLevelEntity.Create(tx)
	if err != nil {
		return nil, err
	}
	return costQuota, nil
}

func (*CostQuotaModel) GetCreditRiskLevelQuota(creditCreditRiskLevel uint) (*forms.CostQuota, error) {
	costQuotaCreditRiskLevel := &entity.CostQuotaCreditRiskLevel{}
	err := errorutils.WrapSQLError(entity.NewCostQuotaCreditRiskLevelQuerySet(db.GetDB()).CreditRiskLevelIDEq(creditCreditRiskLevel).One(costQuotaCreditRiskLevel), fmt.Sprintf("Cost Quota Tenant CreditRiskLevel: %d", creditCreditRiskLevel))
	if err == nil {
		return &forms.CostQuota{
			CostQuotaLimit: costQuotaCreditRiskLevel.CostQuotaLimit,
		}, nil
	}

	if err != nil {
		if !errors.Is(err, billing_errors.ObjectNotExistsError) {
			return nil, err
		}
	}

	defaultCostQuota := config.Get().GetInt64(config.DefaultCostQuota)

	return &forms.CostQuota{
		CostQuotaLimit: decimal.NewFromInt(defaultCostQuota),
	}, nil
}

func (m *CostQuotaModel) CreateCostQuotaInvoice(tenantID uint, billingConfigID uint) (bool, error) {
	costQuota, err := m.GetTenantCostQuota(tenantID, billingConfigID)
	if err != nil {
		return false, err
	}

	if costQuota.CostQuotaLimit.Equal(entity.NoLimitCostQuota) {
		log.Info("tenant has unlimited cost quota, just ignore cost quota invoice generate", zap.Uint("tenantID", tenantID))
		return false, nil
	}

	now := timeutils.GetNow()
	actualCost, err := (&costexplorer.CostExplorerModel{}).GetMonthlyCost(tenantID, billingConfigID, now.Year(), int(now.Month()))
	if err != nil {
		return false, err
	}

	if costQuota.CostQuotaLimit.Cmp(actualCost.UnbindedInvoiceCosts.CostPrice) <= 0 {
		log.Info("tenant match the cost quota, start generate quota invoice", zap.Uint("tenantID", tenantID), zap.String("costQuota", costQuota.CostQuotaLimit.String()), zap.String("actualCost", actualCost.UnbindedInvoiceCosts.CostPrice.String()))
		invoice, err := (&payment.InvoiceModel{}).CreateCostQuotaInvoice(tenantID, billingConfigID, now)
		if err != nil {
			log.Error("failed create cost quota invoice", zap.Uint("tenantID", tenantID), zap.Error(err), zap.Time("now", now))
			return false, err
		}
		log.Info("success create cost quota invoice", zap.Uint("tenantID", tenantID), zap.Uint("invoiceID", invoice.InvoiceID), zap.String("costPrice", invoice.CostPrice.String()))
		return true, nil
	}
	return false, nil
}
