package marketing

import (
	"errors"
	"fmt"
	"time"

	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/eventbushandler"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"github.com/pingcap/dbaas/billing2/utils/transformer"
	"github.com/pingcap/dbaas/billing2/utils/user"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"github.com/google/uuid"
	"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/utils/errorutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/pingcap/dbaas/billing2/utils/uncutils"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
)

const PackageCreditsChargeReason = "credits package opening charge"
const CreditsPackageCloseEventType = "credits-package-close"
const CreditsPackageOpenEventType = "credits-package-open"

type CreditsPackageModel struct{}

func creditsPackageFromEntity(creditsPackageEntity *entity.CreditsPackage) (*forms.CreditsPackage, error) {
	billingConfigID := creditsPackageEntity.BindedBillingConfigID
	orderedUnitFilters := transformer.OrderedUnitFilter(creditsPackageEntity.UnitFilters)
	creditsPackage := &forms.CreditsPackage{
		TenantID:              creditsPackageEntity.TenantID,
		CreditsPackageID:      creditsPackageEntity.ID,
		CreditsCode:           creditsPackageEntity.CreditsCode,
		Status:                creditsPackageEntity.Status,
		StartDate:             creditsPackageEntity.StartDate,
		ExpiredDate:           creditsPackageEntity.ExpiredDate,
		ClosedDate:            creditsPackageEntity.ClosedDate,
		BindedPackageID:       creditsPackageEntity.BindedPackageID,
		TotalCredits:          creditsPackageEntity.TotalCredits,
		Type:                  transformer.JoinUnitFilterNames(orderedUnitFilters, ", "),
		Usage:                 creditsPackageEntity.Usage,
		ClosedReason:          creditsPackageEntity.ClosedReason,
		Note:                  creditsPackageEntity.Note,
		DiscountEnabled:       creditsPackageEntity.DiscountEnabled,
		UnitFilters:           transformer.UnitFiltersSlimFromEntities(orderedUnitFilters),
		UnitFiltersInternal:   transformer.UnitFiltersFromEntities(orderedUnitFilters),
		Internal:              creditsPackageEntity.Internal,
		BindedBillingConfigID: &billingConfigID,
		UsageExtend:           creditsPackageEntity.UsageExtend,
		CopyUnitFilterArgs:    (&unit_filter.Model{}).CopyUnitFilterArgsFromEntity(creditsPackageEntity.CopyUnitFilterArgs),
	}
	creditsPackage.BindTypes = creditsPackage.UnitFilters

	if creditsPackageEntity.BindedPackageID != nil {
		creditsPackage.RemainingCredits = decimal.Max(creditsPackageEntity.BindedPackage.RemainingCredits, decimal.Zero)
	} else {
		creditsPackage.RemainingCredits = creditsPackage.TotalCredits
	}

	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	creditsPackage.RemainingCredits = creditsPackage.RemainingCredits.Shift(precision).Ceil().Shift(-precision)
	return creditsPackage, nil
}

func (*CreditsPackageModel) ListCreditsPackages(filter forms.CreditsPackageFilter, billingConfigID uint) (creditsPackages []forms.CreditsPackage, err error) {
	var entities []entity.CreditsPackage
	query := db.GetDB().Preload(clause.Associations).Model(&entity.CreditsPackage{}).Order("created_at DESC")
	if filter.TenantID != 0 {
		query = query.Where("tenant_id = ?", filter.TenantID)
	}
	if filter.Internal != nil {
		query = query.Where("internal = ?", *filter.Internal)
	}
	if filter.StartDate != nil {
		query = query.Where("((start_date <= ? and expired_date > ?) or start_date >= ?)", *filter.StartDate, *filter.StartDate, *filter.StartDate)
	}
	if filter.ExpiredDate != nil {
		query = query.Where("((start_date < ? and expired_date >= ?) or expired_date <= ?)", *filter.ExpiredDate, *filter.ExpiredDate, *filter.ExpiredDate)
	}
	if filter.Status != "" {
		query = query.Where("status = ?", filter.Status)
	}
	if filter.Usage != "" {
		query = query.Where("`usage` = ?", filter.Usage)
	}
	if billingConfigID != 0 {
		query = query.Where("binded_billing_config_id = ?", billingConfigID)
	}
	err = errorutils.WrapQuerySetError(query.Find(&entities).Error)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	creditsPackages = make([]forms.CreditsPackage, len(entities))
	for i, creditPackageEntity := range entities {
		creditPackage, err := creditsPackageFromEntity(&creditPackageEntity)
		if err != nil {
			return nil, err
		}
		creditsPackages[i] = *creditPackage
	}
	return creditsPackages, nil
}

func (m *CreditsPackageModel) ListTenantCreditsPackages(tenantID uint, billingConfigID uint) (creditsPackages []forms.CreditsPackage, err error) {
	internal := false
	return m.ListCreditsPackages(forms.CreditsPackageFilter{Internal: &internal, TenantID: tenantID}, billingConfigID)
}

func (m *CreditsPackageModel) ListTenantAllCreditsPackages(tenantID uint) (creditsPackages []forms.CreditsPackage, err error) {
	return m.ListCreditsPackages(forms.CreditsPackageFilter{TenantID: tenantID}, 0)
}

func (m *CreditsPackageModel) ListAllCreditsPackagesByUsage(usage string, startDate, ExpiredDate *time.Time) (creditsPackages []forms.CreditsPackage, err error) {
	internal := false
	return m.ListCreditsPackages(forms.CreditsPackageFilter{
		Usage:       usage,
		StartDate:   startDate,
		ExpiredDate: ExpiredDate,
		Internal:    &internal,
	}, 0)
}

func (m *CreditsPackageModel) ListAllPrepaymentCreditsPackages(startDate, expiredDate *time.Time) (creditsPackages []forms.CreditsPackage, err error) {
	return m.ListAllCreditsPackagesByUsage(entity.CreditsPackageUsage_Prepayment, startDate, expiredDate)
}

func (m *CreditsPackageModel) GetCreditsPackage(creditsPackageID uint) (*forms.CreditsPackage, error) {
	return m.GetCreditsPackageWithTx(db.GetDB(), creditsPackageID)
}

func (*CreditsPackageModel) GetCreditsPackageWithTx(tx *gorm.DB, creditsPackageID uint) (*forms.CreditsPackage, error) {
	creditsPackageEntity := &entity.CreditsPackage{}
	err := errorutils.WrapSQLError(entity.NewCreditsPackageQuerySet(tx.Preload(clause.Associations)).IDEq(creditsPackageID).One(creditsPackageEntity), fmt.Sprintf("credit package %d", creditsPackageID))
	if err != nil {
		return nil, err
	}
	return creditsPackageFromEntity(creditsPackageEntity)
}

func (m *CreditsPackageModel) CloseCreditsPackage(creditsPackageID uint, reason string) error {
	return db.GetDB().Transaction(func(tx *gorm.DB) error {
		return m.CloseCreditsPackageWithTx(tx, creditsPackageID, reason)
	})
}

func (m *CreditsPackageModel) CloseCreditsPackageWithTx(tx *gorm.DB, creditsPackageID uint, reason string) error {
	creditsPackage, err := m.queryCredits(tx, creditsPackageID)
	if err != nil {
		log.Error("failed to close credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return err
	}
	now := timeutils.GetNow()
	cnt, err := entity.NewCreditsPackageQuerySet(tx).IDEq(creditsPackageID).ClosedDateIsNull().GetUpdater().SetStatus(entity.CreditsPackageStatus_InActive).SetClosedDate(&now).SetClosedReason(reason).UpdateNum()
	if err != nil {
		log.Error("failed to close credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return err
	} else if cnt == 0 {
		log.Info("credits package is closed", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return nil
	}
	err = (&billing.BindedPackageModel{}).UnbindPackageWithTx(tx, *creditsPackage.BindedPackageID)
	if err != nil {
		log.Error("failed to unbind package in close credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Uint("bindedPakcageID", *creditsPackage.BindedPackageID), zap.Error(err))
		return err
	}
	log.Info("success unbind package in close credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Uint("bindedPakcageID", *creditsPackage.BindedPackageID))
	actionEntity := &entity.AuditRecord{
		Action:    entity.AuditRecordActionReclaimCredit,
		TenantID:  creditsPackage.TenantID,
		UserID:    user.GetCurrentUserID(),
		ExtraInfo: fmt.Sprintf(`{"credits_package_id": %d}`, creditsPackageID),
		UserEmail: user.GetCurrentUserEmail(),
	}

	err = actionEntity.Create(tx)
	if err != nil {
		return err
	}
	if err != nil {
		log.Error("failed to close credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return err
	}

	err = eventbushandler.Get().PublishEventWithTransaction(CreditsPackageCloseEventType, &eventbushandler.Event{
		EventID:   fmt.Sprint(creditsPackageID),
		EventData: creditsPackage,
	}, tx)

	if err != nil {
		return err
	}
	m.sendCreditUpdateNotificationIfNeed(creditsPackage, reason)
	log.Info("success close credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.String("reason", reason))
	return nil
}

func (m *CreditsPackageModel) CreateCreditsPackageAtomically(tenantID uint, creditsPackage *forms.CreditsPackage, fc func(tx *gorm.DB, entity *entity.CreditsPackage) error) (ret *forms.CreditsPackage, err error) {
	if err := validateRerunCreditsPackageWindow(creditsPackage); err != nil {
		return nil, err
	}

	creditsPackageEntity := &entity.CreditsPackage{
		TenantID:              tenantID,
		CreditsCode:           uuid.NewString(),
		Status:                entity.CreditsPackageStatus_BeforeActive,
		StartDate:             creditsPackage.StartDate,
		ExpiredDate:           creditsPackage.ExpiredDate,
		TotalCredits:          creditsPackage.TotalCredits,
		Type:                  creditsPackage.Type,
		Usage:                 creditsPackage.Usage,
		Note:                  creditsPackage.Note,
		DiscountEnabled:       creditsPackage.DiscountEnabled,
		UnitFilters:           transformer.UnitFiltersEntitiesFromUnitFilterSlimForm(creditsPackage.UnitFilters),
		Internal:              creditsPackage.Internal,
		BindedBillingConfigID: *creditsPackage.BindedBillingConfigID,
		UsageExtend:           creditsPackage.UsageExtend,
		CopyUnitFilterArgs:    entity.CopyUnitFilterArgs(creditsPackage.CopyUnitFilterArgs),
	}
	actionEntity := &entity.AuditRecord{
		Action:    entity.AuditRecordActionCreateCredit,
		TenantID:  tenantID,
		UserID:    user.GetCurrentUserID(),
		UserEmail: user.GetCurrentUserEmail(),
	}
	err = db.GetDB().Transaction(func(tx *gorm.DB) (err error) {
		precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
		if creditsPackage.TotalCredits.Truncate(precision).Cmp(creditsPackage.TotalCredits) != 0 {
			log.Error("failed to create credits package with invalid precision total credits", zap.Uint("tenantID", tenantID), zap.String("totalCredits", creditsPackage.TotalCredits.String()))
			return billing_errors.NewInvalidInputError(errors.New("failed to create credits package with invalid precision total credits"))
		}

		bindedPackageInfo, err := (&billing.BindedPackageModel{}).BindPackageExtraWithTx(tx, creditsPackageEntity.TenantID, &forms.BindedPackageInfoInput{
			PackageID:             config.Get().GetUint(config.CreditsPricePackage),
			StartedDate:           &creditsPackageEntity.StartDate,
			ExpiredDate:           &creditsPackageEntity.ExpiredDate,
			ChargeCredits:         &creditsPackageEntity.TotalCredits,
			UnitFilters:           transformer.UnitFiltersFromEntities(creditsPackageEntity.UnitFilters),
			DiscountEnabled:       creditsPackageEntity.DiscountEnabled,
			Internal:              creditsPackageEntity.Internal,
			InvoicePayable:        false,
			BindedBillingConfigID: creditsPackage.BindedBillingConfigID,
		})
		if err != nil {
			log.Error("failed to bind package in create credits package", zap.Error(err))
			return err
		}
		creditsPackageEntity.BindedPackageID = &bindedPackageInfo.BindedPackageID
		err = creditsPackageEntity.Create(tx)
		if err != nil {
			log.Error("failed to create credits package", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
		if creditsPackage.UserEmail != "" {
			actionEntity.UserEmail = creditsPackage.UserEmail
			actionEntity.ExtraInfo = fmt.Sprintf(`{"credits_package_id": %d,"foreign_id":"%s"}`, creditsPackageEntity.ID, creditsPackage.ForeignID)

		} else {
			actionEntity.ExtraInfo = fmt.Sprintf(`{"credits_package_id": %d}`, creditsPackageEntity.ID)

		}
		err = actionEntity.Create(tx)
		if err != nil {
			return err
		}
		if fc != nil {
			log.Info("start to process withTxFunc")
			return fc(tx, creditsPackageEntity)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	now := timeutils.GetNow()
	if creditsPackage.StartDate.Unix() <= now.Unix() {
		log.Info("start to open credits", zap.Uint("tenantID", tenantID), zap.Uint("creditsID", creditsPackageEntity.ID))
		err = m.OpenCreditsPackage(creditsPackageEntity.ID)
		if err != nil {
			log.Error("failed to open credits package in create credits package", zap.Uint("tenantID", tenantID), zap.Uint("creditsPackageID", creditsPackageEntity.ID), zap.Error(err))
		}
	}

	if creditsPackage.ExpiredDate.Unix() <= now.Unix() {
		log.Info("start to close credits", zap.Uint("tenantID", tenantID), zap.Uint("creditsID", creditsPackageEntity.ID))
		err = m.CloseCreditsPackage(creditsPackageEntity.ID, entity.CreditsPackageClosedReason_Expired)
		if err != nil {
			log.Error("failed to close credits package in create credits package", zap.Uint("tenantID", tenantID), zap.Uint("creditsPackageID", creditsPackageEntity.ID), zap.Error(err))
		}
	}
	return m.GetCreditsPackage(creditsPackageEntity.ID)
}

func (m *CreditsPackageModel) CreateCreditsPackage(tenantID uint, creditsPackage *forms.CreditsPackage) (ret *forms.CreditsPackage, err error) {
	return m.CreateCreditsPackageAtomically(tenantID, creditsPackage, nil)
}

func validateRerunCreditsPackageWindow(creditsPackage *forms.CreditsPackage) error {
	if creditsPackage == nil || creditsPackage.Usage != entity.CreditsPackageUsage_Rerun {
		return nil
	}
	if !creditsPackage.ExpiredDate.After(creditsPackage.StartDate) {
		return billing_errors.NewInvalidInputError(errors.New("rerun credits expired_date must be after start_date"))
	}
	if creditsPackage.ExpiredDate.After(timeutils.GetDayDate(timeutils.GetNow())) {
		return billing_errors.NewInvalidInputError(errors.New("rerun credits can only target past usage windows"))
	}
	return nil
}

func (m *CreditsPackageModel) OpenCreditsPackage(creditsPackageID uint) error {
	return db.GetDB().Transaction(func(tx *gorm.DB) error {
		return m.OpenCreditsPackageWithTx(tx, creditsPackageID)
	})
}

func (m *CreditsPackageModel) queryCredits(tx *gorm.DB, creditsPackageID uint) (*forms.CreditsPackage, error) {
	var creditsPackage entity.CreditsPackage
	err := errorutils.WrapSQLError(entity.NewCreditsPackageQuerySet(tx.Preload(clause.Associations)).IDEq(creditsPackageID).One(&creditsPackage), fmt.Sprintf("credit package %d", creditsPackageID))
	if err != nil {
		return nil, err
	}
	return creditsPackageFromEntity(&creditsPackage)
}

func (m *CreditsPackageModel) OpenCreditsPackageWithTx(tx *gorm.DB, creditsPackageID uint) error {
	creditsPackage, err := m.queryCredits(tx, creditsPackageID)
	if err != nil {
		return err
	}
	if err != nil {
		log.Error("failed to open credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return err
	}

	if creditsPackage.Status != entity.CreditsPackageStatus_BeforeActive {
		log.Info("failed to open credits package, status is not before active", zap.Uint("creditsPackageID", creditsPackageID), zap.String("status", creditsPackage.Status))
		return nil
	}

	now := timeutils.GetNow()

	if creditsPackage.StartDate.Unix() > now.Unix() {
		err = billing_errors.NewInvalidInputError(fmt.Errorf("credits package start time is not reached, start time:%s", creditsPackage.StartDate))
		log.Error("failed to open credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return err
	}

	err = entity.NewCreditsPackageQuerySet(tx).IDEq(creditsPackageID).GetUpdater().SetStatus(entity.CreditsPackageStatus_Active).Update()
	if err != nil {
		log.Error("failed to open credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
		return err
	}
	if !creditsPackage.Internal {
		err = eventbushandler.Get().PublishEventWithTransaction(CreditsPackageOpenEventType, &eventbushandler.Event{
			EventID:   fmt.Sprint(creditsPackageID),
			EventData: creditsPackage,
		}, tx)
		if err != nil {
			log.Error("failed to send event in open credits package", zap.Uint("creditsPackageID", creditsPackageID), zap.Error(err))
			return err
		}
	}
	// send notification, non block transaction
	m.sendCreditUpdateNotificationIfNeed(creditsPackage, entity.CreditsPackageStatus_Active)
	return nil
}

func (*CreditsPackageModel) GetAllNeedOpenPackagesIDs() ([]uint, error) {
	now := timeutils.GetNow()
	entities := []entity.CreditsPackage{}
	err := entity.NewCreditsPackageQuerySet(db.GetDB()).StatusEq(entity.CreditsPackageStatus_BeforeActive).StartDateLte(now).All(&entities)
	if err != nil {
		return nil, err
	}
	result := []uint{}
	for _, creditsPackageEntity := range entities {
		result = append(result, creditsPackageEntity.ID)
	}
	return result, nil
}

func (*CreditsPackageModel) GetAllExpiredPackageIDs() ([]uint, error) {
	now := timeutils.GetNow()
	fmt.Printf("%s", now)
	entities := []entity.CreditsPackage{}
	err := entity.NewCreditsPackageQuerySet(db.GetDB()).StatusNe(entity.CreditsPackageStatus_InActive).ExpiredDateLte(now).All(&entities)
	if err != nil {
		return nil, err
	}
	result := []uint{}
	for _, creditsPackageEntity := range entities {
		result = append(result, creditsPackageEntity.ID)
	}
	return result, nil
}

func (*CreditsPackageModel) GetAllExhaustedPackageIDs() ([]uint, error) {
	entities := []entity.CreditsPackage{}
	query := entity.NewCreditsPackageQuerySet(db.GetDB()).StatusNe(entity.CreditsPackageStatus_InActive)
	err := query.GetDB().Where("binded_package_id in (select id from binded_packages where remaining_credits <= 0 and status='normal')").Find(&entities).Error
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	result := []uint{}
	for _, creditsPackageEntity := range entities {
		result = append(result, creditsPackageEntity.ID)
	}
	return result, nil
}

var DefaultClosedDate = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)

func (m *CreditsPackageModel) GetTenantCreditsSummary(tenantID uint, billingConfigID uint) (*forms.CreditsSummary, error) {
	creditsPackages, err := m.ListTenantCreditsPackages(tenantID, billingConfigID)
	if err != nil {
		return nil, err
	}

	closedDate := &DefaultClosedDate

	result := &forms.CreditsSummary{
		TotalCredits:                 decimal.Zero,
		TotalCostedCredits:           decimal.Zero,
		AvailableCredits:             decimal.Zero,
		AvailableCostedCredits:       decimal.Zero,
		AvailableRemainingCredits:    decimal.Zero,
		AvailablePOCCredits:          decimal.Zero,
		AvailableCostedPOCCredits:    decimal.Zero,
		AvailableRemainingPOCCredits: decimal.Zero,
	}
	for _, creditsPackage := range creditsPackages {
		result.TotalCredits = result.TotalCredits.Add(creditsPackage.TotalCredits)
		result.TotalCostedCredits = result.TotalCostedCredits.Add(creditsPackage.TotalCredits.Sub(creditsPackage.RemainingCredits))
		if creditsPackage.Status == entity.CreditsPackageStatus_Active {
			result.AvailableCredits = result.AvailableCredits.Add(creditsPackage.TotalCredits)
			result.AvailableCostedCredits = result.AvailableCostedCredits.Add(creditsPackage.TotalCredits.Sub(creditsPackage.RemainingCredits))
			result.AvailableRemainingCredits = result.AvailableRemainingCredits.Add(creditsPackage.RemainingCredits)
			result.ActiveCreditsPacakgesCount += 1
			if creditsPackage.Usage == entity.CreditsPackageUsage_POC {
				result.AvailablePOCCredits = result.AvailablePOCCredits.Add(creditsPackage.TotalCredits)
				result.AvailableCostedPOCCredits = result.AvailableCostedPOCCredits.Add(creditsPackage.TotalCredits.Sub(creditsPackage.RemainingCredits))
				result.AvailableRemainingPOCCredits = result.AvailableRemainingPOCCredits.Add(creditsPackage.RemainingCredits)
				result.ActivePOCCreditsPackagesCount += 1
			}
		}

		if closedDate != nil {
			if creditsPackage.ClosedDate == nil {
				closedDate = nil
			} else {
				if closedDate.Before(*creditsPackage.ClosedDate) {
					closedDate = creditsPackage.ClosedDate
				}
			}
		}
	}
	result.LastCreditsClosedDate = closedDate
	return result, nil
}

func (*CreditsPackageModel) GetAllCreditsOpenedTenantIDs() ([]forms.TenantID, error) {
	result := []forms.TenantID{}
	err := db.GetDB().Raw("select distinct tenant_id from credits_packages where status='active'").Scan(&result).Error
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return result, nil
}

func (*CreditsPackageModel) sendCreditUpdateNotificationIfNeed(targetCredit *forms.CreditsPackage, operation string) {
	if targetCredit.Internal {
		log.Info("no need to send in-app notification for internal credit package update", zap.Uint("tenantID", targetCredit.TenantID), zap.Any("targetCredit", targetCredit))
		return
	}
	notificationEvent := ""
	startDateTime := timeutils.GetNow()
	// before_active -> active, include 'create' and 'open' credit package
	if operation == entity.CreditsPackageStatus_Active {
		notificationEvent = uncutils.OpenCreditPackage
		startDateTime = targetCredit.StartDate
	}
	// reclaim, active -> inactive
	if operation == entity.CreditsPackageClosedReason_Reclaimed {
		if targetCredit.Status != entity.CreditsPackageStatus_Active {
			log.Info("no need to send in-app notification for reclaiming non effective credit package", zap.Uint("tenantID", targetCredit.TenantID), zap.Any("targetCredit", targetCredit))
			return
		}
		notificationEvent = uncutils.ReclaimCreditPackage
	}
	// expire: active -> inactive
	if operation == entity.CreditsPackageClosedReason_Expired {
		notificationEvent = uncutils.ExpireCreditPackage
		startDateTime = targetCredit.ExpiredDate
	}
	// exhaust: active -> inactive
	if operation == entity.CreditsPackageClosedReason_Exhausted {
		notificationEvent = uncutils.ExhaustCreditPackage
	}
	// query tenant info
	tenant, err := centralhandler.GetCentralHandler().GetTenant(targetCredit.TenantID)
	if err != nil {
		log.Error("fail to send in-app notification for credit update, get tenant info error", zap.Uint("tenantID", targetCredit.TenantID), zap.String("event", notificationEvent), zap.Any("targetCredit", targetCredit), zap.Error(err))
		return
	}
	uncutils.SendInAppNotification(targetCredit.TenantID, notificationEvent, map[string]string{
		"orgName":       tenant.Name,
		"creditAmounts": targetCredit.TotalCredits.String(),
		"creditCode":    targetCredit.CreditsCode,
		"startDateTime": startDateTime.Format("2006-01-02 15:04:05 UTC±00:00"),
		"orgId":         fmt.Sprintf("%d", targetCredit.TenantID),
	})
}
