package spend_limit

import (
	"encoding/json"
	"fmt"

	"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/utils/log"
	"github.com/shopspring/decimal"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

func (m *SpendLimitModel) MarshallNormalBizData(bizData *forms.NormalBizData) (ret string, err error) {
	marshalledBizData, err := json.Marshal(bizData)
	if err != nil {
		return "", err
	}
	return string(marshalledBizData), err
}

func (m *SpendLimitModel) UnmarshallNormalBizData(raw string) (ret *forms.NormalBizData, err error) {
	var bizData forms.NormalBizData
	err = json.Unmarshal([]byte(raw), &bizData)
	if err != nil {
		return
	}
	return &bizData, nil
}

func (m *SpendLimitModel) normalBizDataInvalidate(bizData string) (string, error) {
	if bizData == "" {
		bizData = "{}"
	}
	bizDataStruct, err := m.UnmarshallNormalBizData(bizData)
	if err != nil {
		return "", err
	}
	marshalled, err := m.MarshallNormalBizData(bizDataStruct)
	if err != nil {
		return "", err
	}
	return marshalled, nil
}

func (m *SpendLimitModel) normalProcessOnCreating(tx *gorm.DB, settings *forms.SpendLimitSetting) error {
	bizData, err := m.normalBizDataInvalidate(settings.BizData)
	if err != nil {
		return err
	}
	settings.BizData = bizData
	return nil
}

func (m *SpendLimitModel) normalProcessAfterCreating(settings *forms.SpendLimitSetting) error {
	// It is necessary to ensure that thresholds that have been reached before the user updates are not processed.
	// now thresholds are not created, so there are unnecessary to do on same transaction
	log.Info("flush water mark on creating", zap.Uint("tenantID", settings.TenantID), zap.Any("settings", settings))
	info, err := m.SpendLimitSettingWithCost(settings)
	if err != nil {
		return err
	}
	err = entity.NewSpendLimitSettingQuerySet(db.GetDB()).IDEq(settings.SpendLimitSettingID).TenantIDEq(settings.TenantID).GetUpdater().SetWaterMark(info.Costs).Update()
	if err != nil {
		log.Info("failed to flush spending limit water mark after creating", zap.Error(err), zap.Uint("tenantID", settings.SpendLimitSettingID))
		return err
	}
	log.Info("success to flush spending limit watermark after creating", zap.String("watermark", info.Costs.String()))
	return nil
}

func (m *SpendLimitModel) normalProcessOnUpdating(tx *gorm.DB, settings *forms.SpendLimitSetting, origin *forms.SpendLimitSetting) error {
	bizData, err := m.normalBizDataInvalidate(settings.BizData)
	if err != nil {
		return err
	}
	marshalledBizData, err := m.UnmarshallNormalBizData(bizData)
	if err != nil {
		return err
	}
	marshalledOriginBizData, err := m.UnmarshallNormalBizData(origin.BizData)
	if err != nil {
		return err
	}
	// It is necessary to ensure that thresholds that have been reached before the user updates are not processed.
	// now thresholds are created, so there are necessary to do on same transaction
	flushWaterMark := marshalledOriginBizData.ApplyCredits != marshalledBizData.ApplyCredits ||
		marshalledOriginBizData.ApplyDiscount != marshalledBizData.ApplyDiscount ||
		settings.RangeType != settings.RangeType ||
		settings.RangeID != settings.RangeID ||
		settings.CostType != settings.CostType
	if flushWaterMark {
		log.Info("spending limit scope changed, need flush watermark", zap.Any("origin", origin), zap.Any("settings", settings))
		info, err := m.SpendLimitSettingWithCost(settings)
		if err != nil {
			return err
		}
		err = entity.NewSpendLimitSettingQuerySet(tx).IDEq(settings.SpendLimitSettingID).TenantIDEq(settings.TenantID).GetUpdater().SetWaterMark(info.Costs).Update()
		if err != nil {
			log.Info("failed to flush spending limit water mark on updating", zap.Error(err), zap.Uint("spendingLimitID", settings.SpendLimitSettingID))
			return err
		}
		log.Info("success to flush spending limit water mark on updating", zap.Uint("spendingLimitID", settings.SpendLimitSettingID), zap.String("watermark", info.Costs.String()))
	}
	settings.BizData = bizData
	return nil
}

func (m *SpendLimitModel) SendNormalSpendingLimitEmail(emailID string, info *forms.SpendLimitThresholdAlert) (err error) {
	bizData, err := m.UnmarshallNormalBizData(info.BizData)
	if err != nil {
		return err
	}
	if !bizData.EnableEmailNotification {
		log.Info("email notification is not enabled, skip to send email", zap.Uint("spendLimitID", info.SpendLimitSettingID))
		return nil
	}
	central := centralhandler.GetCentralHandler()
	tenant, err := central.GetTenant(info.TenantID)
	if err != nil {
		return err
	}
	budgetUrl := fmt.Sprintf(config.Get().GetString(config.BudgetsUrlTmpl), tenant.TenantID)
	err = central.SendTenantEmailWithLabelOnce(
		info.TenantID, emailID,
		constant.NormalSpendingLimitEmailTemplateID,
		map[string]string{
			constant.NormalSpendingLimitEmailUserKey:         tenant.Name,
			constant.NormalSpendingLimitEmailOrgNameKey:      tenant.Name,
			constant.NormalSpendingLimitEmailThresholdKey:    info.Threshold.Round(config.Get().GetInt32(config.BudgetThresholdPrecision)).Mul(decimal.NewFromInt(100)).String(),
			constant.NormalSpendingLimitEmailActualCostKey:   info.Costs.Truncate(config.Get().GetInt32(config.BillingCostsPrecisionDigits)).String(),
			constant.NormalSpendingLimitEmailBudgetNameKey:   info.Name,
			constant.NormalSpendingLimitEmailBudgetURLKey:    budgetUrl,
			constant.NormalSpendingLimitEmailBudgetAmountKey: info.Limit.Truncate(config.Get().GetInt32(config.BillingCostsPrecisionDigits)).String(),
		},
		account.MailLabels_MAIL_LABELS_ORG_OWNER,
		[]account.MailLabels{account.MailLabels_MAIL_LABELS_ORG_BILLING_ADMINS},
	)
	if err != nil {
		return err
	}
	return
}
