package costexplorer

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"slices"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"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/cachehandler"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	billingsnapshot "github.com/pingcap/dbaas/billing2/model/billing/snapshot"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"github.com/pingcap/dbaas/billing2/utils/array"
	"github.com/pingcap/dbaas/billing2/utils/labels"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/pingcap/dbaas/billing2/utils/transformer"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

const showOrderLowest = 100
const showOrderLowest90 = 90

type CostExplorerModel struct{}

const BetaNoCatName = "Others"
const BetaTail = "(Beta)"

type SpendLimitCostSummary struct {
	TotalRawPrice    decimal.Decimal
	TotalCostPrice   decimal.Decimal
	InvoiceCostPrice decimal.Decimal
	CreditsCostPrice decimal.Decimal
}

type spendLimitCostSummaryRow struct {
	ConsumedSource string          `gorm:"column:consumed_source"`
	CostPrice      decimal.Decimal `gorm:"column:cost_price"`
	RawPrice       decimal.Decimal `gorm:"column:raw_price"`
}

func toBetaName(name string) string {
	return fmt.Sprintf("%s %s", name, BetaTail)
}

func isBetaName(name string) bool {
	return strings.HasSuffix(name, BetaTail)
}

func UnitCostSummaryWithClusterName(tenantID uint, costEntities []entity.UnitCostSummary) {
	if !config.Get().GetBool(config.BillsDisplayProjectAndClusterNameWithID) {
		return
	}
	disabledTenants := config.Get().GetStringSlice(config.DisableClusterDisplayTenants)
	if slices.Contains(disabledTenants, fmt.Sprint(tenantID)) {
		return
	}
	log.Info("start attach cluster name to unit costs", zap.Uint("tenantID", tenantID))
	start := timeutils.GetNow()
	for i := 0; i != len(costEntities); i++ {
		now := timeutils.GetNow()
		if start.Add(time.Second * config.Get().GetDuration(config.MaxAttachLabelTimeSecond)).Before(now) {
			log.Error("failed to get all cluster name", zap.Uint("tenantID", tenantID))
			break
		}
		costEntities[i].Labels = labels.WithClusterName(tenantID, costEntities[i].Unit, costEntities[i].Labels)
	}
	log.Info("finished to attach cluster name to unit costs", zap.Uint("tenantID", tenantID))
}

func UnitCostSummaryWithProjectName(tenantID uint, costEntities []entity.UnitCostSummary) {
	if !config.Get().GetBool(config.BillsDisplayProjectAndClusterNameWithID) {
		return
	}
	start := timeutils.GetNow()
	tenantIDPrefix := fmt.Sprintf("%d-", tenantID)
	list := config.Get().GetStringSlice(config.MonthlyBillsCacheList)
	isBigCustomer := slices.ContainsFunc(list, func(s string) bool {
		return strings.HasPrefix(s, tenantIDPrefix)
	})
	log.Info("start attach project name to unit costs", zap.Uint("tenantID", tenantID), zap.Bool("isBigCustomer", isBigCustomer))
	for i := 0; i != len(costEntities); i++ {
		now := timeutils.GetNow()
		if start.Add(time.Second*config.Get().GetDuration(config.MaxAttachLabelTimeSecond)).Before(now) && !isBigCustomer {
			log.Error("failed to get all project name", zap.Uint("tenantID", tenantID))
			break
		}
		costEntities[i].Labels = labels.WithProjectName(costEntities[i].Labels)
	}
	log.Info("finished to attach project name to unit costs", zap.Uint("tenantID", tenantID), zap.Bool("isBigCustomer", isBigCustomer))
}

func CostsDetailsQueryWithClusterAndProjectName(costEntities []entity.CostsDetailsQuery) {
	if !config.Get().GetBool(config.BillsDisplayProjectAndClusterNameWithID) {
		return
	}
	log.Info("start attach cluster name and project name to unit costs")
	start := timeutils.GetNow()
	disabledTenants := config.Get().GetStringSlice(config.DisableClusterDisplayTenants)
	for i := 0; i != len(costEntities); i++ {
		now := timeutils.GetNow()
		if start.Add(time.Second * config.Get().GetDuration(config.MaxAttachLabelTimeSecond)).Before(now) {
			log.Error("failed to attach cluster name and project name to unit costs", zap.Uint("tenantID", costEntities[i].TenantID))
			break
		}
		costEntities[i].Labels = labels.WithProjectName(costEntities[i].Labels)
		if !slices.Contains(disabledTenants, fmt.Sprint(costEntities[i].TenantID)) {
			costEntities[i].Labels = labels.WithClusterName(costEntities[i].TenantID, costEntities[i].Unit, costEntities[i].Labels)
		}
	}
	log.Info("finished to attach cluster name and project name to unit costs")
}

func CostFromEntity(costEntities []entity.UnitCostSummary, units []forms.UnitInfo) *forms.Costs {
	costs := CostsSummaryFromEntities(costEntities)
	return CostSummaryFromUnitCosts(costs, units)
}

func (c *CostExplorerModel) GetTransferCostsByInvoiceId(tenantID uint, invoiceId uint) ([]entity.DateUnitCostSummary, error) {
	var allCosts []entity.DateUnitCostSummary
	err := c.ForEachTransferCostByInvoiceId(tenantID, invoiceId, func(cost entity.DateUnitCostSummary) error {
		allCosts = append(allCosts, cost)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return allCosts, nil
}

func (c *CostExplorerModel) ForEachTransferCostByInvoiceId(tenantID uint, invoiceId uint, handle func(entity.DateUnitCostSummary) error) error {
	filter := []string{}
	params := map[string]interface{}{}

	{
		filter = append(filter, "tenant_id = @tenant_id")
		params["tenant_id"] = tenantID

		filter = append(filter, "invoice_id = @invoice_id")
		params["invoice_id"] = invoiceId

		filter = append(filter, "status = @status")
		params["status"] = entity.UserUnitCostStatus_DONE

		filter = append(filter, "internal = @internal")
		params["internal"] = false
	}

	query := fmt.Sprintf(`select DATE(from_unixtime(cost_time_stamp)) as date, unit, labels, consumed_source ,sum(cost) as cost, sum(cost_price) as cost_price, sum(raw_price) as raw_price, merged_labels_history
    from user_unit_costs where %s
    group by DATE(from_unixtime(cost_time_stamp)), unit, labels, consumed_source, merged_labels_history;
    `, strings.Join(filter, " and "))

	queryTimeout := config.Get().GetDuration(config.PerDBQueryTimeoutSeconds) * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), queryTimeout)
	defer cancel()

	queryDB := db.GetReadOnlyDB()
	queryStartTime := time.Now()
	rows, err := queryDB.WithContext(ctx).Raw(query, params).Rows()
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			log.Error("GetTransferCostsByInvoiceId query timeout",
				zap.Uint("tenantID", tenantID),
				zap.Uint("invoiceID", invoiceId),
				zap.Duration("timeout", queryTimeout),
				zap.Error(err))
			return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
		}
		return billing_errors.NewSystemError(err)
	}
	defer rows.Close()

	costsCount := 0
	for rows.Next() {
		var cost entity.DateUnitCostSummary
		if err = queryDB.ScanRows(rows, &cost); err != nil {
			if errors.Is(err, context.DeadlineExceeded) {
				log.Error("GetTransferCostsByInvoiceId scan timeout",
					zap.Uint("tenantID", tenantID),
					zap.Uint("invoiceID", invoiceId),
					zap.Duration("timeout", queryTimeout),
					zap.Error(err))
				return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
			}
			return billing_errors.NewSystemError(err)
		}
		costsCount++
		if err = handle(cost); err != nil {
			return err
		}
	}
	if err = rows.Err(); err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			log.Error("GetTransferCostsByInvoiceId rows timeout",
				zap.Uint("tenantID", tenantID),
				zap.Uint("invoiceID", invoiceId),
				zap.Duration("timeout", queryTimeout),
				zap.Error(err))
			return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
		}
		return billing_errors.NewSystemError(err)
	}

	log.Info("GetCostsSummaryByInvoiceId query done",
		zap.Int("costsCount", costsCount),
		zap.Uint("tenantID", tenantID),
		zap.Uint("invoiceID", invoiceId),
		zap.Duration("queryDuration", time.Since(queryStartTime)))
	return nil
}

func (c *CostExplorerModel) buildCalculateCostCacheArgs(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2) (bool, []interface{}) {
	labelsKeys := []string{constant.ClusterLabelKey, constant.ProjectLabelKey, constant.BranchLabelKey}
	labelsArgs := make([]string, len(labelsKeys))
	totalValidArgs := 0
	for i, key := range labelsKeys {
		if val, ok := costQuery.Labels[key]; ok {
			labelsArgs[i] = val
			totalValidArgs++
		} else {
			labelsArgs[i] = "nil"
		}
	}
	if totalValidArgs != len(costQuery.Labels) {
		return false, nil
	}
	return true, []interface{}{
		array.JoinUnitSlice(tenantIDs, "|"), array.JoinUnitSlice(billingConfigIDs, "|"), costQuery.StartDate, costQuery.EndDate, costQuery.InvoiceID,
		array.JoinUnitSlice(costQuery.BindedPackageIDs, "|"), strings.Join(costQuery.Units, "|"), costQuery.IgnoreBeta,
		costQuery.DisableAttachClusterName, costQuery.DisableAttachProjectName,
		strings.Join(labelsArgs, "|"), array.JoinUnitSlice(costQuery.InvoiceIDs, "|"),
	}
}

func (c *CostExplorerModel) CalculateOpenAPIMonthlyCostWithCache(tenantID uint, billingConfigID uint, billedMonth time.Time) (*forms.BillsExplorer, error) {
	costs, err := c.GetMonthlyCostWithCache(tenantID, billingConfigID, billedMonth.Year(), int(billedMonth.Month()))
	if err != nil {
		return nil, err
	}
	billsServiceCosts, overviewBillsCosts := c.GetBillsServiceCostsfromCosts(costs)
	summaryByProject := c.GetSummaryByProjectFromCosts(costs)
	billsExplorer := &forms.BillsExplorer{
		BillsOverView: forms.BillsOverview{
			BilledMonth: billedMonth.Format("2006-01"),
			BillsCosts:  overviewBillsCosts,
		},
		BillsServiceCosts: billsServiceCosts,
		SummaryByProject:  summaryByProject,
	}

	return billsExplorer, nil
}

func (c *CostExplorerModel) CalculateOpenCosts(tenantID uint, billingConfigID uint, date time.Time) (*forms.CostExplorerOpenCostsResponse, error) {
	start := timeutils.GetDayDate(date).UTC()
	end := start.AddDate(0, 0, 1)
	return c.CalculateOpenCostsForRange(tenantID, billingConfigID, start, end)
}

func (c *CostExplorerModel) CalculateOpenCostsForRange(tenantID uint, billingConfigID uint, start time.Time, end time.Time) (*forms.CostExplorerOpenCostsResponse, error) {
	start = timeutils.GetDayDate(start).UTC()
	end = timeutils.GetDayDate(end).UTC()
	if !end.After(start) {
		return nil, billing_errors.NewInvalidInputError(fmt.Errorf("open costs end must be after start"))
	}
	costs, err := c.CalculateCost(tenantID, billingConfigID, &forms.CostExplorerQuery{
		StartDate:       start,
		EndDate:         end,
		IgnoreInvoiceId: true,
	})
	if err != nil {
		return nil, err
	}
	details, err := c.getBillsDetailsForRange(tenantID, billingConfigID, start, end)
	if err != nil {
		return nil, err
	}
	if details == nil {
		return nil, nil
	}
	billsServiceCosts, overviewBillsCosts := c.GetBillsServiceCostsfromCosts(costs)
	summaryByProject := c.GetSummaryByProjectFromCosts(costs)
	roundingAdjustmentCosts, err := c.calculateOpenCostRoundingAdjustmentCosts(tenantID, billingConfigID, start, end)
	if err != nil {
		return nil, err
	}
	roundingAdjustments := buildRoundingAdjustmentBreakdown(roundingAdjustmentCosts)
	billsServiceCosts = applyRoundingAdjustmentToCostExplorer(&overviewBillsCosts, billsServiceCosts, &summaryByProject, roundingAdjustments)
	matchedAdjustments, err := lookupLegacyOpenCostMatchedAdjustments(tenantID, billingConfigID, start, end)
	if err != nil {
		return nil, err
	}
	// LegacyRowCount mirrors the non-internal rows the legacy cost sum reads
	// (external costs), so it is comparable to the snapshot fact counts.
	var legacyRowCount int64
	if err := db.GetReadOnlyDB().Model(&entity.UserUnitCost{}).
		Where("tenant_id = ? AND binded_billing_config_id = ? AND status = ? AND internal = ? AND cost_time_stamp >= ? AND cost_time_stamp < ?",
			tenantID, billingConfigID, entity.UserUnitCostStatus_DONE, false, start.Unix(), end.Unix()).
		Count(&legacyRowCount).Error; err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return buildCostExplorerOpenCostsResponse(
		openCostsBilledDateLabel(start, end),
		overviewBillsCosts,
		billsServiceCosts,
		summaryByProject,
		*details,
		forms.CostExplorerOpenCostsMeta{
			SourceMode:              "legacy_open_costs",
			TenantID:                tenantID,
			BillingConfigID:         billingConfigID,
			PeriodMonth:             start.Format("2006-01"),
			WindowStart:             start.Format(time.RFC3339),
			WindowEnd:               end.Format(time.RFC3339),
			LegacyRowCount:          legacyRowCount,
			MatchedCredits:          overviewBillsCosts.Credits,
			MatchedDiscounts:        overviewBillsCosts.Discounts,
			MatchedCreditPackageIDs: matchedAdjustments.CreditPackageIDs,
			MatchedDiscountIDs:      matchedAdjustments.DiscountIDs,
			MatchedCreditItems:      matchedAdjustments.CreditItems,
			MatchedDiscountItems:    matchedAdjustments.DiscountItems,
		},
		roundingAdjustments,
	), nil
}

func openCostsBilledDateLabel(start time.Time, end time.Time) string {
	lastIncludedDay := end.AddDate(0, 0, -1)
	if start.Equal(lastIncludedDay) {
		return start.Format("2006-01-02")
	}
	return start.Format("2006-01-02") + "/" + lastIncludedDay.Format("2006-01-02")
}

type matchedAdjustmentRefs struct {
	CreditPackageIDs string
	DiscountIDs      string
}

type matchedAdjustmentDetails struct {
	matchedAdjustmentRefs
	CreditItems   []forms.CreditsPackage
	DiscountItems []*forms.Discount
}

type matchedAdjustmentID struct {
	ID uint `gorm:"column:id"`
}

func lookupLegacyOpenCostMatchedAdjustments(tenantID uint, billingConfigID uint, start time.Time, end time.Time) (matchedAdjustmentDetails, error) {
	var creditIDs []matchedAdjustmentID
	err := db.GetDB().Raw(`
		SELECT binded_package_id AS id
		FROM user_unit_costs
		WHERE tenant_id = ?
		  AND binded_billing_config_id = ?
		  AND status = ?
		  AND cost_time_stamp >= ? AND cost_time_stamp < ?
		  AND consumed_source = ?
		  AND internal = ?
		  AND binded_package_id <> 0
		GROUP BY binded_package_id
		ORDER BY binded_package_id
	`, tenantID, billingConfigID, entity.UserUnitCostStatus_DONE, start.Unix(), end.Unix(), entity.ConsumedSource_Credits, false).
		Scan(&creditIDs).Error
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	var discountIDs []matchedAdjustmentID
	err = db.GetDB().Raw(`
		SELECT discount_id AS id
		FROM user_unit_costs
		WHERE tenant_id = ?
		  AND binded_billing_config_id = ?
		  AND status = ?
		  AND cost_time_stamp >= ? AND cost_time_stamp < ?
		  AND consumed_source = ?
		  AND internal = ?
		  AND discount_id <> 0
		GROUP BY discount_id
		ORDER BY discount_id
	`, tenantID, billingConfigID, entity.UserUnitCostStatus_DONE, start.Unix(), end.Unix(), entity.ConsumedSource_Invoice, false).
		Scan(&discountIDs).Error
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}

	creditItems, err := buildMatchedCreditItems(creditIDs)
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	discountItems, err := buildMatchedDiscountItems(discountIDs)
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	return matchedAdjustmentDetails{
		matchedAdjustmentRefs: matchedAdjustmentRefs{
			CreditPackageIDs: joinMatchedAdjustmentIDs(creditIDs),
			DiscountIDs:      joinMatchedAdjustmentIDs(discountIDs),
		},
		CreditItems:   creditItems,
		DiscountItems: discountItems,
	}, nil
}

func lookupSnapshotRunMatchedAdjustments(runID uint) (matchedAdjustmentDetails, error) {
	var creditIDs []matchedAdjustmentID
	err := db.GetDB().Raw(`
		SELECT CAST(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_binded_package_id')) AS UNSIGNED) AS id
		FROM billing_snapshot_settlement_fact
		WHERE run_id = ?
		  AND consumed_source = ?
		  AND is_internal = ?
		  AND NULLIF(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_binded_package_id')), '') IS NOT NULL
		GROUP BY CAST(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_binded_package_id')) AS UNSIGNED)
		ORDER BY CAST(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_binded_package_id')) AS UNSIGNED)
	`, runID, entity.ConsumedSource_Credits, false).
		Scan(&creditIDs).Error
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	var discountIDs []matchedAdjustmentID
	err = db.GetDB().Raw(`
		SELECT CAST(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_discount_id')) AS UNSIGNED) AS id
		FROM billing_snapshot_settlement_fact
		WHERE run_id = ?
		  AND is_internal = ?
		  AND NULLIF(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_discount_id')), '') IS NOT NULL
		GROUP BY CAST(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_discount_id')) AS UNSIGNED)
		ORDER BY CAST(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.snapshot_discount_id')) AS UNSIGNED)
	`, runID, false).
		Scan(&discountIDs).Error
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	creditItems, err := buildMatchedCreditItems(creditIDs)
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	discountItems, err := buildMatchedDiscountItems(discountIDs)
	if err != nil {
		return matchedAdjustmentDetails{}, err
	}
	return matchedAdjustmentDetails{
		matchedAdjustmentRefs: matchedAdjustmentRefs{
			CreditPackageIDs: joinMatchedAdjustmentIDs(creditIDs),
			DiscountIDs:      joinMatchedAdjustmentIDs(discountIDs),
		},
		CreditItems:   creditItems,
		DiscountItems: discountItems,
	}, nil
}

func buildMatchedCreditItems(ids []matchedAdjustmentID) ([]forms.CreditsPackage, error) {
	if len(ids) == 0 {
		return nil, nil
	}
	bindedPackageIDs := make([]uint, 0, len(ids))
	for _, id := range ids {
		if id.ID == 0 {
			continue
		}
		bindedPackageIDs = append(bindedPackageIDs, id.ID)
	}
	if len(bindedPackageIDs) == 0 {
		return nil, nil
	}
	var packages []entity.CreditsPackage
	err := db.GetDB().
		Preload("BindedPackage").
		Preload("UnitFilters").
		Where("binded_package_id IN ?", bindedPackageIDs).
		Find(&packages).Error
	if err != nil {
		return nil, err
	}
	items := make([]forms.CreditsPackage, 0, len(packages))
	for _, pkg := range packages {
		if pkg.BindedPackageID == nil {
			continue
		}
		items = append(items, matchedCreditPackageFromEntity(&pkg))
	}
	sort.Slice(items, func(i, j int) bool {
		if items[i].BindedPackageID == nil || items[j].BindedPackageID == nil {
			return items[i].CreditsPackageID < items[j].CreditsPackageID
		}
		return *items[i].BindedPackageID < *items[j].BindedPackageID
	})
	return items, nil
}

func buildMatchedDiscountItems(ids []matchedAdjustmentID) ([]*forms.Discount, error) {
	if len(ids) == 0 {
		return nil, nil
	}
	discountIDs := make([]uint, 0, len(ids))
	for _, id := range ids {
		if id.ID == 0 {
			continue
		}
		discountIDs = append(discountIDs, id.ID)
	}
	if len(discountIDs) == 0 {
		return nil, nil
	}
	var discounts []entity.Discount
	err := db.GetDB().
		Preload("UnitFilters").
		Where("id IN ?", discountIDs).
		Find(&discounts).Error
	if err != nil {
		return nil, err
	}
	items := make([]*forms.Discount, 0, len(discounts))
	for _, discount := range discounts {
		items = append(items, matchedDiscountFromEntity(&discount))
	}
	sort.Slice(items, func(i, j int) bool {
		return items[i].DiscountID < items[j].DiscountID
	})
	return items, nil
}

func matchedCreditPackageFromEntity(pkg *entity.CreditsPackage) forms.CreditsPackage {
	billingConfigID := pkg.BindedBillingConfigID
	orderedUnitFilters := transformer.OrderedUnitFilter(pkg.UnitFilters)
	creditsPackage := forms.CreditsPackage{
		TenantID:              pkg.TenantID,
		CreditsPackageID:      pkg.ID,
		CreditsCode:           pkg.CreditsCode,
		Status:                pkg.Status,
		StartDate:             pkg.StartDate,
		ExpiredDate:           pkg.ExpiredDate,
		ClosedDate:            pkg.ClosedDate,
		BindedPackageID:       pkg.BindedPackageID,
		TotalCredits:          pkg.TotalCredits,
		Type:                  transformer.JoinUnitFilterNames(orderedUnitFilters, ", "),
		Usage:                 pkg.Usage,
		ClosedReason:          pkg.ClosedReason,
		Note:                  pkg.Note,
		DiscountEnabled:       pkg.DiscountEnabled,
		UnitFilters:           transformer.UnitFiltersSlimFromEntities(orderedUnitFilters),
		UnitFiltersInternal:   transformer.UnitFiltersFromEntities(orderedUnitFilters),
		Internal:              pkg.Internal,
		BindedBillingConfigID: &billingConfigID,
		UsageExtend:           pkg.UsageExtend,
		CopyUnitFilterArgs:    (&unit_filter.Model{}).CopyUnitFilterArgsFromEntity(pkg.CopyUnitFilterArgs),
	}
	creditsPackage.BindTypes = creditsPackage.UnitFilters
	if pkg.BindedPackageID != nil && pkg.BindedPackage != nil {
		creditsPackage.RemainingCredits = decimal.Max(pkg.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
}

func matchedDiscountFromEntity(ent *entity.Discount) *forms.Discount {
	status := ent.Status
	now := timeutils.GetNow()
	if ent.Status != constant.DiscountStatus_Reclaimed {
		if ent.ExpiredAt.After(now) {
			status = constant.DiscountStatus_Valid
		} else {
			status = constant.DiscountStatus_Expired
		}
	}
	billingConfigID := ent.BindedBillingConfigID
	orderedUnitFilters := transformer.OrderedUnitFilter(ent.UnitFilters)
	return &forms.Discount{
		DiscountID:            ent.ID,
		DiscountSID:           fmt.Sprint(ent.ID),
		Code:                  ent.Code,
		Status:                status,
		Value:                 ent.Value,
		Version:               ent.Version,
		TenantID:              ent.TenantID,
		Usage:                 ent.Usage,
		Note:                  ent.Note,
		Description:           ent.Description,
		StartedAt:             ent.StartedAt,
		ExpiredAt:             ent.ExpiredAt,
		ClosedAt:              ent.ClosedAt,
		Active:                ent.Active,
		BindedBillingConfigID: &billingConfigID,
		UnitFilters:           transformer.UnitFiltersSlimFromEntities(orderedUnitFilters),
		UnitFiltersInternal:   transformer.UnitFiltersFromEntities(orderedUnitFilters),
		CopyUnitFilterArgs:    (&unit_filter.Model{}).CopyUnitFilterArgsFromEntity(ent.CopyUnitFilterArgs),
	}
}

func joinMatchedAdjustmentIDs(values []matchedAdjustmentID) string {
	ids := make([]string, 0, len(values))
	for _, value := range values {
		if value.ID == 0 {
			continue
		}
		ids = append(ids, strconv.FormatUint(uint64(value.ID), 10))
	}
	return strings.Join(ids, ",")
}

func (c *CostExplorerModel) CalculateRecalculateRunCosts(tenantID uint, billingConfigID uint, sourceRunID uint) (*forms.CostExplorerOpenCostsResponse, error) {
	if !billingsnapshot.IsSnapshotEnabledForTenant(tenantID) {
		return nil, billing_errors.NewForbiddenError(fmt.Errorf("snapshot recalculate is disabled for tenant %d", tenantID))
	}
	var run entity.BillingSnapshotRun
	err := db.GetDB().
		Where("run_id = ?", sourceRunID).
		Where("tenant_id = ?", tenantID).
		Where("billing_config_id = ?", billingConfigID).
		First(&run).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, billing_errors.ObjectNotExistsError
	}
	if err != nil {
		return nil, err
	}
	if run.Status != entity.BillingSnapshotRunStatusSuccess && run.Status != entity.BillingSnapshotRunStatusFrozen {
		return nil, billing_errors.NewInvalidInputError(fmt.Errorf("snapshot run %d is not successful: %s", sourceRunID, run.Status))
	}
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	costsAcc := newCostExplorerResultAccumulator(units)
	detailAcc := newBillsDetailsAccumulator(units)
	var factCount int64
	var invoiceFactCount int64
	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	summaryAcc := newSnapshotFactCostSummaryAccumulator(precision)
	detailFactAcc := newSnapshotFactDetailAccumulator()
	rows, err := db.GetDB().
		Model(&entity.BillingSnapshotSettlementFact{}).
		Select(`
			id,
			run_id,
			tenant_id,
			billing_config_id,
			billed_date,
			unit,
			labels,
			consumed_source,
			usage_amount,
			raw_price,
			discount_price,
			credit_price,
			cost_price,
			source_item_ref,
			is_internal,
			created_at
		`).
		Where("run_id = ?", sourceRunID).
		Where("tenant_id = ?", tenantID).
		Where("billing_config_id = ?", billingConfigID).
		Rows()
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var fact entity.BillingSnapshotSettlementFact
		if err := db.GetDB().ScanRows(rows, &fact); err != nil {
			return nil, err
		}
		factCount++
		if fact.IsInternal {
			continue
		}
		if fact.ConsumedSource == entity.ConsumedSource_Invoice {
			invoiceFactCount++
		}
		summaryAcc.add(fact)
		detailFactAcc.add(fact)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	for _, cost := range summaryAcc.costs() {
		costsAcc.add(cost)
	}
	for _, detail := range detailFactAcc.details() {
		if !detailAcc.add(detail) {
			return nil, nil
		}
	}
	costs := costsAcc.result()
	details := detailAcc.result()
	billsServiceCosts, overviewBillsCosts := c.GetBillsServiceCostsfromCosts(&costs)
	summaryByProject := c.GetSummaryByProjectFromCosts(&costs)
	roundingAdjustmentCosts, err := c.calculateSnapshotRoundingAdjustmentCosts(sourceRunID, tenantID, billingConfigID)
	if err != nil {
		return nil, err
	}
	roundingAdjustments := buildRoundingAdjustmentBreakdown(roundingAdjustmentCosts)
	billsServiceCosts = applyRoundingAdjustmentToCostExplorer(&overviewBillsCosts, billsServiceCosts, &summaryByProject, roundingAdjustments)
	matchedAdjustments, err := lookupSnapshotRunMatchedAdjustments(sourceRunID)
	if err != nil {
		return nil, err
	}
	return buildCostExplorerOpenCostsResponse(
		run.PeriodMonth,
		overviewBillsCosts,
		billsServiceCosts,
		summaryByProject,
		*details,
		forms.CostExplorerOpenCostsMeta{
			SourceMode:              run.SourceMode,
			TenantID:                tenantID,
			BillingConfigID:         billingConfigID,
			SourceRunID:             sourceRunID,
			InputBundleID:           run.InputBundleID,
			PeriodMonth:             run.PeriodMonth,
			WindowStart:             run.WindowStart.Format(time.RFC3339),
			WindowEnd:               run.WindowEnd.Format(time.RFC3339),
			FactCount:               factCount,
			InvoiceFactCount:        invoiceFactCount,
			MatchedCredits:          overviewBillsCosts.Credits,
			MatchedDiscounts:        overviewBillsCosts.Discounts,
			MatchedCreditPackageIDs: matchedAdjustments.CreditPackageIDs,
			MatchedDiscountIDs:      matchedAdjustments.DiscountIDs,
			MatchedCreditItems:      matchedAdjustments.CreditItems,
			MatchedDiscountItems:    matchedAdjustments.DiscountItems,
		},
		roundingAdjustments,
	), nil
}

// GetRecalculateRunBillsDetails applies the regular bills-details aggregation
// to settlement facts produced by one completed rerun.
func (c *CostExplorerModel) GetRecalculateRunBillsDetails(tenantID uint, billingConfigID uint, sourceRunID uint) (*forms.CostExplorerDetailsResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	acc := newBillsDetailsAccumulator(units)
	err = c.forEachRecalculateRunFact(tenantID, billingConfigID, sourceRunID, func(fact entity.BillingSnapshotSettlementFact) error {
		if !acc.add(settlementFactToCostsDetailsQuery(fact)) {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return acc.result(), nil
}

// GetRecalculateRunExportDetails applies the regular bills CSV aggregation to
// settlement facts produced by one completed rerun.
func (c *CostExplorerModel) GetRecalculateRunExportDetails(tenantID uint, billingConfigID uint, sourceRunID uint) ([]forms.ExportUsageDetails, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	acc := newBillsExportDetailsAccumulator(units, newDailyExportUsageContext())
	err = c.forEachRecalculateRunFact(tenantID, billingConfigID, sourceRunID, func(fact entity.BillingSnapshotSettlementFact) error {
		if !acc.add(settlementFactToCostsDetailsQuery(fact)) {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return acc.result(), nil
}

func (c *CostExplorerModel) forEachRecalculateRunFact(tenantID uint, billingConfigID uint, sourceRunID uint, handle func(entity.BillingSnapshotSettlementFact) error) error {
	var run entity.BillingSnapshotRun
	err := db.GetDB().Model(&entity.BillingSnapshotRun{}).
		Joins("JOIN billing_snapshot_input_bundle ON billing_snapshot_input_bundle.bundle_id = billing_snapshot_run.input_bundle_id").
		Where("billing_snapshot_run.run_id = ?", sourceRunID).
		Where("billing_snapshot_run.tenant_id = ?", tenantID).
		Where("billing_snapshot_run.billing_config_id = ?", billingConfigID).
		Where("billing_snapshot_input_bundle.bundle_type IN ?", []string{
			entity.BillingSnapshotInputBundleTypeRerunCandidate,
			entity.BillingSnapshotInputBundleTypeRerunPublished,
		}).
		Select("billing_snapshot_run.*").First(&run).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return billing_errors.ObjectNotExistsError
	}
	if err != nil {
		return err
	}
	if run.Status != entity.BillingSnapshotRunStatusSuccess && run.Status != entity.BillingSnapshotRunStatusFrozen {
		return billing_errors.NewInvalidInputError(fmt.Errorf("snapshot run %d is not successful: %s", sourceRunID, run.Status))
	}

	rows, err := db.GetDB().Model(&entity.BillingSnapshotSettlementFact{}).
		Where("run_id = ? AND tenant_id = ? AND billing_config_id = ? AND is_internal = ?", sourceRunID, tenantID, billingConfigID, false).
		Order("id ASC").Rows()
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var fact entity.BillingSnapshotSettlementFact
		if err := db.GetDB().ScanRows(rows, &fact); err != nil {
			return err
		}
		if err := handle(fact); err != nil {
			return err
		}
	}
	return rows.Err()
}

func loadSnapshotInvoiceLink(tenantID uint, billingConfigID uint, invoiceID uint) (*entity.BillingSnapshotInvoiceLink, bool, error) {
	if invoiceID == 0 || !billingsnapshot.IsSnapshotEnabledForTenant(tenantID) {
		return nil, false, nil
	}
	link := &entity.BillingSnapshotInvoiceLink{}
	err := db.GetDB().
		Where("invoice_id = ?", invoiceID).
		Where("tenant_id = ?", tenantID).
		Where("billing_config_id = ?", billingConfigID).
		First(link).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, err
	}
	return link, true, nil
}

type displayRoundingAdjustmentKey struct {
	TenantID  uint
	Unit      string
	LabelsKey string
	Internal  bool
}

type displayRoundingAdjustmentGroup struct {
	Cost      entity.UnitCostSummary
	MergedRaw decimal.Decimal
	SplitRaw  decimal.Decimal
}

func (c *CostExplorerModel) calculateOpenCostRoundingAdjustmentCosts(tenantID uint, billingConfigID uint, start time.Time, end time.Time) (*forms.CostExplorerResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	groups := map[displayRoundingAdjustmentKey]displayRoundingAdjustmentGroup{}
	err = c.queryCostStreaming([]uint{tenantID}, []uint{billingConfigID}, &forms.CostExplorerQueryV2{
		StartDate:       start,
		EndDate:         end,
		IgnoreInvoiceId: true,
	}, func(cost entity.UnitCostSummary) error {
		if cost.Internal || (cost.ConsumedSource != entity.ConsumedSource_Invoice && cost.ConsumedSource != entity.ConsumedSource_Credits) {
			return nil
		}
		key := displayRoundingAdjustmentKey{
			TenantID:  cost.TenantID,
			Unit:      cost.Unit,
			LabelsKey: labelsStableKey(cost.Labels),
			Internal:  cost.Internal,
		}
		group := groups[key]
		if group.Cost.Unit == "" {
			group.Cost = cost
		}
		group.MergedRaw = group.MergedRaw.Add(cost.RawPrice)
		group.SplitRaw = group.SplitRaw.Add(cost.RawPrice.Truncate(precision))
		groups[key] = group
		return nil
	})
	if err != nil {
		return nil, err
	}
	return buildRoundingAdjustmentCosts(units, groups, precision), nil
}

func (c *CostExplorerModel) calculateSnapshotRoundingAdjustmentCosts(runID uint, tenantID uint, billingConfigID uint) (*forms.CostExplorerResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	type snapshotRoundingSummaryGroup struct {
		Cost       entity.UnitCostSummary
		DisplayKey displayRoundingAdjustmentKey
		RawPrice   decimal.Decimal
	}
	summaryGroups := map[snapshotFactCostSummaryKey]snapshotRoundingSummaryGroup{}
	rows, err := db.GetDB().
		Model(&entity.BillingSnapshotSettlementFact{}).
		Select(`
			id,
			run_id,
			tenant_id,
			billing_config_id,
			billed_date,
			unit,
			labels,
			consumed_source,
			usage_amount,
			raw_price,
			discount_price,
			credit_price,
			cost_price,
			source_item_ref,
			is_internal,
			created_at
		`).
		Where("run_id = ?", runID).
		Where("tenant_id = ?", tenantID).
		Where("billing_config_id = ?", billingConfigID).
		Where("consumed_source IN ?", []string{entity.ConsumedSource_Invoice, entity.ConsumedSource_Credits}).
		Where("is_internal = ?", false).
		Rows()
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var fact entity.BillingSnapshotSettlementFact
		if err := db.GetDB().ScanRows(rows, &fact); err != nil {
			return nil, err
		}
		normalizedLabels := normalizeSnapshotFactLabels(fact.Labels)
		cost := settlementFactToUnitCostSummary(fact)
		cost.Labels = normalizedLabels
		summaryKey := snapshotFactCostSummaryKey{
			TenantID:       cost.TenantID,
			Unit:           cost.Unit,
			LabelsKey:      labelsStableKey(normalizedLabels),
			ConsumedSource: cost.ConsumedSource,
			Internal:       cost.Internal,
		}
		displayKey := displayRoundingAdjustmentKey{
			TenantID:  cost.TenantID,
			Unit:      cost.Unit,
			LabelsKey: labelsStableKey(normalizedLabels),
			Internal:  cost.Internal,
		}
		group := summaryGroups[summaryKey]
		if group.Cost.Unit == "" {
			group.Cost = cost
			group.DisplayKey = displayKey
		}
		group.RawPrice = group.RawPrice.Add(cost.RawPrice)
		summaryGroups[summaryKey] = group
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	groups := map[displayRoundingAdjustmentKey]displayRoundingAdjustmentGroup{}
	for _, summaryGroup := range summaryGroups {
		group := groups[summaryGroup.DisplayKey]
		if group.Cost.Unit == "" {
			group.Cost = summaryGroup.Cost
		}
		group.MergedRaw = group.MergedRaw.Add(summaryGroup.RawPrice)
		group.SplitRaw = group.SplitRaw.Add(summaryGroup.RawPrice.Truncate(precision))
		groups[summaryGroup.DisplayKey] = group
	}
	return buildRoundingAdjustmentCosts(units, groups, precision), nil
}

func buildRoundingAdjustmentCosts(units []forms.UnitInfo, groups map[displayRoundingAdjustmentKey]displayRoundingAdjustmentGroup, precision int32) *forms.CostExplorerResult {
	acc := newCostExplorerResultAccumulator(units)
	for _, group := range groups {
		adjustment := group.MergedRaw.Truncate(precision).Sub(group.SplitRaw)
		if adjustment.Equal(decimal.Zero) {
			continue
		}
		cost := group.Cost
		cost.ConsumedSource = entity.ConsumedSource_Invoice
		cost.InvoiceID = entity.InvoiceUnbindID
		cost.RawPrice = adjustment
		cost.CostPrice = adjustment
		cost.Cost = 0
		acc.add(cost)
	}
	result := acc.result()
	return &result
}

type roundingAdjustmentBreakdown struct {
	Overview decimal.Decimal
	Services []forms.BillsServiceCosts
	Summary  forms.SummaryByProject
}

func buildRoundingAdjustmentBreakdown(adjustmentCosts *forms.CostExplorerResult) roundingAdjustmentBreakdown {
	if adjustmentCosts == nil {
		return roundingAdjustmentBreakdown{}
	}
	adjustmentServices, adjustmentOverview := (&CostExplorerModel{}).GetBillsServiceCostsfromCosts(adjustmentCosts)
	adjustmentSummary := (&CostExplorerModel{}).GetSummaryByProjectFromCosts(adjustmentCosts)
	return roundingAdjustmentBreakdown{
		Overview: adjustmentOverview.RunningTotal,
		Services: adjustmentServices,
		Summary:  adjustmentSummary,
	}
}

func applyRoundingAdjustmentToCostExplorer(overview *forms.BillsCosts, services []forms.BillsServiceCosts, summary *forms.SummaryByProject, adjustments roundingAdjustmentBreakdown) []forms.BillsServiceCosts {
	addRoundingAdjustment(overview, adjustments.Overview)
	services = overlayServiceRoundingAdjustments(services, adjustments.Services)
	overlayProjectRoundingAdjustments(summary, adjustments.Summary)
	return services
}

func addRoundingAdjustment(costs *forms.BillsCosts, adjustment decimal.Decimal) {
	if costs == nil || adjustment.Equal(decimal.Zero) {
		return
	}
	costs.RunningTotal = costs.RunningTotal.Add(adjustment)
}

func overlayServiceRoundingAdjustments(target []forms.BillsServiceCosts, adjustments []forms.BillsServiceCosts) []forms.BillsServiceCosts {
	indexByName := map[string]int{}
	for i := range target {
		indexByName[target[i].ServiceName] = i
	}
	for _, adjustment := range adjustments {
		index, ok := indexByName[adjustment.ServiceName]
		if !ok {
			newItem := forms.BillsServiceCosts{ServiceName: adjustment.ServiceName}
			addRoundingAdjustment(&newItem.BillsCosts, adjustment.RunningTotal)
			newItem.ServiceCosts = overlayServiceRoundingAdjustments(newItem.ServiceCosts, adjustment.ServiceCosts)
			target = append(target, newItem)
			indexByName[adjustment.ServiceName] = len(target) - 1
			continue
		}
		addRoundingAdjustment(&target[index].BillsCosts, adjustment.RunningTotal)
		target[index].ServiceCosts = overlayServiceRoundingAdjustments(target[index].ServiceCosts, adjustment.ServiceCosts)
	}
	return target
}

func overlayProjectRoundingAdjustments(target *forms.SummaryByProject, adjustments forms.SummaryByProject) {
	if target == nil {
		return
	}
	projectIndexByName := map[string]int{}
	for i := range target.Projects {
		projectIndexByName[target.Projects[i].ProjectName] = i
	}
	for _, adjustment := range adjustments.Projects {
		index, ok := projectIndexByName[adjustment.ProjectName]
		if !ok {
			newItem := forms.BillsProjects{ProjectName: adjustment.ProjectName}
			addRoundingAdjustment(&newItem.BillsCosts, adjustment.RunningTotal)
			target.Projects = append(target.Projects, newItem)
			projectIndexByName[adjustment.ProjectName] = len(target.Projects) - 1
			continue
		}
		addRoundingAdjustment(&target.Projects[index].BillsCosts, adjustment.RunningTotal)
	}

	otherIndexByName := map[string]int{}
	for i := range target.OtherCharges {
		otherIndexByName[target.OtherCharges[i].ChargeName] = i
	}
	for _, adjustment := range adjustments.OtherCharges {
		index, ok := otherIndexByName[adjustment.ChargeName]
		if !ok {
			newItem := forms.BillsOtherName{ChargeName: adjustment.ChargeName}
			addRoundingAdjustment(&newItem.BillsCosts, adjustment.RunningTotal)
			target.OtherCharges = append(target.OtherCharges, newItem)
			otherIndexByName[adjustment.ChargeName] = len(target.OtherCharges) - 1
			continue
		}
		addRoundingAdjustment(&target.OtherCharges[index].BillsCosts, adjustment.RunningTotal)
	}
}

func buildCostExplorerOpenCostsResponse(
	billedMonth string,
	overview forms.BillsCosts,
	services []forms.BillsServiceCosts,
	summary forms.SummaryByProject,
	details forms.CostExplorerDetailsResult,
	meta forms.CostExplorerOpenCostsMeta,
	roundingAdjustments roundingAdjustmentBreakdown,
) *forms.CostExplorerOpenCostsResponse {
	return &forms.CostExplorerOpenCostsResponse{
		BillsOverView: forms.OpenCostsBillsOverview{
			BilledMonth:         billedMonth,
			OpenCostsBillsCosts: openCostsBillsCosts(overview, roundingAdjustments.Overview),
		},
		BillsServiceCosts: openCostsServiceCosts(services, roundingAdjustments.Services),
		SummaryByProject:  openCostsSummaryByProject(summary, roundingAdjustments.Summary),
		BillsDetails:      details,
		Meta:              meta,
	}
}

func openCostsBillsCosts(costs forms.BillsCosts, roundingAdjustment decimal.Decimal) forms.OpenCostsBillsCosts {
	return forms.OpenCostsBillsCosts{
		RunningTotal:       costs.RunningTotal,
		Discounts:          costs.Discounts,
		Credits:            costs.Credits,
		RoundingAdjustment: roundingAdjustment,
		TotalCost:          costs.TotalCost,
	}
}

func openCostsServiceCosts(services []forms.BillsServiceCosts, adjustments []forms.BillsServiceCosts) []forms.OpenCostsBillsServiceCosts {
	adjustmentByName := map[string]forms.BillsServiceCosts{}
	for _, adjustment := range adjustments {
		adjustmentByName[adjustment.ServiceName] = adjustment
	}
	result := make([]forms.OpenCostsBillsServiceCosts, 0, len(services))
	for _, service := range services {
		adjustment := adjustmentByName[service.ServiceName]
		result = append(result, forms.OpenCostsBillsServiceCosts{
			ServiceName:         service.ServiceName,
			OpenCostsBillsCosts: openCostsBillsCosts(service.BillsCosts, adjustment.RunningTotal),
			ServiceCosts:        openCostsServiceCosts(service.ServiceCosts, adjustment.ServiceCosts),
		})
	}
	return result
}

func openCostsSummaryByProject(summary forms.SummaryByProject, adjustments forms.SummaryByProject) forms.OpenCostsSummaryByProject {
	projectAdjustmentByName := map[string]forms.BillsProjects{}
	for _, adjustment := range adjustments.Projects {
		projectAdjustmentByName[adjustment.ProjectName] = adjustment
	}
	projects := make([]forms.OpenCostsBillsProjects, 0, len(summary.Projects))
	for _, project := range summary.Projects {
		adjustment := projectAdjustmentByName[project.ProjectName]
		projects = append(projects, forms.OpenCostsBillsProjects{
			ProjectName:         project.ProjectName,
			OpenCostsBillsCosts: openCostsBillsCosts(project.BillsCosts, adjustment.RunningTotal),
		})
	}

	otherAdjustmentByName := map[string]forms.BillsOtherName{}
	for _, adjustment := range adjustments.OtherCharges {
		otherAdjustmentByName[adjustment.ChargeName] = adjustment
	}
	otherCharges := make([]forms.OpenCostsBillsOtherName, 0, len(summary.OtherCharges))
	for _, charge := range summary.OtherCharges {
		adjustment := otherAdjustmentByName[charge.ChargeName]
		otherCharges = append(otherCharges, forms.OpenCostsBillsOtherName{
			ChargeName:          charge.ChargeName,
			OpenCostsBillsCosts: openCostsBillsCosts(charge.BillsCosts, adjustment.RunningTotal),
		})
	}

	return forms.OpenCostsSummaryByProject{
		Projects:     projects,
		OtherCharges: otherCharges,
	}
}

func (c *CostExplorerModel) CalculateOpenAPIMonthlyClusterCost(tenantID uint, billingConfigID uint, billedMonth time.Time, clusterID uint) (*forms.BillsExplorerV1, error) {

	costs, err := c.GetClusterMonthlyCost(tenantID, billingConfigID, billedMonth.Year(), int(billedMonth.Month()), clusterID)
	if err != nil {
		return nil, err
	}
	billsServiceCosts, overviewBillsCosts := c.GetBillsServiceCostsfromCosts(costs)
	billsExplorer := &forms.BillsExplorerV1{
		BillsOverView: forms.BillsOverview{
			BilledMonth: billedMonth.Format("2006-01"),
			BillsCosts:  overviewBillsCosts,
		},
		BillsServiceCosts: billsServiceCosts,
	}

	return billsExplorer, nil
}
func (c *CostExplorerModel) CalculatePartnerOpenAPIMonthlyCostWithCache(tenantIDs []uint, billingConfigIDs []uint, billedMonth time.Time, tenantIDAsCustomer bool) (*forms.PartnerBillsExplorer, error) {
	if tenantIDAsCustomer {
		costs, summaryByCustomer, err := c.GetMonthlyCostWithTenantCustomerSummaryWithCache(tenantIDs, billingConfigIDs, billedMonth.Year(), int(billedMonth.Month()))
		if err != nil {
			return nil, err
		}
		return c.toPartnerBillsExplorerWithCustomerSummary(costs, billedMonth, summaryByCustomer)
	}
	costs, err := c.GetMonthlyCostWithCacheWithMultiBillingConfigsWithCache(tenantIDs, billingConfigIDs, billedMonth.Year(), int(billedMonth.Month()))
	if err != nil {
		return nil, err
	}
	return c.toPartnerBillsExplorer(costs, billedMonth, tenantIDAsCustomer)
}

func (c *CostExplorerModel) toPartnerBillsExplorerWithCustomerSummary(costs *forms.CostExplorerResult, billedMonth time.Time, summaryByCustomer *forms.SummaryBillsByCustomer) (*forms.PartnerBillsExplorer, error) {
	billsServiceCosts, overviewBillsCosts := c.GetBillsServiceCostsfromCosts(costs)
	summaryByProject := c.GetSummaryByProjectFromCosts(costs)
	billsExplorer := &forms.BillsExplorer{
		BillsOverView: forms.BillsOverview{
			BilledMonth: billedMonth.Format("2006-01"),
			BillsCosts:  overviewBillsCosts,
		},
		BillsServiceCosts: billsServiceCosts,
		SummaryByProject:  summaryByProject,
	}
	summaryByServices := make([]forms.PartnerBillsServiceCosts, len(billsServiceCosts))
	type copyStruct struct {
		in  *forms.BillsServiceCosts
		out *forms.PartnerBillsServiceCosts
	}
	queue := make([]copyStruct, len(billsServiceCosts))
	for i := 0; i != len(billsServiceCosts); i++ {
		queue[i].in = &billsServiceCosts[i]
		queue[i].out = &summaryByServices[i]
	}
	for len(queue) != 0 {
		elem := queue[0]
		queue = queue[1:]
		elem.out.BillsCosts = elem.in.BillsCosts
		elem.out.ServiceName = elem.in.ServiceName
		if len(elem.in.ServiceCosts) == 0 {
			continue
		}
		elem.out.ServiceCosts = make([]forms.PartnerBillsServiceCosts, len(elem.in.ServiceCosts))
		for i := 0; i != len(elem.in.ServiceCosts); i++ {
			queue = append(queue, copyStruct{in: &elem.in.ServiceCosts[i], out: &elem.out.ServiceCosts[i]})
		}
	}
	return &forms.PartnerBillsExplorer{BillsExplorer: *billsExplorer, SummaryByCustomer: *summaryByCustomer, SummaryByService: summaryByServices}, nil
}

func (c *CostExplorerModel) toPartnerBillsExplorer(costs *forms.CostExplorerResult, billedMonth time.Time, tenantIDAsCustomer bool) (*forms.PartnerBillsExplorer, error) {
	billsServiceCosts, overviewBillsCosts := c.GetBillsServiceCostsfromCosts(costs)
	summaryByProject := c.GetSummaryByProjectFromCosts(costs)
	summaryByCustomer, err := c.GetSummaryByCustomerFromCosts(costs, tenantIDAsCustomer)
	if err != nil {
		return nil, err
	}
	billsExplorer := &forms.BillsExplorer{
		BillsOverView: forms.BillsOverview{
			BilledMonth: billedMonth.Format("2006-01"),
			BillsCosts:  overviewBillsCosts,
		},
		BillsServiceCosts: billsServiceCosts,
		SummaryByProject:  summaryByProject,
	}
	summaryByServices := make([]forms.PartnerBillsServiceCosts, len(billsServiceCosts))
	type copyStruct struct {
		in  *forms.BillsServiceCosts
		out *forms.PartnerBillsServiceCosts
	}
	queue := make([]copyStruct, len(billsServiceCosts))
	for i := 0; i != len(billsServiceCosts); i++ {
		queue[i].in = &billsServiceCosts[i]
		queue[i].out = &summaryByServices[i]
	}
	for len(queue) != 0 {
		elem := queue[0]
		queue = queue[1:]
		elem.out.BillsCosts = elem.in.BillsCosts
		elem.out.ServiceName = elem.in.ServiceName
		if len(elem.in.ServiceCosts) == 0 {
			continue
		}
		elem.out.ServiceCosts = make([]forms.PartnerBillsServiceCosts, len(elem.in.ServiceCosts))
		for i := 0; i != len(elem.in.ServiceCosts); i++ {
			queue = append(queue, copyStruct{in: &elem.in.ServiceCosts[i], out: &elem.out.ServiceCosts[i]})
		}
	}
	return &forms.PartnerBillsExplorer{BillsExplorer: *billsExplorer, SummaryByCustomer: *summaryByCustomer, SummaryByService: summaryByServices}, nil
}

func (c *CostExplorerModel) GetSummaryByCustomerFromCosts(costs *forms.CostExplorerResult, tenantIDAsCustomer bool) (*forms.SummaryBillsByCustomer, error) {
	var err error
	var customers []forms.CustomerBills
	var otherCharges []forms.BillsOtherName
	defaultLabelValue := config.Get().GetString(config.DefaultLabelValueCategory)
	if tenantIDAsCustomer {
		return nil, billing_errors.NewSystemError(errors.New("tenant-as-customer summary should use streaming aggregation"))
	}
	for labelKey, labelsUnitsCosts := range costs.InvoiceCostsCosts.LabelsUnitsCosts {
		if labelKey != constant.MSPCustomerIDLabel {
			continue
		}

		for key2name, categoryCost := range labelsUnitsCosts {

			if key2name == defaultLabelValue {
				// Other Charges
				otherCharges = appendOtherCharges(otherCharges, categoryCost.CategoryUnitsCosts, false)
			} else {
				// Customer
				customers, err = appendCustomer(customers, key2name, categoryCost, false)
				if err != nil {
					return nil, err
				}
			}
		}
	}
	for labelKey, labelsUnitsCosts := range costs.CreditsCosts.LabelsUnitsCosts {
		if labelKey != constant.MSPCustomerIDLabel {
			continue
		}

		for key2name, categoryCost := range labelsUnitsCosts {

			if key2name == defaultLabelValue {
				// Other Charges
				otherCharges = appendOtherCharges(otherCharges, categoryCost.CategoryUnitsCosts, true)
			} else {
				// Customer
				customers, err = appendCustomer(customers, key2name, categoryCost, true)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	return &forms.SummaryBillsByCustomer{
		Customers:    customers,
		OtherCharges: otherCharges,
	}, nil
}

func (c *CostExplorerModel) GetSummaryByProjectFromCosts(costs *forms.CostExplorerResult) forms.SummaryByProject {
	var projects []forms.BillsProjects
	var otherCharges []forms.BillsOtherName
	defaultLabelValue := config.Get().GetString(config.DefaultLabelValueCategory)

	for labelKey, labelsUnitsCosts := range costs.InvoiceCostsCosts.LabelsUnitsCosts {
		if labelKey != constant.ProjectNameLabelKey {
			continue
		}
		for key2name, categoryCost := range labelsUnitsCosts {

			if key2name == defaultLabelValue {
				// Other Charges
				otherCharges = appendOtherCharges(otherCharges, categoryCost.CategoryUnitsCosts, false)
			} else {
				// Project
				projects = appendProject(projects, key2name, categoryCost, false)
			}
		}
	}
	for labelKey, labelsUnitsCosts := range costs.CreditsCosts.LabelsUnitsCosts {
		if labelKey != constant.ProjectNameLabelKey {
			continue
		}

		for key2name, categoryCost := range labelsUnitsCosts {

			if key2name == defaultLabelValue {
				// Other Charges
				otherCharges = appendOtherCharges(otherCharges, categoryCost.CategoryUnitsCosts, true)
			} else {
				// Project
				projects = appendProject(projects, key2name, categoryCost, true)
			}
		}
	}

	return forms.SummaryByProject{
		Projects:     projects,
		OtherCharges: otherCharges,
	}
}

func appendOtherCharges(otherCharges []forms.BillsOtherName, unitCosts map[string]forms.Price, isCredits bool) []forms.BillsOtherName {
	var billsMap = make(map[string]int)
	for index, other := range otherCharges {
		billsMap[other.ChargeName] = index
	}

	for key3name, unitCost := range unitCosts {
		var credits = decimal.Zero
		var totalCost = decimal.Zero
		if isCredits {
			credits = unitCost.CostPrice
		} else {
			totalCost = unitCost.CostPrice
		}
		index, ok := billsMap[key3name]
		if ok {
			otherCharges[index].RunningTotal = otherCharges[index].RunningTotal.Add(unitCost.RawPrice)
			otherCharges[index].Discounts = otherCharges[index].Discounts.Add(unitCost.DiscountPrice)
			otherCharges[index].Credits = otherCharges[index].Credits.Add(credits)
			otherCharges[index].TotalCost = otherCharges[index].TotalCost.Add(totalCost)

		} else {
			otherCharges = append(otherCharges, forms.BillsOtherName{
				ChargeName: key3name,
				BillsCosts: forms.BillsCosts{
					RunningTotal: unitCost.RawPrice,
					Discounts:    unitCost.DiscountPrice,
					Credits:      credits,
					TotalCost:    totalCost,
				},
			})
		}

	}

	return otherCharges
}

func appendCustomer(customers []forms.CustomerBills, customerIDStr string, categoryCosts forms.CategoryCosts, isCredits bool) ([]forms.CustomerBills, error) {
	var billsMap = make(map[uint]int)
	for index, project := range customers {
		billsMap[project.CustomerID] = index
	}
	var credits = decimal.Zero
	var totalCost = decimal.Zero
	if isCredits {
		credits = categoryCosts.CostPrice
	} else {
		totalCost = categoryCosts.CostPrice
	}
	parsed, err := strconv.ParseUint(customerIDStr, 10, 64)
	if err != nil {
		return nil, err
	}
	customerID := uint(parsed)
	index, ok := billsMap[customerID]
	if ok {
		customers[index].RunningTotal = customers[index].RunningTotal.Add(categoryCosts.RawPrice)
		customers[index].Discounts = customers[index].Discounts.Add(categoryCosts.DiscountPrice)
		customers[index].Credits = customers[index].Credits.Add(credits)
		customers[index].TotalCost = customers[index].TotalCost.Add(totalCost)

	} else {
		org, err := centralhandler.GetCentralHandler().GetTenant(customerID)
		if err != nil {
			return nil, err
		}
		customers = append(customers, forms.CustomerBills{
			CustomerID:   customerID,
			CustomerName: org.Name,
			BillsCosts: forms.BillsCosts{
				RunningTotal: categoryCosts.RawPrice,
				Discounts:    categoryCosts.DiscountPrice,
				Credits:      credits,
				TotalCost:    totalCost,
			},
		})
	}

	return customers, nil
}

func appendProject(projects []forms.BillsProjects, projectName string, categoryCosts forms.CategoryCosts, isCredits bool) []forms.BillsProjects {
	var billsMap = make(map[string]int)
	for index, project := range projects {
		billsMap[project.ProjectName] = index
	}
	var credits = decimal.Zero
	var totalCost = decimal.Zero
	if isCredits {
		credits = categoryCosts.CostPrice
	} else {
		totalCost = categoryCosts.CostPrice
	}
	index, ok := billsMap[projectName]
	if ok {
		projects[index].RunningTotal = projects[index].RunningTotal.Add(categoryCosts.RawPrice)
		projects[index].Discounts = projects[index].Discounts.Add(categoryCosts.DiscountPrice)
		projects[index].Credits = projects[index].Credits.Add(credits)
		projects[index].TotalCost = projects[index].TotalCost.Add(totalCost)

	} else {
		projects = append(projects, forms.BillsProjects{
			ProjectName: projectName,
			BillsCosts: forms.BillsCosts{
				RunningTotal: categoryCosts.RawPrice,
				Discounts:    categoryCosts.DiscountPrice,
				Credits:      credits,
				TotalCost:    totalCost,
			},
		})
	}

	return projects
}

func (c *CostExplorerModel) GetBillsServiceCostsfromCosts(costs *forms.CostExplorerResult) ([]forms.BillsServiceCosts, forms.BillsCosts) {
	var bills []forms.BillsServiceCosts
	var overviewBillsCosts forms.BillsCosts

	for key1name, kindCategoryUnitsCost := range costs.InvoiceCostsCosts.KindCategoryUnitsCost {

		billsCost := forms.BillsCosts{
			RunningTotal: kindCategoryUnitsCost.RawPrice,
			Discounts:    kindCategoryUnitsCost.DiscountPrice,
			TotalCost:    kindCategoryUnitsCost.CostPrice,
		}
		bills = append(bills, forms.BillsServiceCosts{
			ServiceName:  key1name,
			BillsCosts:   billsCost,
			ServiceCosts: getServiceCostsFromCategoryCosts(kindCategoryUnitsCost.CategoryItemUnitsCosts, nil, false),
		})
		overviewBillsCosts.RunningTotal = overviewBillsCosts.RunningTotal.Add(billsCost.RunningTotal)
		overviewBillsCosts.Discounts = overviewBillsCosts.Discounts.Add(billsCost.Discounts)
		overviewBillsCosts.TotalCost = overviewBillsCosts.TotalCost.Add(billsCost.TotalCost)

	}
	var billsMap = make(map[string]int)
	for index, bill := range bills {
		billsMap[bill.ServiceName] = index
	}
	for key1name, creditsKindCategoryUnitsCost := range costs.CreditsCosts.KindCategoryUnitsCost {

		billsCost := forms.BillsCosts{
			RunningTotal: creditsKindCategoryUnitsCost.RawPrice,
			Discounts:    creditsKindCategoryUnitsCost.DiscountPrice,
			Credits:      creditsKindCategoryUnitsCost.CostPrice,
		}
		index, ok := billsMap[key1name]
		if ok {
			bills[index].RunningTotal = bills[index].RunningTotal.Add(billsCost.RunningTotal)
			bills[index].Discounts = bills[index].Discounts.Add(billsCost.Discounts)
			bills[index].Credits = bills[index].Credits.Add(billsCost.Credits)
			bills[index].ServiceCosts = getServiceCostsFromCategoryCosts(creditsKindCategoryUnitsCost.CategoryItemUnitsCosts, bills[index].ServiceCosts, true)

		} else {
			bills = append(bills, forms.BillsServiceCosts{
				ServiceName:  key1name,
				BillsCosts:   billsCost,
				ServiceCosts: getServiceCostsFromCategoryCosts(creditsKindCategoryUnitsCost.CategoryItemUnitsCosts, nil, true),
			})
		}

		overviewBillsCosts.RunningTotal = overviewBillsCosts.RunningTotal.Add(billsCost.RunningTotal)
		overviewBillsCosts.Discounts = overviewBillsCosts.Discounts.Add(billsCost.Discounts)
		overviewBillsCosts.Credits = overviewBillsCosts.Credits.Add(billsCost.Credits)

	}
	return bills, overviewBillsCosts
}

func getServiceCostsFromCategoryCosts(categoryCostMap map[string]forms.CategoryCosts, bills []forms.BillsServiceCosts, isCredits bool) []forms.BillsServiceCosts {
	var billsMap = make(map[string]int)
	for index, bill := range bills {
		billsMap[bill.ServiceName] = index
	}
	for key2Name, categoryCosts := range categoryCostMap {
		index, ok := billsMap[key2Name]
		var credits = decimal.Zero
		var totalCost = decimal.Zero
		if isCredits {
			credits = categoryCosts.CostPrice
		} else {
			totalCost = categoryCosts.CostPrice
		}

		if ok {
			bills[index].RunningTotal = bills[index].RunningTotal.Add(categoryCosts.RawPrice)
			bills[index].Discounts = bills[index].Discounts.Add(categoryCosts.DiscountPrice)
			bills[index].Credits = bills[index].Credits.Add(credits)
			bills[index].TotalCost = bills[index].TotalCost.Add(totalCost)
			bills[index].ServiceCosts = getServiceCostsFromUnitsCosts(categoryCosts.CategoryUnitsCosts, bills[index].ServiceCosts, isCredits)
		} else {
			bills = append(bills, forms.BillsServiceCosts{
				ServiceName: key2Name,
				BillsCosts: forms.BillsCosts{
					RunningTotal: categoryCosts.RawPrice,
					Discounts:    categoryCosts.DiscountPrice,
					Credits:      credits,
					TotalCost:    totalCost,
				},
				ServiceCosts: getServiceCostsFromUnitsCosts(categoryCosts.CategoryUnitsCosts, nil, isCredits),
			})
		}

	}
	return bills
}
func getServiceCostsFromUnitsCosts(unitsCostMap map[string]forms.Price, bills []forms.BillsServiceCosts, isCredits bool) []forms.BillsServiceCosts {
	var billsMap = make(map[string]int)
	for index, bill := range bills {
		billsMap[bill.ServiceName] = index
	}

	for key3Name, unitsCosts := range unitsCostMap {
		index, ok := billsMap[key3Name]
		var credits = decimal.Zero
		var totalCost = decimal.Zero

		if isCredits {
			credits = unitsCosts.CostPrice
		} else {
			totalCost = unitsCosts.CostPrice
		}
		if ok {
			bills[index].RunningTotal = bills[index].RunningTotal.Add(unitsCosts.RawPrice)
			bills[index].Discounts = bills[index].Discounts.Add(unitsCosts.DiscountPrice)
			bills[index].Credits = bills[index].Credits.Add(credits)
			bills[index].TotalCost = bills[index].TotalCost.Add(totalCost)

		} else {
			bills = append(bills, forms.BillsServiceCosts{
				ServiceName: key3Name,
				BillsCosts: forms.BillsCosts{
					RunningTotal: unitsCosts.RawPrice,
					Discounts:    unitsCosts.DiscountPrice,
					Credits:      credits,
					TotalCost:    totalCost,
				},
			})
		}

	}
	return bills
}

const forceClusterIndex = "force index(tenant_timestamp_status_cluster)"

type costQueryTenantClusterBucket struct {
	TenantID  uint   `gorm:"column:tenant_id"`
	ClusterID string `gorm:"column:cluster_id"`
}

func buildCostSummaryQuery(index string, filter []string, ignoreInvoiceID bool, shouldRemoveLabelsFromGroupBy bool) string {
	if shouldRemoveLabelsFromGroupBy {
		if ignoreInvoiceID {
			return fmt.Sprintf(`select tenant_id, unit, consumed_source, if(ifnull(internal, 0), 1, 0) as internal, sum(cost_price) as cost_price, sum(raw_price) as raw_price ,sum(cost) as cost
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, consumed_source, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))
		}
		return fmt.Sprintf(`select tenant_id, unit, consumed_source, invoice_id, if(ifnull(internal, 0), 1, 0) as internal, sum(cost_price) as cost_price, sum(raw_price) as raw_price ,sum(cost) as cost
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, consumed_source, invoice_id, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))
	}

	if ignoreInvoiceID {
		return fmt.Sprintf(`select tenant_id, unit, labels, consumed_source, if(ifnull(internal, 0), 1, 0) as internal, sum(cost_price) as cost_price, sum(raw_price) as raw_price ,sum(cost) as cost
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, labels, consumed_source, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))
	}
	return fmt.Sprintf(`select tenant_id, unit, labels, consumed_source, invoice_id, if(ifnull(internal, 0), 1, 0) as internal, sum(cost_price) as cost_price, sum(raw_price) as raw_price ,sum(cost) as cost
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, labels, consumed_source, invoice_id, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))
}

func shouldSplitCostQueryByTenantCluster(tenantIDs []uint, costQuery *forms.CostExplorerQueryV2, shouldRemoveLabelsFromGroupBy bool) bool {
	if shouldRemoveLabelsFromGroupBy || len(tenantIDs) == 0 || costQuery.StartDate.IsZero() || costQuery.EndDate.IsZero() {
		return false
	}
	if len(costQuery.BindedPackageIDs) != 0 {
		return false
	}
	if _, hasClusterFilter := costQuery.Labels[constant.ClusterLabelKey]; hasClusterFilter {
		return false
	}
	return true
}

func copyCostQueryParams(params map[string]interface{}) map[string]interface{} {
	copied := make(map[string]interface{}, len(params)+2)
	for key, value := range params {
		copied[key] = value
	}
	return copied
}

type timeRange struct {
	start, end time.Time
}

// splitDateRangeByDay breaks a time range into per-day intervals so that each sub-query
// against user_unit_costs is bounded to a single day's data and completes within the DB
// timeout (30s). A full-month scan can easily exceed the timeout even with cursor streaming.
// The first range preserves the original start timestamp (not truncated to midnight) and
// the last range preserves the original end timestamp, so partial-day boundaries are exact.
func splitDateRangeByDay(start, end time.Time) []timeRange {
	if !end.After(start) {
		return nil
	}
	var ranges []timeRange
	current := time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
	for current.Before(end) {
		next := current.AddDate(0, 0, 1)
		if next.After(end) {
			next = end
		}
		rangeStart := current
		if len(ranges) == 0 {
			rangeStart = start
		}
		ranges = append(ranges, timeRange{start: rangeStart, end: next})
		current = next
	}
	return ranges
}

// splitDateRangeByMonth breaks a time range into calendar-month intervals. Used for queries
// (like trends) whose GROUP BY already aggregates to daily/monthly granularity — splitting
// by day would produce too many sub-queries. The first range preserves the original start and
// the last preserves the original end, matching splitDateRangeByDay semantics.
func splitDateRangeByMonth(start, end time.Time) []timeRange {
	if !end.After(start) {
		return nil
	}
	var ranges []timeRange
	current := time.Date(start.Year(), start.Month(), 1, 0, 0, 0, 0, start.Location())
	for current.Before(end) {
		next := current.AddDate(0, 1, 0)
		if next.After(end) {
			next = end
		}
		rangeStart := current
		if len(ranges) == 0 {
			rangeStart = start
		}
		ranges = append(ranges, timeRange{start: rangeStart, end: next})
		current = next
	}
	return ranges
}

// getSplitLoopTimeout returns the appropriate split-loop overall timeout. When
// deadline is set (non-zero), it takes precedence — workers use this to set a
// longer deadline for background jobs. Otherwise falls back to the config:
// big customers → BigCustomerSplitLoopTimeoutMinutes (default 30 min),
// regular → SplitLoopTotalTimeoutSeconds (default 120 s).
func getSplitLoopTimeout(isBigCustomer bool, deadline time.Time) time.Duration {
	if !deadline.IsZero() {
		d := time.Until(deadline)
		if d > 0 {
			return d
		}
	}
	if isBigCustomer {
		return time.Duration(config.Get().GetInt(config.BigCustomerSplitLoopTimeoutMinutes)) * time.Minute
	}
	return config.Get().GetDuration(config.SplitLoopTotalTimeoutSeconds) * time.Second
}

// queryCostStreamingJuneDailySplit is the per-day-split variant of the
// streaming cost query, used ONLY for the June 2026 bills freeze — see the
// June2026DailySplit branch in queryCostStreaming and the date check in
// GetMonthlyCost. The main bills path does NOT call this.
//
// June 2026 bills were issued under commit 3bfb2859's per-day-split +
// per-row-truncate algorithm. To preserve the already-issued numbers and
// avoid re-reconciling finance's books, June 2026 specifically still emits
// the per-day-split result. All other months skip this path.
func queryCostStreamingJuneDailySplit(
	streamQuery func(query string, queryParams map[string]interface{}, logTenantIDs []uint) error,
	costQuery *forms.CostExplorerQueryV2,
	filter []string,
	params map[string]interface{},
	tenantIDs []uint,
	shouldRemoveLabelsFromGroupBy bool,
) error {
	if !costQuery.StartDate.IsZero() && !costQuery.EndDate.IsZero() {
		days := splitDateRangeByDay(costQuery.StartDate, costQuery.EndDate)
		if len(days) > 1 {
			log.Info("split cost explore query by day (June 2026 freeze path)",
				zap.Int("dayCount", len(days)),
				zap.Uints("tenantIDs", tenantIDs),
				zap.Time("startDate", costQuery.StartDate),
				zap.Time("endDate", costQuery.EndDate))
			splitCtx, splitCancel := context.WithTimeout(context.Background(),
				getSplitLoopTimeout(shouldRemoveLabelsFromGroupBy, costQuery.SplitLoopDeadline))
			defer splitCancel()

			for _, day := range days {
				if err := splitCtx.Err(); err != nil {
					log.Error("cost explore query cancelled by overall timeout",
						zap.Uints("tenantIDs", tenantIDs),
						zap.Error(err))
					return billing_errors.NewSystemError(fmt.Errorf("cost explore query cancelled: %w", err))
				}
				dayParams := copyCostQueryParams(params)
				dayParams["start_date"] = day.start.Unix() - 1
				dayParams["end_date"] = day.end.Unix()
				dayQuery := buildCostSummaryQuery(forceClusterIndex, filter, costQuery.IgnoreInvoiceId, shouldRemoveLabelsFromGroupBy)
				if err := streamQuery(dayQuery, dayParams, tenantIDs); err != nil {
					return err
				}
			}
			return nil
		}
	}
	// Single-day window or StartDate/EndDate unset — fall through to a plain
	// single-query scan. (June freeze only matters for multi-day windows.)
	return streamQuery(
		buildCostSummaryQuery(forceClusterIndex, filter, costQuery.IgnoreInvoiceId, shouldRemoveLabelsFromGroupBy),
		params, tenantIDs,
	)
}

func getCostQueryTenantClusterBuckets(queryDB *gorm.DB, queryTimeout time.Duration, filter []string, params map[string]interface{}) ([]costQueryTenantClusterBucket, error) {
	query := fmt.Sprintf(`select tenant_id, ifnull(cluster_id, '') as cluster_id
    from user_unit_costs %s
    where %s
    group by tenant_id, ifnull(cluster_id, '')
    order by tenant_id, ifnull(cluster_id, '')`, forceClusterIndex, strings.Join(filter, " and "))

	ctx, cancel := context.WithTimeout(context.Background(), queryTimeout)
	defer cancel()

	var buckets []costQueryTenantClusterBucket
	queryStartTime := time.Now()
	err := queryDB.WithContext(ctx).Raw(query, params).Find(&buckets).Error
	queryDuration := time.Since(queryStartTime)
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			log.Error("tenant cluster bucket query timeout",
				zap.Duration("timeout", queryTimeout),
				zap.String("query", query),
				zap.Any("params", params),
				zap.Error(err))
			return nil, billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
		}
		return nil, billing_errors.NewSystemError(err)
	}
	log.Info("tenant cluster bucket query size",
		zap.Int("bucketCount", len(buckets)),
		zap.Duration("queryDuration", queryDuration))
	return buckets, nil
}

func (c *CostExplorerModel) queryCostStreaming(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2, handleCost func(entity.UnitCostSummary) error) error {
	if len(tenantIDs) != 1 || len(billingConfigIDs) != 1 || !snapshotOverlayQuerySupported(costQuery) {
		return c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, costQuery, handleCost)
	}

	query := *costQuery
	invoiceID := snapshotOverlayInvoiceID(&query)
	if query.StartDate.IsZero() || query.EndDate.IsZero() {
		if invoiceID == 0 {
			return c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, &query, handleCost)
		}
		start, end, found, err := invoiceWindowForSnapshotOverlay(tenantIDs[0], billingConfigIDs[0], invoiceID)
		if err != nil {
			return err
		}
		if !found {
			return c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, &query, handleCost)
		}
		query.StartDate = start
		query.EndDate = end
	}
	if len(query.BindedPackageIDs) != 0 || len(query.InvoiceIDs) > 1 {
		return c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, &query, handleCost)
	}

	queryDB := costExplorerQueryDB(&query)
	plan, err := loadSnapshotOverlayPlanWithDB(queryDB, tenantIDs[0], billingConfigIDs[0], query.StartDate, query.EndDate, invoiceID)
	if err != nil {
		return err
	}
	if plan == nil || len(plan.Segments) == 0 {
		return c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, &query, handleCost)
	}

	// Split both sources first, merge by the same logical key, and only then
	// let the caller apply its existing display precision. This prevents a
	// separate truncate at each source/window boundary.
	merger := newSnapshotOverlayCostMerger(query.IgnoreInvoiceId)
	mergeCost := func(cost entity.UnitCostSummary) error {
		merger.add(cost)
		return nil
	}
	cursor := query.StartDate
	for _, snapshotSegment := range plan.Segments {
		if snapshotSegment.Start.After(cursor) {
			legacySegment := forms.CostExplorerQueryV2(query)
			legacySegment.StartDate = cursor
			legacySegment.EndDate = snapshotSegment.Start
			if err := c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, &legacySegment, mergeCost); err != nil {
				return err
			}
		}
		cursor = snapshotSegment.End
		if err := forEachSnapshotOverlayCost(&snapshotOverlayPlan{Segments: []snapshotOverlaySegment{snapshotSegment}}, tenantIDs[0], billingConfigIDs[0], &query, mergeCost, queryDB); err != nil {
			return err
		}
	}
	if cursor.Before(query.EndDate) {
		legacySegment := forms.CostExplorerQueryV2(query)
		legacySegment.StartDate = cursor
		legacySegment.EndDate = query.EndDate
		if err := c.queryLegacyCostStreaming(tenantIDs, billingConfigIDs, &legacySegment, mergeCost); err != nil {
			return err
		}
	}
	for _, cost := range merger.costs() {
		if err := handleCost(cost); err != nil {
			return err
		}
	}
	return nil
}

func costExplorerQueryDB(costQuery *forms.CostExplorerQueryV2) *gorm.DB {
	if costQuery != nil && costQuery.UseMainDB {
		return db.GetDB()
	}
	return db.GetReadOnlyDB()
}

func (c *CostExplorerModel) queryLegacyCostStreaming(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2, handleCost func(entity.UnitCostSummary) error) error {
	var index string
	filter := []string{"tenant_id in @tenant_id", "status = @status"}
	params := map[string]interface{}{"tenant_id": tenantIDs, "status": entity.UserUnitCostStatus_DONE}

	if costQuery.InvoiceID != 0 {
		filter = append(filter, "invoice_id = @invoice_id")
		params["invoice_id"] = costQuery.InvoiceID
	} else if len(costQuery.InvoiceIDs) != 0 {
		filter = append(filter, "invoice_id in @invoice_ids")
		params["invoice_ids"] = costQuery.InvoiceIDs
	}
	if len(billingConfigIDs) != 0 {
		filter = append(filter, "binded_billing_config_id in @binded_billing_config_id")
		params["binded_billing_config_id"] = billingConfigIDs
	}

	labelFilterIndex := 0
	hasClusterFilter := false
	for key, val := range costQuery.Labels {
		if key == constant.ClusterLabelKey {
			filter = append(filter, "cluster_id = @cluster_id")
			params["cluster_id"] = val
			index = forceClusterIndex
			hasClusterFilter = true
			continue
		}
		pathKey := fmt.Sprintf("label_val_path_%d", labelFilterIndex)
		valKey := fmt.Sprintf("label_val_%d", labelFilterIndex)
		filter = append(filter, fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(labels, @%s)) = @%s", pathKey, valKey))
		params[pathKey] = fmt.Sprintf("$.%s", key)
		params[valKey] = val
		labelFilterIndex++
	}

	if costQuery.IgnoreBeta {
		filter = append(filter, "(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.beta')) is null or JSON_UNQUOTE(JSON_EXTRACT(labels, '$.beta')) != \"true\")")
	}

	if !costQuery.StartDate.IsZero() {
		filter = append(filter, "cost_time_stamp > @start_date")
		params["start_date"] = costQuery.StartDate.Unix() - 1
		index = forceClusterIndex
	}

	if !costQuery.EndDate.IsZero() {
		filter = append(filter, "cost_time_stamp < @end_date")
		params["end_date"] = costQuery.EndDate.Unix()
		index = forceClusterIndex
	}

	if len(costQuery.BindedPackageIDs) != 0 {
		filter = append(filter, "binded_package_id in @binded_package_id")
		params["binded_package_id"] = costQuery.BindedPackageIDs
		if !hasClusterFilter {
			index = ""
		}
	}

	if len(costQuery.Units) != 0 {
		filter = append(filter, "unit in @unit")
		params["unit"] = costQuery.Units
	}

	disabledTenants := config.Get().GetStringSlice(config.DisableClusterDisplayTenants)
	shouldRemoveLabelsFromGroupBy := false

	if len(tenantIDs) > 0 {
		for _, tenantID := range tenantIDs {
			if slices.Contains(disabledTenants, fmt.Sprint(tenantID)) {
				shouldRemoveLabelsFromGroupBy = true
				break
			}
		}

		if shouldRemoveLabelsFromGroupBy && !costQuery.StartDate.IsZero() {
			cutoffDate := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
			if costQuery.StartDate.Before(cutoffDate) {
				shouldRemoveLabelsFromGroupBy = false
			}
		}
	}

	query := buildCostSummaryQuery(index, filter, costQuery.IgnoreInvoiceId, shouldRemoveLabelsFromGroupBy)

	var queryDB *gorm.DB
	if costQuery.UseMainDB {
		queryDB = db.GetDB()
	} else {
		queryDB = db.GetReadOnlyDB()
	}

	queryTimeout := config.Get().GetDuration(config.PerDBQueryTimeoutSeconds) * time.Second
	// Big customers (those that opt out of GROUP BY labels) used to get a
	// 15-minute timeout — pre-3bfb2859 contract restored when we removed the
	// per-day split. Big-customer bills queries scan a full month in a
	// single SQL statement and need the larger window.
	if shouldRemoveLabelsFromGroupBy {
		bigCustomerTimeoutMinutes := config.Get().GetInt(config.BigCustomerQueryTimeoutMinutes)
		if bigCustomerTimeoutMinutes > 0 {
			queryTimeout = time.Duration(bigCustomerTimeoutMinutes) * time.Minute
		} else {
			queryTimeout = 15 * time.Minute
		}
	}

	attachTenantID := uint(0)
	if len(tenantIDs) == 1 && !shouldRemoveLabelsFromGroupBy {
		attachTenantID = tenantIDs[0]
	}
	attachProjectName := attachTenantID != 0 && !costQuery.DisableAttachProjectName && config.Get().GetBool(config.BillsDisplayProjectAndClusterNameWithID)
	// Cluster name attachment is intentionally skipped for tenants in DisableClusterDisplayTenants,
	// matching the legacy UnitCostSummaryWithClusterName behavior.
	attachClusterName := attachTenantID != 0 && !costQuery.DisableAttachClusterName && config.Get().GetBool(config.BillsDisplayProjectAndClusterNameWithID) && !slices.Contains(disabledTenants, fmt.Sprint(attachTenantID))
	// attachStart is set lazily on the first streamed row so the name-attachment budget
	// excludes the (potentially slow) query time, matching the legacy post-query attachment.
	var attachStart time.Time
	attachProjectExpired := false
	attachClusterExpired := false
	tenantIDPrefix := fmt.Sprintf("%d-", attachTenantID)
	monthlyCacheList := config.Get().GetStringSlice(config.MonthlyBillsCacheList)
	isBigCustomer := slices.ContainsFunc(monthlyCacheList, func(s string) bool {
		return strings.HasPrefix(s, tenantIDPrefix)
	})

	streamQuery := func(query string, queryParams map[string]interface{}, logTenantIDs []uint) error {
		ctx, cancel := context.WithTimeout(context.Background(), queryTimeout)
		defer cancel()

		queryStartTime := time.Now()
		rows, err := queryDB.WithContext(ctx).Raw(query, queryParams).Rows()
		queryDuration := time.Since(queryStartTime)
		if err != nil {
			if errors.Is(err, context.DeadlineExceeded) {
				log.Error("streaming query timeout",
					zap.Uints("tenantIDs", logTenantIDs),
					zap.Duration("timeout", queryTimeout),
					zap.Bool("isBigCustomer", shouldRemoveLabelsFromGroupBy),
					zap.Error(err))
				return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
			}
			if strings.Contains(err.Error(), "invalid connection") {
				log.Warn("invalid connection detected on streaming query, retrying with fresh connection",
					zap.Uints("tenantIDs", logTenantIDs),
					zap.Duration("queryDuration", queryDuration),
					zap.Error(err))
				if costQuery.UseMainDB {
					queryDB = db.GetDB()
				} else {
					queryDB = db.GetReadOnlyDB()
				}
				retryStartTime := time.Now()
				rows, err = queryDB.WithContext(ctx).Raw(query, queryParams).Rows()
				retryDuration := time.Since(retryStartTime)
				if err == nil {
					log.Info("retry streaming query succeeded",
						zap.Uints("tenantIDs", logTenantIDs),
						zap.Duration("retryDuration", retryDuration))
				} else {
					log.Error("retry streaming query failed",
						zap.Uints("tenantIDs", logTenantIDs),
						zap.Duration("retryDuration", retryDuration),
						zap.Error(err))
				}
			}
			if err != nil {
				return billing_errors.NewSystemError(err)
			}
		}
		defer rows.Close()

		costsCount := 0
		for rows.Next() {
			var cost entity.UnitCostSummary
			if err = queryDB.ScanRows(rows, &cost); err != nil {
				return billing_errors.NewSystemError(err)
			}
			if (attachProjectName || attachClusterName) && attachStart.IsZero() {
				attachStart = timeutils.GetNow()
			}
			if attachProjectName && !attachProjectExpired {
				now := timeutils.GetNow()
				if attachStart.Add(time.Second*config.Get().GetDuration(config.MaxAttachLabelTimeSecond)).Before(now) && !isBigCustomer {
					log.Error("failed to get all project name", zap.Uint("tenantID", attachTenantID))
					attachProjectExpired = true
				} else {
					cost.Labels = labels.WithProjectName(cost.Labels)
				}
			}
			if attachClusterName && !attachClusterExpired {
				now := timeutils.GetNow()
				if attachStart.Add(time.Second * config.Get().GetDuration(config.MaxAttachLabelTimeSecond)).Before(now) {
					log.Error("failed to get all cluster name", zap.Uint("tenantID", attachTenantID))
					attachClusterExpired = true
				} else {
					cost.Labels = labels.WithClusterName(attachTenantID, cost.Unit, cost.Labels)
				}
			}
			costsCount++
			if err = handleCost(cost); err != nil {
				return err
			}
		}
		if err = rows.Err(); err != nil {
			return billing_errors.NewSystemError(err)
		}

		totalDuration := time.Since(queryStartTime)
		slowLogThreshold := config.Get().GetDuration(config.DBQuerySlowLogThresholdSeconds) * time.Second
		if totalDuration > slowLogThreshold {
			log.Warn("slow streaming query detected",
				zap.Int("costsCount", costsCount),
				zap.Uints("tenantIDs", logTenantIDs),
				zap.Duration("firstRowDuration", queryDuration),
				zap.Duration("queryDuration", totalDuration),
				zap.Duration("timeout", queryTimeout),
				zap.Bool("isBigCustomer", shouldRemoveLabelsFromGroupBy),
				zap.String("query", query),
				zap.Any("params", queryParams))
		} else {
			log.Info("streaming cost explore query size",
				zap.Int("costsCount", costsCount),
				zap.Uints("tenantIDs", logTenantIDs),
				zap.Duration("queryDuration", totalDuration))
		}
		return nil
	}

	// June 2026 bills "hole": re-introduce the per-day-split + per-row-truncate
	// algorithm ONLY for 2026-06. GetMonthlyCost sets June2026DailySplit when
	// (year=2026, month=June). All other months skip this block and use the
	// single-query scan above, which is the correct (pre-3bfb2859) algorithm.
	//
	// Do NOT extend this branch to other months — the per-day-split behaviour
	// was a regression that this branch reverts. The June freeze is preserved
	// because finance already reconciled against the per-day-split numbers.
	if costQuery.June2026DailySplit {
		return queryCostStreamingJuneDailySplit(streamQuery, costQuery, filter, params, tenantIDs, shouldRemoveLabelsFromGroupBy)
	}

	if shouldSplitCostQueryByTenantCluster(tenantIDs, costQuery, shouldRemoveLabelsFromGroupBy) {
		buckets, err := getCostQueryTenantClusterBuckets(queryDB, queryTimeout, filter, params)
		if err != nil {
			return err
		}
		if len(buckets) == 0 {
			return nil
		}
		if len(buckets) > 1 {
			log.Info("split cost explore query by tenant cluster",
				zap.Int("bucketCount", len(buckets)),
				zap.Uints("tenantIDs", tenantIDs))
			for _, bucket := range buckets {
				chunkFilter := append([]string{}, filter...)
				chunkParams := copyCostQueryParams(params)
				chunkFilter = append(chunkFilter, "tenant_id = @split_tenant_id")
				chunkParams["split_tenant_id"] = bucket.TenantID
				if bucket.ClusterID == "" {
					chunkFilter = append(chunkFilter, "(cluster_id is null or cluster_id = '')")
				} else {
					chunkFilter = append(chunkFilter, "cluster_id = @split_cluster_id")
					chunkParams["split_cluster_id"] = bucket.ClusterID
				}
				chunkQuery := buildCostSummaryQuery(forceClusterIndex, chunkFilter, costQuery.IgnoreInvoiceId, shouldRemoveLabelsFromGroupBy)
				if err = streamQuery(chunkQuery, chunkParams, []uint{bucket.TenantID}); err != nil {
					return err
				}
			}
			return nil
		}
	}
	return streamQuery(query, params, tenantIDs)
}

func (*CostExplorerModel) forEachSpendLimitCostRow(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2, handle func(spendLimitCostSummaryRow) error) error {
	var index string
	filter := []string{"tenant_id in @tenant_id", "status = @status", "internal = @internal"}
	params := map[string]interface{}{"tenant_id": tenantIDs, "status": entity.UserUnitCostStatus_DONE, "internal": false}

	if costQuery.InvoiceID != 0 {
		filter = append(filter, "invoice_id = @invoice_id")
		params["invoice_id"] = costQuery.InvoiceID
	} else if len(costQuery.InvoiceIDs) != 0 {
		filter = append(filter, "invoice_id in @invoice_ids")
		params["invoice_ids"] = costQuery.InvoiceIDs
	}
	if len(billingConfigIDs) != 0 {
		filter = append(filter, "binded_billing_config_id in @binded_billing_config_id")
		params["binded_billing_config_id"] = billingConfigIDs
	}

	labelFilterIndex := 0
	for key, val := range costQuery.Labels {
		if key == constant.ClusterLabelKey {
			filter = append(filter, "cluster_id = @cluster_id")
			params["cluster_id"] = val
			continue
		}
		pathKey := fmt.Sprintf("label_val_path_%d", labelFilterIndex)
		valKey := fmt.Sprintf("label_val_%d", labelFilterIndex)
		filter = append(filter, fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(labels, @%s)) = @%s", pathKey, valKey))
		params[pathKey] = fmt.Sprintf("$.%s", key)
		params[valKey] = val
		labelFilterIndex++
	}

	if costQuery.IgnoreBeta {
		filter = append(filter, "(JSON_UNQUOTE(JSON_EXTRACT(labels, '$.beta')) is null or JSON_UNQUOTE(JSON_EXTRACT(labels, '$.beta')) != \"true\")")
	}

	if !costQuery.StartDate.IsZero() {
		filter = append(filter, "cost_time_stamp > @start_date")
		params["start_date"] = costQuery.StartDate.Unix() - 1
		index = forceClusterIndex
	}

	if !costQuery.EndDate.IsZero() {
		filter = append(filter, "cost_time_stamp < @end_date")
		params["end_date"] = costQuery.EndDate.Unix()
		index = forceClusterIndex
	}

	if len(costQuery.BindedPackageIDs) != 0 {
		filter = append(filter, "binded_package_id in @binded_package_id")
		params["binded_package_id"] = costQuery.BindedPackageIDs
		index = ""
	}

	if len(costQuery.Units) != 0 {
		filter = append(filter, "unit in @unit")
		params["unit"] = costQuery.Units
	}

	disabledTenants := config.Get().GetStringSlice(config.DisableClusterDisplayTenants)
	shouldRemoveLabelsFromGroupBy := false

	if len(tenantIDs) > 0 {
		for _, tenantID := range tenantIDs {
			if slices.Contains(disabledTenants, fmt.Sprint(tenantID)) {
				shouldRemoveLabelsFromGroupBy = true
				break
			}
		}

		if shouldRemoveLabelsFromGroupBy && !costQuery.StartDate.IsZero() {
			cutoffDate := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
			if costQuery.StartDate.Before(cutoffDate) {
				shouldRemoveLabelsFromGroupBy = false
			}
		}
	}

	var query string
	if shouldRemoveLabelsFromGroupBy {
		query = fmt.Sprintf(`select consumed_source, sum(cost_price) as cost_price, sum(raw_price) as raw_price
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, consumed_source, invoice_id, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))

		if costQuery.IgnoreInvoiceId {
			query = fmt.Sprintf(`select consumed_source, sum(cost_price) as cost_price, sum(raw_price) as raw_price
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, consumed_source, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))
		}
	} else {
		query = fmt.Sprintf(`select consumed_source, sum(cost_price) as cost_price, sum(raw_price) as raw_price
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, labels, consumed_source, invoice_id, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))

		if costQuery.IgnoreInvoiceId {
			query = fmt.Sprintf(`select consumed_source, sum(cost_price) as cost_price, sum(raw_price) as raw_price
    from user_unit_costs %s
    where %s
    group by tenant_id, unit, labels, consumed_source, if(ifnull(internal, 0), 1, 0);
    `, index, strings.Join(filter, " and "))
		}
	}

	var queryDB *gorm.DB
	if costQuery.UseMainDB {
		queryDB = db.GetDB()
	} else {
		queryDB = db.GetReadOnlyDB()
	}

	queryTimeout := config.Get().GetDuration(config.PerDBQueryTimeoutSeconds) * time.Second
	// Big-customer spend-limit path needs the 15-minute window — same rationale
	// as the bills path above. See the corresponding block in queryCostStreaming.
	if shouldRemoveLabelsFromGroupBy {
		bigCustomerTimeoutMinutes := config.Get().GetInt(config.BigCustomerQueryTimeoutMinutes)
		if bigCustomerTimeoutMinutes > 0 {
			queryTimeout = time.Duration(bigCustomerTimeoutMinutes) * time.Minute
		} else {
			queryTimeout = 15 * time.Minute
		}
	}

	// runQuery executes the spend-limit query for a single set of params and streams
	// rows to the handler. Extracted as a closure so multi-day queries can call it
	// once per day. Each call gets its own context with a fresh timeout.
	runQuery := func(queryParams map[string]interface{}) error {
		ctx, cancel := context.WithTimeout(context.Background(), queryTimeout)
		defer cancel()

		queryStartTime := time.Now()
		rows, err := queryDB.WithContext(ctx).Raw(query, queryParams).Rows()
		if err != nil {
			if errors.Is(err, context.DeadlineExceeded) {
				log.Error("spend limit query timeout",
					zap.Uints("tenantIDs", tenantIDs),
					zap.Duration("timeout", queryTimeout),
					zap.Bool("isBigCustomer", shouldRemoveLabelsFromGroupBy),
					zap.Error(err))
				return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
			}
			if strings.Contains(err.Error(), "invalid connection") {
				log.Warn("invalid connection detected on spend limit query, retrying with fresh connection",
					zap.Uints("tenantIDs", tenantIDs),
					zap.Error(err))
				if costQuery.UseMainDB {
					queryDB = db.GetDB()
				} else {
					queryDB = db.GetReadOnlyDB()
				}
				retryStartTime := time.Now()
				rows, err = queryDB.WithContext(ctx).Raw(query, queryParams).Rows()
				retryDuration := time.Since(retryStartTime)
				if err == nil {
					log.Info("retry spend limit query succeeded",
						zap.Uints("tenantIDs", tenantIDs),
						zap.Duration("retryDuration", retryDuration))
				} else {
					log.Error("retry spend limit query failed",
						zap.Uints("tenantIDs", tenantIDs),
						zap.Duration("retryDuration", retryDuration),
						zap.Error(err))
				}
			}
			if err != nil {
				return billing_errors.NewSystemError(err)
			}
		}
		defer rows.Close()

		costsCount := 0
		for rows.Next() {
			var row spendLimitCostSummaryRow
			if err = queryDB.ScanRows(rows, &row); err != nil {
				if errors.Is(err, context.DeadlineExceeded) {
					log.Error("spend limit query timeout while scanning",
						zap.Uints("tenantIDs", tenantIDs),
						zap.Duration("timeout", queryTimeout),
						zap.Bool("isBigCustomer", shouldRemoveLabelsFromGroupBy),
						zap.Error(err))
					return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
				}
				return billing_errors.NewSystemError(err)
			}
			costsCount++
			if err = handle(row); err != nil {
				return err
			}
		}
		if err = rows.Err(); err != nil {
			if errors.Is(err, context.DeadlineExceeded) {
				log.Error("spend limit query timeout while streaming",
					zap.Uints("tenantIDs", tenantIDs),
					zap.Duration("timeout", queryTimeout),
					zap.Bool("isBigCustomer", shouldRemoveLabelsFromGroupBy),
					zap.Error(err))
				return billing_errors.NewSystemError(fmt.Errorf("query timeout after %v: %w", queryTimeout, err))
			}
			return billing_errors.NewSystemError(err)
		}

		queryDuration := time.Since(queryStartTime)
		slowLogThreshold := config.Get().GetDuration(config.DBQuerySlowLogThresholdSeconds) * time.Second
		if queryDuration > slowLogThreshold {
			log.Warn("slow spend limit query detected",
				zap.Int("costsCount", costsCount),
				zap.Uints("tenantIDs", tenantIDs),
				zap.Duration("queryDuration", queryDuration),
				zap.Duration("timeout", queryTimeout),
				zap.Bool("isBigCustomer", shouldRemoveLabelsFromGroupBy),
				zap.String("query", query),
				zap.Any("params", queryParams))
		} else {
			log.Info("spend limit query size",
				zap.Int("costsCount", costsCount),
				zap.Uints("tenantIDs", tenantIDs),
				zap.Duration("queryDuration", queryDuration))
		}
		return nil
	}

	return runQuery(params)
}

func (c *CostExplorerModel) calculateSpendLimitCost(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQuery) (*SpendLimitCostSummary, error) {
	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	ret := &SpendLimitCostSummary{}
	err := c.forEachSpendLimitCostRow(tenantIDs, billingConfigIDs, &forms.CostExplorerQueryV2{
		StartDate:       costQuery.StartDate,
		EndDate:         costQuery.EndDate,
		InvoiceID:       costQuery.InvoiceID,
		Labels:          costQuery.Labels,
		Units:           costQuery.Units,
		IgnoreBeta:      costQuery.IgnoreBeta,
		IgnoreInvoiceId: costQuery.IgnoreInvoiceId,
		UseMainDB:       costQuery.UseMainDB,
	}, func(row spendLimitCostSummaryRow) error {
		rawPrice := row.RawPrice.Truncate(precision)
		costPrice := row.CostPrice.Truncate(precision)
		ret.TotalRawPrice = ret.TotalRawPrice.Add(rawPrice)
		ret.TotalCostPrice = ret.TotalCostPrice.Add(costPrice)
		if row.ConsumedSource == entity.ConsumedSource_Credits {
			ret.CreditsCostPrice = ret.CreditsCostPrice.Add(costPrice)
			return nil
		}
		ret.InvoiceCostPrice = ret.InvoiceCostPrice.Add(costPrice)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return ret, nil
}

type costExplorerResultAccumulator struct {
	creditsCosts         *costSummaryAccumulator
	bindedInvoiceCosts   *costSummaryAccumulator
	unbindedInvoiceCosts *costSummaryAccumulator
	invoiceCosts         *costSummaryAccumulator
	totalCosts           *costSummaryAccumulator
}

func newCostExplorerResultAccumulator(units []forms.UnitInfo) *costExplorerResultAccumulator {
	return &costExplorerResultAccumulator{
		creditsCosts:         newCostSummaryAccumulator(units),
		bindedInvoiceCosts:   newCostSummaryAccumulator(units),
		unbindedInvoiceCosts: newCostSummaryAccumulator(units),
		invoiceCosts:         newCostSummaryAccumulator(units),
		totalCosts:           newCostSummaryAccumulator(units),
	}
}

func unitCostSummaryFromEntity(cost entity.UnitCostSummary) forms.UnitCostSummary {
	return forms.UnitCostSummary{
		Unit:           cost.Unit,
		Labels:         cost.Labels,
		CostPrice:      cost.CostPrice,
		RawPrice:       cost.RawPrice,
		ConsumedSource: cost.ConsumedSource,
		InvoiceID:      cost.InvoiceID,
		Cost:           cost.Cost,
		Internal:       cost.Internal,
		TenantID:       cost.TenantID,
	}
}

func settlementFactToUnitCostSummary(fact entity.BillingSnapshotSettlementFact) entity.UnitCostSummary {
	invoiceID := 0
	if fact.ConsumedSource == entity.ConsumedSource_Invoice {
		invoiceID = entity.InvoiceUnbindID
	}
	return entity.UnitCostSummary{
		Unit:           fact.Unit,
		Labels:         fact.Labels,
		Internal:       fact.IsInternal,
		CostPrice:      fact.CostPrice,
		RawPrice:       fact.RawPrice,
		ConsumedSource: fact.ConsumedSource,
		InvoiceID:      invoiceID,
		Cost:           fact.UsageAmount,
		TenantID:       fact.TenantID,
	}
}

func settlementFactToCostsDetailsQuery(fact entity.BillingSnapshotSettlementFact) entity.CostsDetailsQuery {
	return entity.CostsDetailsQuery{
		TenantID:              fact.TenantID,
		BindedBillingConfigID: fact.BillingConfigID,
		BilledDate:            fact.BilledDate.Format("2006-01-02"),
		Unit:                  fact.Unit,
		Cost:                  int64(fact.UsageAmount),
		Labels:                fact.Labels,
		ConsumedSource:        fact.ConsumedSource,
		CostPrice:             fact.CostPrice,
		RawPrice:              fact.RawPrice,
	}
}

type snapshotFactCostSummaryKey struct {
	TenantID       uint
	Unit           string
	LabelsKey      string
	ConsumedSource string
	Internal       bool
}

type snapshotFactDetailKey struct {
	TenantID        uint
	BillingConfigID uint
	BilledDate      string
	Unit            string
	LabelsKey       string
	ConsumedSource  string
}

type snapshotFactCostSummaryAccumulator struct {
	precision int32
	items     map[snapshotFactCostSummaryKey]entity.UnitCostSummary
}

type snapshotInvoiceCostSummaryAccumulator struct {
	invoiceID uint
	items     map[snapshotFactCostSummaryKey]entity.UnitCostSummary
}

type snapshotFactDetailAccumulator struct {
	items map[snapshotFactDetailKey]entity.CostsDetailsQuery
}

func newSnapshotFactCostSummaryAccumulator(precision int32) *snapshotFactCostSummaryAccumulator {
	return &snapshotFactCostSummaryAccumulator{
		precision: precision,
		items:     map[snapshotFactCostSummaryKey]entity.UnitCostSummary{},
	}
}

func newSnapshotInvoiceCostSummaryAccumulator(invoiceID uint) *snapshotInvoiceCostSummaryAccumulator {
	return &snapshotInvoiceCostSummaryAccumulator{
		invoiceID: invoiceID,
		items:     map[snapshotFactCostSummaryKey]entity.UnitCostSummary{},
	}
}

func newSnapshotFactDetailAccumulator() *snapshotFactDetailAccumulator {
	return &snapshotFactDetailAccumulator{items: map[snapshotFactDetailKey]entity.CostsDetailsQuery{}}
}

func (a *snapshotFactCostSummaryAccumulator) add(fact entity.BillingSnapshotSettlementFact) {
	normalizedLabels := normalizeSnapshotFactLabels(fact.Labels)
	cost := settlementFactToUnitCostSummary(fact)
	cost.Labels = normalizedLabels
	key := snapshotFactCostSummaryKey{
		TenantID:       cost.TenantID,
		Unit:           cost.Unit,
		LabelsKey:      labelsStableKey(normalizedLabels),
		ConsumedSource: cost.ConsumedSource,
		Internal:       cost.Internal,
	}
	item, ok := a.items[key]
	if !ok {
		a.items[key] = cost
		return
	}
	item.RawPrice = item.RawPrice.Add(cost.RawPrice)
	item.CostPrice = item.CostPrice.Add(cost.CostPrice)
	item.Cost += cost.Cost
	a.items[key] = item
}

func (a *snapshotFactCostSummaryAccumulator) costs() []entity.UnitCostSummary {
	keys := make([]snapshotFactCostSummaryKey, 0, len(a.items))
	for key := range a.items {
		keys = append(keys, key)
	}
	sort.Slice(keys, func(i, j int) bool {
		return snapshotFactCostSummaryKeyLess(keys[i], keys[j])
	})
	costs := make([]entity.UnitCostSummary, 0, len(keys))
	for _, key := range keys {
		cost := a.items[key]
		cost.RawPrice = cost.RawPrice.Truncate(a.precision)
		cost.CostPrice = cost.CostPrice.Truncate(a.precision)
		costs = append(costs, cost)
	}
	return costs
}

func (a *snapshotInvoiceCostSummaryAccumulator) add(fact entity.BillingSnapshotSettlementFact) {
	cost := settlementFactToUnitCostSummary(fact)
	if cost.ConsumedSource == entity.ConsumedSource_Invoice {
		cost.InvoiceID = int(a.invoiceID)
	}
	key := snapshotFactCostSummaryKey{
		TenantID:       cost.TenantID,
		Unit:           cost.Unit,
		LabelsKey:      labelsStableKey(cost.Labels),
		ConsumedSource: cost.ConsumedSource,
		Internal:       cost.Internal,
	}
	item, ok := a.items[key]
	if !ok {
		a.items[key] = cost
		return
	}
	item.RawPrice = item.RawPrice.Add(cost.RawPrice)
	item.CostPrice = item.CostPrice.Add(cost.CostPrice)
	item.Cost += cost.Cost
	a.items[key] = item
}

func (a *snapshotInvoiceCostSummaryAccumulator) costs() []entity.UnitCostSummary {
	keys := make([]snapshotFactCostSummaryKey, 0, len(a.items))
	for key := range a.items {
		keys = append(keys, key)
	}
	sort.Slice(keys, func(i, j int) bool {
		return snapshotFactCostSummaryKeyLess(keys[i], keys[j])
	})
	costs := make([]entity.UnitCostSummary, 0, len(keys))
	for _, key := range keys {
		costs = append(costs, a.items[key])
	}
	return costs
}

func (a *snapshotFactDetailAccumulator) add(fact entity.BillingSnapshotSettlementFact) {
	normalizedLabels := normalizeSnapshotFactLabels(fact.Labels)
	detail := settlementFactToCostsDetailsQuery(fact)
	detail.Labels = normalizedLabels
	key := snapshotFactDetailKey{
		TenantID:        detail.TenantID,
		BillingConfigID: detail.BindedBillingConfigID,
		BilledDate:      detail.BilledDate,
		Unit:            detail.Unit,
		LabelsKey:       labelsStableKey(normalizedLabels),
		ConsumedSource:  detail.ConsumedSource,
	}
	item, ok := a.items[key]
	if !ok {
		a.items[key] = detail
		return
	}
	item.RawPrice = item.RawPrice.Add(detail.RawPrice)
	item.CostPrice = item.CostPrice.Add(detail.CostPrice)
	item.Cost += detail.Cost
	a.items[key] = item
}

func (a *snapshotFactDetailAccumulator) details() []entity.CostsDetailsQuery {
	keys := make([]snapshotFactDetailKey, 0, len(a.items))
	for key := range a.items {
		keys = append(keys, key)
	}
	sort.Slice(keys, func(i, j int) bool {
		return snapshotFactDetailKeyLess(keys[i], keys[j])
	})
	details := make([]entity.CostsDetailsQuery, 0, len(keys))
	for _, key := range keys {
		details = append(details, a.items[key])
	}
	return details
}

func normalizeSnapshotFactLabels(labels entity.Labels) entity.Labels {
	if len(labels) == 0 {
		return entity.Labels{}
	}
	normalized := entity.Labels{}
	for key, value := range labels {
		if strings.HasPrefix(key, "snapshot_") {
			continue
		}
		normalized[key] = value
	}
	return normalized
}

func labelsStableKey(labels entity.Labels) string {
	if len(labels) == 0 {
		return "{}"
	}
	keys := make([]string, 0, len(labels))
	for key := range labels {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	ordered := make([][2]string, 0, len(keys))
	for _, key := range keys {
		ordered = append(ordered, [2]string{key, labels[key]})
	}
	payload, _ := json.Marshal(ordered)
	return string(payload)
}

func snapshotFactCostSummaryKeyLess(left, right snapshotFactCostSummaryKey) bool {
	if left.TenantID != right.TenantID {
		return left.TenantID < right.TenantID
	}
	if left.Unit != right.Unit {
		return left.Unit < right.Unit
	}
	if left.LabelsKey != right.LabelsKey {
		return left.LabelsKey < right.LabelsKey
	}
	if left.ConsumedSource != right.ConsumedSource {
		return left.ConsumedSource < right.ConsumedSource
	}
	return !left.Internal && right.Internal
}

func snapshotFactDetailKeyLess(left, right snapshotFactDetailKey) bool {
	if left.TenantID != right.TenantID {
		return left.TenantID < right.TenantID
	}
	if left.BillingConfigID != right.BillingConfigID {
		return left.BillingConfigID < right.BillingConfigID
	}
	if left.BilledDate != right.BilledDate {
		return left.BilledDate < right.BilledDate
	}
	if left.Unit != right.Unit {
		return left.Unit < right.Unit
	}
	if left.LabelsKey != right.LabelsKey {
		return left.LabelsKey < right.LabelsKey
	}
	return left.ConsumedSource < right.ConsumedSource
}

func (a *costExplorerResultAccumulator) add(cost entity.UnitCostSummary) {
	formCost := unitCostSummaryFromEntity(cost)
	a.totalCosts.add(formCost)
	if cost.ConsumedSource == entity.ConsumedSource_Credits {
		a.creditsCosts.add(formCost)
		return
	}
	a.invoiceCosts.add(formCost)
	if cost.InvoiceID == entity.InvoiceUnbindID {
		a.unbindedInvoiceCosts.add(formCost)
	} else {
		a.bindedInvoiceCosts.add(formCost)
	}
}

func (a *costExplorerResultAccumulator) result() forms.CostExplorerResult {
	return forms.CostExplorerResult{
		CreditsCosts:         *a.creditsCosts.result(),
		BindedInvoiceCosts:   *a.bindedInvoiceCosts.result(),
		UnbindedInvoiceCosts: *a.unbindedInvoiceCosts.result(),
		InvoiceCostsCosts:    *a.invoiceCosts.result(),
		TotalCosts:           *a.totalCosts.result(),
	}
}

type tenantCustomerSummaryAccumulator struct {
	customers map[uint]forms.BillsCosts
}

func newTenantCustomerSummaryAccumulator() *tenantCustomerSummaryAccumulator {
	return &tenantCustomerSummaryAccumulator{customers: map[uint]forms.BillsCosts{}}
}

func (a *tenantCustomerSummaryAccumulator) add(cost forms.UnitCostSummary) {
	customerCosts := a.customers[cost.TenantID]
	customerCosts.RunningTotal = customerCosts.RunningTotal.Add(cost.RawPrice)
	customerCosts.Discounts = customerCosts.Discounts.Add(cost.RawPrice.Sub(cost.CostPrice))
	if cost.ConsumedSource == entity.ConsumedSource_Credits {
		customerCosts.Credits = customerCosts.Credits.Add(cost.CostPrice)
	} else {
		customerCosts.TotalCost = customerCosts.TotalCost.Add(cost.CostPrice)
	}
	a.customers[cost.TenantID] = customerCosts
}

func (a *tenantCustomerSummaryAccumulator) result() (*forms.SummaryBillsByCustomer, error) {
	customerIDs := make([]uint, 0, len(a.customers))
	for customerID := range a.customers {
		customerIDs = append(customerIDs, customerID)
	}
	sort.Slice(customerIDs, func(i, j int) bool {
		return customerIDs[i] < customerIDs[j]
	})

	customers := make([]forms.CustomerBills, 0, len(customerIDs))
	for _, customerID := range customerIDs {
		org, err := centralhandler.GetCentralHandler().GetTenant(customerID)
		if err != nil {
			return nil, err
		}
		customers = append(customers, forms.CustomerBills{
			CustomerID:   customerID,
			CustomerName: org.Name,
			BillsCosts:   a.customers[customerID],
		})
	}
	return &forms.SummaryBillsByCustomer{Customers: customers}, nil
}

func (c *CostExplorerModel) CalculateCostV2(tenantID uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2) (*forms.CompleteCostExplorerResult, error) {
	return c.CalculateCostV2MultiTenants([]uint{tenantID}, billingConfigIDs, costQuery)
}

func (c *CostExplorerModel) CalculateCostV2MultiTenants(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2) (*forms.CompleteCostExplorerResult, error) {
	return c.calculateCostV2MultiTenantsWithHandler(tenantIDs, billingConfigIDs, costQuery, nil)
}

func (c *CostExplorerModel) calculateCostV2MultiTenantsWithHandler(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2, handleCost func(forms.UnitCostSummary) error) (*forms.CompleteCostExplorerResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	internalCosts := newCostExplorerResultAccumulator(units)
	externalCosts := newCostExplorerResultAccumulator(units)
	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	err = c.queryCostStreaming(tenantIDs, billingConfigIDs, costQuery, func(cost entity.UnitCostSummary) error {
		cost.RawPrice = cost.RawPrice.Truncate(precision)
		cost.CostPrice = cost.CostPrice.Truncate(precision)
		formCost := unitCostSummaryFromEntity(cost)
		if handleCost != nil {
			if err := handleCost(formCost); err != nil {
				return err
			}
		}
		if cost.Internal {
			internalCosts.add(cost)
		} else {
			externalCosts.add(cost)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	log.Info("split internal/external costs done")
	res := &forms.CompleteCostExplorerResult{
		InternalCosts: internalCosts.result(),
		ExternalCosts: externalCosts.result(),
	}
	log.Info("calculate costs done ", zap.Uints("tenantIDs", tenantIDs))
	return res, nil
}

func (c *CostExplorerModel) CalculateCostV2MultiTenantsWithTenantCustomerSummary(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2) (*forms.CostExplorerResult, *forms.SummaryBillsByCustomer, error) {
	customerSummary := newTenantCustomerSummaryAccumulator()
	costs, err := c.calculateCostV2MultiTenantsWithHandler(tenantIDs, billingConfigIDs, costQuery, func(cost forms.UnitCostSummary) error {
		if !cost.Internal {
			customerSummary.add(cost)
		}
		return nil
	})
	if err != nil {
		return nil, nil, err
	}
	summary, err := customerSummary.result()
	if err != nil {
		return nil, nil, err
	}
	return &costs.ExternalCosts, summary, nil
}

func (c *CostExplorerModel) ForEachCostSummary(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2, handleCost func(forms.UnitCostSummary) error) error {
	precision := config.Get().GetInt32(config.BillingCostsPrecisionDigits)
	return c.queryCostStreaming(tenantIDs, billingConfigIDs, costQuery, func(cost entity.UnitCostSummary) error {
		cost.RawPrice = cost.RawPrice.Truncate(precision)
		cost.CostPrice = cost.CostPrice.Truncate(precision)
		return handleCost(unitCostSummaryFromEntity(cost))
	})
}

func (c *CostExplorerModel) CalculateCostV2WithCache(tenantID uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2) (*forms.CompleteCostExplorerResult, error) {
	return c.CalculateCostV2MultiTenantsWithCache([]uint{tenantID}, billingConfigIDs, costQuery)
}

func (c *CostExplorerModel) CalculateCostV2MultiTenantsWithCache(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQueryV2) (*forms.CompleteCostExplorerResult, error) {
	canCache, cacheArgs := c.buildCalculateCostCacheArgs(tenantIDs, billingConfigIDs, costQuery)
	if !canCache {
		log.Error("cannot utilize cache, unknown labels key existed", zap.String("Labels", fmt.Sprintf("%v", costQuery.Labels)))
		return c.CalculateCostV2MultiTenants(tenantIDs, billingConfigIDs, costQuery)
	} else {
		ret := &forms.CompleteCostExplorerResult{}
		ok := cachehandler.Get().Get("CalculateCostV2WithCache", ret, cacheArgs...)
		if ok {
			return ret, nil
		}
		ret, err := c.CalculateCostV2MultiTenants(tenantIDs, billingConfigIDs, costQuery)
		if err != nil {
			log.Info("cost explorer cache hits", zap.String("cacheArgs", fmt.Sprintf("%v", cacheArgs)))
			return nil, err
		}
		cachehandler.Get().Set("CalculateCostV2WithCache", ret, cacheArgs...)
		return ret, err
	}
}

func (c *CostExplorerModel) calculateCost(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQuery, withCache bool) (*forms.CostExplorerResult, error) {
	query := &forms.CostExplorerQueryV2{
		StartDate:                costQuery.StartDate,
		EndDate:                  costQuery.EndDate,
		InvoiceID:                costQuery.InvoiceID,
		Labels:                   costQuery.Labels,
		Units:                    costQuery.Units,
		IgnoreBeta:               costQuery.IgnoreBeta,
		UseMainDB:                costQuery.UseMainDB,
		IgnoreInvoiceId:          costQuery.IgnoreInvoiceId,
		DisableAttachClusterName: costQuery.DisableAttachClusterName,
		DisableAttachProjectName: costQuery.DisableAttachProjectName,
		June2026DailySplit:       costQuery.June2026DailySplit,
	}
	var completeResult *forms.CompleteCostExplorerResult
	var err error
	if withCache {
		completeResult, err = c.CalculateCostV2MultiTenantsWithCache(tenantIDs, billingConfigIDs, query)
	} else {
		completeResult, err = c.CalculateCostV2MultiTenants(tenantIDs, billingConfigIDs, query)
	}
	if err != nil {
		return nil, err
	}
	if costQuery.Internal {
		return &completeResult.InternalCosts, nil
	} else {
		return &completeResult.ExternalCosts, nil
	}
}

func (c *CostExplorerModel) CalculateCostWithCache(tenantID uint, billingConfigID uint, costQuery *forms.CostExplorerQuery) (*forms.CostExplorerResult, error) {
	var billingConfigIDs []uint
	if billingConfigID != 0 {
		billingConfigIDs = append(billingConfigIDs, billingConfigID)
	}
	return c.calculateCost([]uint{tenantID}, billingConfigIDs, costQuery, true)
}

func (c *CostExplorerModel) CalculateCost(tenantID uint, billingConfigID uint, costQuery *forms.CostExplorerQuery) (*forms.CostExplorerResult, error) {
	var billingConfigIDs []uint
	if billingConfigID != 0 {
		billingConfigIDs = append(billingConfigIDs, billingConfigID)
	}
	return c.calculateCost([]uint{tenantID}, billingConfigIDs, costQuery, false)
}

func (c *CostExplorerModel) CalculateCostWithMultiBillingConfigs(tenantIDs []uint, billingConfigIDs []uint, costQuery *forms.CostExplorerQuery) (*forms.CostExplorerResult, error) {
	return c.calculateCost(tenantIDs, billingConfigIDs, costQuery, false)
}

func (c *CostExplorerModel) GetInvoiceCosts(tenantID, billingConfigID uint, invoiceID uint, useMainDB bool) (*forms.Costs, error) {
	costs, err := c.CalculateCost(tenantID, billingConfigID, &forms.CostExplorerQuery{InvoiceID: invoiceID, UseMainDB: useMainDB, DisableAttachClusterName: true})
	if err != nil {
		return nil, err
	}
	return &costs.BindedInvoiceCosts, nil
}
func (c *CostExplorerModel) GetMonthlyCostFromCache(tenantID uint, billingConfigID uint, year int, month int) (*forms.CostExplorerResult, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	if hasSnapshotOverlay(tenantID, billingConfigID, monthStart, monthStart.AddDate(0, 1, 0)) {
		return c.GetMonthlyCost(tenantID, billingConfigID, year, month)
	}
	cache := cachehandler.Get()
	result := &forms.CostExplorerResult{}
	found := cache.Get("GetMonthlyCostFromCache", &result, tenantID, billingConfigID, year, month)
	if found {
		return result, nil
	} else {
		return nil, billing_errors.NewSystemError(errors.New("bill viewing is temporarily unavailable. please retry shortly. if the problem continues, please contact support"))
	}
}
func (c *CostExplorerModel) SetMonthlyCostToCache(tenantID uint, billingConfigID uint, year int, month int) error {
	cache := cachehandler.Get()
	result, err := c.GetMonthlyCostNoClusterName(tenantID, billingConfigID, year, month)
	if err == nil {
		now := time.Now()

		// Calculate stabilize date: 3rd of next month at 00:00 UTC
		nextMonth := time.Month(month + 1)
		nextYear := year
		if nextMonth > 12 {
			nextMonth = 1
			nextYear = year + 1
		}
		stabilizeDate := time.Date(nextYear, nextMonth, 3, 0, 0, 0, 0, time.UTC)

		// Set cache timeout: 6 hours for current month, 365 days for stabilized months
		var cacheTimeout time.Duration
		if now.Before(stabilizeDate) {
			cacheTimeout = 6 * time.Hour
		} else {
			cacheTimeout = 365 * 24 * time.Hour
		}

		cache.SetWithTimeOut(cacheTimeout, "GetMonthlyCostFromCache", result, tenantID, billingConfigID, year, month)
	}
	return err
}

func (c *CostExplorerModel) GetMonthlyCostWithCache(tenantID uint, billingConfigID uint, year int, month int) (*forms.CostExplorerResult, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	if hasSnapshotOverlay(tenantID, billingConfigID, monthStart, monthStart.AddDate(0, 1, 0)) {
		return c.GetMonthlyCost(tenantID, billingConfigID, year, month)
	}
	cache := cachehandler.Get()
	result := &forms.CostExplorerResult{}
	found := cache.Get("GetMonthlyCost", &result, tenantID, billingConfigID, year, month)
	if found {
		return result, nil
	}
	result, err := c.GetMonthlyCost(tenantID, billingConfigID, year, month)
	if err == nil {
		cache.Set("GetMonthlyCost", result, tenantID, billingConfigID, year, month)
	}
	return result, err
}

func (c *CostExplorerModel) GetMonthlyCostWithCacheWithMultiBillingConfigsWithCache(tenantIDs []uint, billingConfigIDs []uint, year int, month int) (*forms.CostExplorerResult, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	monthEnd := monthStart.AddDate(0, 1, 0)
	if len(tenantIDs) == 1 && len(billingConfigIDs) == 1 && hasSnapshotOverlay(tenantIDs[0], billingConfigIDs[0], monthStart, monthEnd) {
		return c.GetMonthlyCostWithCacheWithMultiBillingConfigs(tenantIDs, billingConfigIDs, year, month)
	}
	cache := cachehandler.Get()
	result := &forms.CostExplorerResult{}
	cacheArgs := []interface{}{array.JoinUnitSlice(tenantIDs, "|"), array.JoinUnitSlice(billingConfigIDs, "|"), year, month}
	funcName := "GetMonthlyCostWithCacheWithMultiBillingConfigs"
	found := cache.Get(funcName, &result, cacheArgs...)
	if found {
		return result, nil
	}
	result, err := c.GetMonthlyCostWithCacheWithMultiBillingConfigs(tenantIDs, billingConfigIDs, year, month)
	if err == nil {
		cache.Set(funcName, result, cacheArgs...)
	}
	return result, err
}

func (c *CostExplorerModel) GetMonthlyCostWithCacheWithMultiBillingConfigs(tenantIDs []uint, billingConfigIDs []uint, year int, month int) (*forms.CostExplorerResult, error) {
	query := getCostQueryTime(year, month)
	model := &billingconfig.BillingConfigModel{}
	if len(billingConfigIDs) != 0 {
		// TODO here maybe a performance blocker
		mspCustomerCounter := 0
		for _, bid := range billingConfigIDs {
			isMspCustomer, err := model.IsMspCustomerByBillingConfigIDWithCache(bid)
			if err != nil {
				return nil, err
			}
			if isMspCustomer {
				mspCustomerCounter++
			}
		}

		if len(billingConfigIDs) == mspCustomerCounter {
			query.IgnoreInvoiceId = true
		} else if mspCustomerCounter != 0 {
			log.Error("cross query on msp customer billing config and other billing config", zap.Uints("tenantIDs", tenantIDs), zap.Uints("billingConfigIDs", billingConfigIDs))
		}
	}
	result, err := c.CalculateCostWithMultiBillingConfigs(tenantIDs, billingConfigIDs, query)
	return result, err
}

func (c *CostExplorerModel) GetMonthlyCostWithTenantCustomerSummaryWithCache(tenantIDs []uint, billingConfigIDs []uint, year int, month int) (*forms.CostExplorerResult, *forms.SummaryBillsByCustomer, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	monthEnd := monthStart.AddDate(0, 1, 0)
	if len(tenantIDs) == 1 && len(billingConfigIDs) == 1 && hasSnapshotOverlay(tenantIDs[0], billingConfigIDs[0], monthStart, monthEnd) {
		return c.GetMonthlyCostWithTenantCustomerSummary(tenantIDs, billingConfigIDs, year, month)
	}
	type cachedResult struct {
		Costs             forms.CostExplorerResult
		SummaryByCustomer forms.SummaryBillsByCustomer
	}
	cache := cachehandler.Get()
	result := &cachedResult{}
	cacheArgs := []interface{}{array.JoinUnitSlice(tenantIDs, "|"), array.JoinUnitSlice(billingConfigIDs, "|"), year, month}
	funcName := "GetMonthlyCostWithTenantCustomerSummary"
	found := cache.Get(funcName, result, cacheArgs...)
	if found {
		return &result.Costs, &result.SummaryByCustomer, nil
	}
	costs, summaryByCustomer, err := c.GetMonthlyCostWithTenantCustomerSummary(tenantIDs, billingConfigIDs, year, month)
	if err == nil {
		cache.Set(funcName, &cachedResult{Costs: *costs, SummaryByCustomer: *summaryByCustomer}, cacheArgs...)
	}
	return costs, summaryByCustomer, err
}

func (c *CostExplorerModel) GetMonthlyCostWithTenantCustomerSummary(tenantIDs []uint, billingConfigIDs []uint, year int, month int) (*forms.CostExplorerResult, *forms.SummaryBillsByCustomer, error) {
	query := getCostQueryV2Time(year, month)
	model := &billingconfig.BillingConfigModel{}
	if len(billingConfigIDs) != 0 {
		mspCustomerCounter := 0
		for _, bid := range billingConfigIDs {
			isMspCustomer, err := model.IsMspCustomerByBillingConfigIDWithCache(bid)
			if err != nil {
				return nil, nil, err
			}
			if isMspCustomer {
				mspCustomerCounter++
			}
		}

		if len(billingConfigIDs) == mspCustomerCounter {
			query.IgnoreInvoiceId = true
		} else if mspCustomerCounter != 0 {
			log.Error("cross query on msp customer billing config and other billing config", zap.Uints("tenantIDs", tenantIDs), zap.Uints("billingConfigIDs", billingConfigIDs))
		}
	}
	return c.CalculateCostV2MultiTenantsWithTenantCustomerSummary(tenantIDs, billingConfigIDs, query)
}

func (c *CostExplorerModel) GetMonthlyCostNoClusterName(tenantID uint, billingConfigID uint, year int, month int) (*forms.CostExplorerResult, error) {
	query := getCostQueryTime(year, month)
	model := &billingconfig.BillingConfigModel{}
	if billingConfigID != 0 {
		IsMspCustomer, err := model.IsMspCustomerByBillingConfigIDWithCache(billingConfigID)
		if err != nil {
			return nil, err
		}
		if IsMspCustomer {
			query.IgnoreInvoiceId = true
		}
	}
	query.DisableAttachClusterName = true
	result, err := c.CalculateCost(tenantID, billingConfigID, query)
	return result, err
}
func (c *CostExplorerModel) GetMonthlyCost(tenantID uint, billingConfigID uint, year int, month int) (*forms.CostExplorerResult, error) {
	query := getCostQueryTime(year, month)
	model := &billingconfig.BillingConfigModel{}
	if billingConfigID != 0 {
		IsMspCustomer, err := model.IsMspCustomerByBillingConfigIDWithCache(billingConfigID)
		if err != nil {
			return nil, err
		}
		if IsMspCustomer {
			query.IgnoreInvoiceId = true
		}
	}
	// June 2026 bills freeze: re-introduce the per-day-split + per-row-truncate
	// streaming path (commit 3bfb2859 semantics) ONLY for 2026-06, and only when
	// June2026DailySplitEnabled is set. All other months — and June 2026 with the
	// flag off — use the default single-query scan, which is the correct
	// pre-3bfb2859 cents-precise algorithm. See queryCostStreamingJuneDailySplit
	// for the implementation. Removing this branch will recompute June 2026 with
	// the correct cents-precise sum and shift the displayed numbers away from
	// finance's reconciled totals — wait for finance sign-off before removing.
	if config.Get().GetBool(config.June2026DailySplitEnabled) &&
		year == 2026 && time.Month(month) == time.June {
		query.June2026DailySplit = true
	}
	result, err := c.CalculateCost(tenantID, billingConfigID, query)
	return result, err
}

func (c *CostExplorerModel) GetMonthlySpendLimitCost(tenantID uint, billingConfigID uint, year int, month int) (*SpendLimitCostSummary, error) {
	query := getCostQueryTime(year, month)
	var billingConfigIDs []uint
	model := &billingconfig.BillingConfigModel{}
	if billingConfigID != 0 {
		billingConfigIDs = append(billingConfigIDs, billingConfigID)
		IsMspCustomer, err := model.IsMspCustomerByBillingConfigIDWithCache(billingConfigID)
		if err != nil {
			return nil, err
		}
		if IsMspCustomer {
			query.IgnoreInvoiceId = true
		}
	}
	return c.calculateSpendLimitCost([]uint{tenantID}, billingConfigIDs, query)
}

func (c *CostExplorerModel) GetProjectMonthlyCost(tenantID uint, billingConfigID uint, year int, month int, projectID uint) (*forms.CostExplorerResult, error) {
	query := getCostQueryTime(year, month)
	query.Labels = map[string]string{constant.ProjectLabelKey: strconv.FormatUint(uint64(projectID), 10)}
	result, err := c.CalculateCost(tenantID, billingConfigID, query)
	return result, err
}

func (c *CostExplorerModel) GetProjectMonthlySpendLimitCost(tenantID uint, billingConfigID uint, year int, month int, projectID uint) (*SpendLimitCostSummary, error) {
	query := getCostQueryTime(year, month)
	query.Labels = map[string]string{constant.ProjectLabelKey: strconv.FormatUint(uint64(projectID), 10)}
	if billingConfigID == 0 {
		return c.calculateSpendLimitCost([]uint{tenantID}, nil, query)
	}
	return c.calculateSpendLimitCost([]uint{tenantID}, []uint{billingConfigID}, query)
}

func (c *CostExplorerModel) GetClusterMonthlyCost(tenantID uint, billingConfigID uint, year int, month int, clusterID uint) (*forms.CostExplorerResult, error) {
	query := getCostQueryTime(year, month)
	query.Labels = map[string]string{constant.ClusterLabelKey: strconv.FormatUint(uint64(clusterID), 10)}
	result, err := c.CalculateCost(tenantID, billingConfigID, query)
	return result, err
}

func (c *CostExplorerModel) GetClusterMonthlySpendLimitCost(tenantID uint, billingConfigID uint, year int, month int, clusterID uint) (*SpendLimitCostSummary, error) {
	query := getCostQueryTime(year, month)
	query.Labels = map[string]string{constant.ClusterLabelKey: strconv.FormatUint(uint64(clusterID), 10)}
	if billingConfigID == 0 {
		return c.calculateSpendLimitCost([]uint{tenantID}, nil, query)
	}
	return c.calculateSpendLimitCost([]uint{tenantID}, []uint{billingConfigID}, query)
}

func (c *CostExplorerModel) GetCustomerMonthlyCost(tenantID uint, billingConfigID uint, year int, month int, customerID uint) (*forms.CostExplorerResult, error) {
	query := getCostQueryTime(year, month)
	query.Labels = map[string]string{constant.MSPCustomerIDLabel: strconv.FormatUint(uint64(customerID), 10)}
	result, err := c.CalculateCost(tenantID, billingConfigID, query)
	return result, err
}

func (c *CostExplorerModel) GetCustomerMonthlySpendLimitCost(tenantID uint, billingConfigID uint, year int, month int, customerID uint) (*SpendLimitCostSummary, error) {
	query := getCostQueryTime(year, month)
	query.Labels = map[string]string{constant.MSPCustomerIDLabel: strconv.FormatUint(uint64(customerID), 10)}
	if billingConfigID == 0 {
		return c.calculateSpendLimitCost([]uint{tenantID}, nil, query)
	}
	return c.calculateSpendLimitCost([]uint{tenantID}, []uint{billingConfigID}, query)
}

func (c *CostExplorerModel) GetBillsDetailsWithCache(tenantID uint, billingConfigID uint, year int, month int) (*forms.CostExplorerDetailsResult, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	if hasSnapshotOverlay(tenantID, billingConfigID, monthStart, monthStart.AddDate(0, 1, 0)) {
		return c.GetBillsDetails(tenantID, billingConfigID, year, month, 0)
	}
	cache := cachehandler.Get()
	result := &forms.CostExplorerDetailsResult{}
	found := cache.Get("GetBillsDetailsWithCache", &result, tenantID, billingConfigID, year, month)
	if found {
		return result, nil
	}
	result, err := c.GetBillsDetails(tenantID, billingConfigID, year, month, 0)
	if err == nil {
		cache.Set("GetBillsDetailsWithCache", result, tenantID, billingConfigID, year, month)
	}
	return result, err
}

func (c *CostExplorerModel) GetClusterBillsDetailsWithCache(tenantID uint, billingConfigID uint, year int, month int, clusterID uint) (*forms.CostExplorerDetailsResult, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	if hasSnapshotOverlay(tenantID, billingConfigID, monthStart, monthStart.AddDate(0, 1, 0)) {
		return c.GetBillsDetails(tenantID, billingConfigID, year, month, clusterID)
	}
	cache := cachehandler.Get()
	result := &forms.CostExplorerDetailsResult{}
	found := cache.Get("GetClusterBillsDetailsWithCache", &result, tenantID, billingConfigID, year, month, clusterID)
	if found {
		return result, nil
	}
	result, err := c.GetBillsDetails(tenantID, billingConfigID, year, month, clusterID)
	if err == nil {
		cache.Set("GetClusterBillsDetailsWithCache", result, tenantID, billingConfigID, year, month, clusterID)
	}
	return result, err
}

func (c *CostExplorerModel) GetPartnerBillsDetails(tenantIDs []uint, billingConfigIDs []uint, year int, month int, tenantIDAsCustomer bool) (*forms.PartnerBillsDetails, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	acc := newPartnerBillsDetailsAccumulator(units, tenantIDAsCustomer)
	err = forEachCostsDetailMultiBillingConfigs(tenantIDs, billingConfigIDs, year, month, nil, 0, func(cost entity.CostsDetailsQuery) error {
		ok, addErr := acc.add(cost)
		if addErr != nil {
			return addErr
		}
		if !ok {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return &forms.PartnerBillsDetails{Year: year, Month: month, BillsDetails: nil}, nil
	}
	if err != nil {
		return nil, err
	}
	details, err := acc.result()
	if err != nil {
		return nil, err
	}
	return &forms.PartnerBillsDetails{
		Year:         year,
		Month:        month,
		BillsDetails: details,
	}, nil
}

func (c *CostExplorerModel) GetPartnerBillsDetailsWithCache(tenantIDs []uint, billingConfigIDs []uint, year int, month int, tenantIDAsCustomer bool) (*forms.PartnerBillsDetails, error) {
	funcName := "GetPartnerBillsDetails"
	cacheArgs := []interface{}{array.JoinUnitSlice(tenantIDs, "|"), array.JoinUnitSlice(billingConfigIDs, "|"), year, month, tenantIDAsCustomer}
	ret := &forms.PartnerBillsDetails{}
	found := cachehandler.Get().Get(funcName, ret, cacheArgs...)
	if found {
		return ret, nil
	}
	ret, err := c.GetPartnerBillsDetails(tenantIDs, billingConfigIDs, year, month, tenantIDAsCustomer)
	if err != nil {
		return nil, err
	}
	cachehandler.Get().Set(funcName, ret, cacheArgs...)
	return ret, nil
}

func (c *CostExplorerModel) GetBillsDetails(tenantID uint, billingConfigID uint, year int, month int, clusterID uint) (*forms.CostExplorerDetailsResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	var billingConfigIDs []uint
	if billingConfigID != 0 {
		billingConfigIDs = append(billingConfigIDs, billingConfigID)
	}
	acc := newBillsDetailsAccumulator(units)
	err = forEachCostsDetailMultiBillingConfigs([]uint{tenantID}, billingConfigIDs, year, month, nil, clusterID, func(cost entity.CostsDetailsQuery) error {
		if !acc.add(cost) {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return acc.result(), nil
}

func (c *CostExplorerModel) getBillsDetailsForRange(tenantID uint, billingConfigID uint, start time.Time, end time.Time) (*forms.CostExplorerDetailsResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	acc := newBillsDetailsAccumulator(units)
	err = forEachCostsDetailForDateRange(tenantID, billingConfigID, start, end, func(cost entity.CostsDetailsQuery) error {
		if !acc.add(cost) {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return acc.result(), nil
}

func (c *CostExplorerModel) GetBillsDetailsForDate(tenantID uint, billingConfigID uint, date time.Time) (*forms.S3BillsDetailResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	acc := newS3BillsDetailsAccumulator(units)
	err = forEachCostsDetailForDateRange(tenantID, billingConfigID, date, date.AddDate(0, 0, 1), func(cost entity.CostsDetailsQuery) error {
		if !acc.add(cost) {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	result := acc.result()
	return result, nil
}

func costExplorerDecimalAbs(value decimal.Decimal) decimal.Decimal {
	if value.IsNegative() {
		return value.Neg()
	}
	return value
}

func (c *CostExplorerModel) GetBillsDetailsForDateStarterBatched(tenantID uint, billingConfigID uint, date time.Time) ([]byte, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	writer := newS3JSONLRecordWriter(units, &buf)
	const batchSize = 500

	clusterIDs, err := getDistinctClusterIDs(tenantID, billingConfigID, date)
	if err != nil {
		return nil, err
	}

	for i := 0; i < len(clusterIDs); i += batchSize {
		end := i + batchSize
		if end > len(clusterIDs) {
			end = len(clusterIDs)
		}
		batch := clusterIDs[i:end]

		err := forEachCostsDetailForDateRangeByClusterBatch(tenantID, billingConfigID, date, date.AddDate(0, 0, 1), batch, func(cost entity.CostsDetailsQuery) error {
			writer.add(cost)
			return nil
		})
		if err != nil {
			log.Error("failed to stream costs for cluster batch",
				zap.Uint("tenantID", tenantID),
				zap.Int("batchStart", i),
				zap.Int("batchEnd", end),
				zap.Error(err))
			continue
		}
	}

	err = forEachCostsDetailForDateRangeByInstanceCapacity(tenantID, billingConfigID, date, date.AddDate(0, 0, 1), func(cost entity.CostsDetailsQuery) error {
		writer.add(cost)
		return nil
	})
	if err != nil {
		log.Error("failed to stream costs for instance-capacity",
			zap.Uint("tenantID", tenantID),
			zap.Error(err))
	}

	writer.flush()

	if buf.Len() == 0 {
		return nil, nil
	}
	return buf.Bytes(), nil
}

func flushRecordsToJSONL(buf *bytes.Buffer, records []forms.S3BillsDetailRecord) {
	for i, record := range records {
		record.Discounts = record.RunningTotal.Sub(record.Credits).Sub(record.TotalCost)
		records[i] = record
	}
	for _, record := range records {
		if buf.Len() > 0 {
			buf.WriteByte('\n')
		}
		line, err := json.Marshal(record)
		if err != nil {
			continue
		}
		buf.Write(line)
	}
}

func getDistinctClusterIDs(tenantID uint, billingConfigID uint, date time.Time) ([]string, error) {
	filter := []string{
		"tenant_id = @tenant_id",
		"status = @status",
		"cost_time_stamp > @start_date",
		"cost_time_stamp < @end_date",
		"internal = @internal",
		"unit REGEXP 'serverless-.*:starter$'",
		"cluster_id != ''",
	}
	params := map[string]interface{}{
		"tenant_id":  tenantID,
		"status":     entity.UserUnitCostStatus_DONE,
		"start_date": date.Unix() - 1,
		"end_date":   date.AddDate(0, 0, 1).Unix(),
		"internal":   false,
	}
	if billingConfigID != 0 {
		filter = append(filter, "binded_billing_config_id = @binded_billing_config_id")
		params["binded_billing_config_id"] = billingConfigID
	}

	query := fmt.Sprintf(`SELECT DISTINCT cluster_id FROM user_unit_costs %s WHERE %s`, forceClusterIndex, strings.Join(filter, " and "))

	queryTimeout := config.Get().GetDuration(config.PerDBQueryTimeoutSeconds) * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), queryTimeout)
	defer cancel()

	var clusterIDs []string
	err := db.GetReadOnlyDB().WithContext(ctx).Raw(query, params).Find(&clusterIDs).Error
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return clusterIDs, nil
}

func (c *CostExplorerModel) GetExportBillsDetailsWithCache(tenantID uint, billingConfigID uint, year int, month int) ([]forms.ExportUsageDetails, error) {
	monthStart := timeutils.GetMonthDateByYearMonth(year, month)
	if hasSnapshotOverlay(tenantID, billingConfigID, monthStart, monthStart.AddDate(0, 1, 0)) {
		return c.GetExportBillsDetailsByTenant([]uint{tenantID}, []uint{billingConfigID}, year, month, nil)
	}
	cache := cachehandler.Get()
	var result []forms.ExportUsageDetails
	found := cache.Get("GetBillsDetailsWithCache", &result, tenantID, billingConfigID, year, month)
	if found {
		return result, nil
	}
	result, err := c.GetExportBillsDetailsByTenant([]uint{tenantID}, []uint{billingConfigID}, year, month, nil)
	if err == nil {
		cache.Set("GetBillsDetailsWithCache", result, tenantID, billingConfigID, year, month)
	}
	return result, err
}

func (c *CostExplorerModel) GetExportBillsDetailsByTenant(tenantIDs []uint, billingConfigIDs []uint, year int, month int, invoiceIDs []int) ([]forms.ExportUsageDetails, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	acc := newBillsExportDetailsAccumulator(units, newDailyExportUsageContext())
	if len(invoiceIDs) != 0 && len(tenantIDs) == 1 && len(billingConfigIDs) == 1 {
		overlayErr := forEachCostsDetailByInvoiceIDs(tenantIDs, billingConfigIDs, year, month, invoiceIDs, func(cost entity.CostsDetailsQuery) error {
			if !acc.add(cost) {
				return errCostsDetailServiceUnresolved
			}
			return nil
		})
		if errors.Is(overlayErr, errCostsDetailServiceUnresolved) {
			return nil, nil
		}
		if overlayErr != nil {
			return nil, overlayErr
		}
		return acc.result(), nil
	}
	err = forEachCostsDetailMultiBillingConfigs(tenantIDs, billingConfigIDs, year, month, invoiceIDs, 0, func(cost entity.CostsDetailsQuery) error {
		if !acc.add(cost) {
			return errCostsDetailServiceUnresolved
		}
		return nil
	})
	if errors.Is(err, errCostsDetailServiceUnresolved) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return acc.result(), nil
}

func getCostQueryTime(year int, month int) *forms.CostExplorerQuery {
	start := timeutils.GetMonthDateByYearMonth(year, month)
	query := &forms.CostExplorerQuery{
		StartDate: start,
		EndDate:   start.AddDate(0, 1, 0),
	}
	return query
}

// for CostExplorer Usage
func CostsSummaryFromEntities(costs []entity.UnitCostSummary) []forms.UnitCostSummary {
	infos := make([]forms.UnitCostSummary, len(costs))
	for i, cost := range costs {
		infos[i] = forms.UnitCostSummary{
			Unit:           cost.Unit,
			Labels:         cost.Labels,
			CostPrice:      cost.CostPrice,
			RawPrice:       cost.RawPrice,
			ConsumedSource: cost.ConsumedSource,
			InvoiceID:      cost.InvoiceID,
			Cost:           cost.Cost,
			Internal:       cost.Internal,
			TenantID:       cost.TenantID,
		}
	}
	return infos
}

func getSummaryLabelFromConfig() map[string]bool {
	res := map[string]bool{}
	for _, label := range strings.Split(config.Get().GetString(config.CostSummaryGroupLabelList), ",") {
		res[label] = true
	}
	return res
}
func (c *CostExplorerModel) GetAllCategoryViewForOps(shows map[string]entity.CategoryItemShow) []*forms.CategoryNode {
	if shows == nil {
		shows = GetAllCategoryItemInfoWithCache()

	}
	var records []entity.CategoryItemShow
	for _, record := range shows {
		records = append(records, record)
	}

	return buildTree(records)
}

func buildTree(records []entity.CategoryItemShow) []*forms.CategoryNode {
	rootMap := make(map[string]*forms.CategoryNode)

	for _, record := range records {
		if record.ShowKindName == "" {
			continue
		}

		rootOrder, ok := record.OrderMap[record.ShowKindName]
		if !ok {
			continue
		}
		rootNode, exists := rootMap[record.ShowKindName]
		if !exists {
			rootNode = &forms.CategoryNode{
				Key:   categoryNodeKey("", record.ShowKindName),
				Name:  record.ShowKindName,
				Order: rootOrder,
			}
			rootMap[record.ShowKindName] = rootNode
		}
		addNodeCategory(rootNode, record.Category)

		if record.ShowCategoryName == "" {
			if len(record.Labels) == 0 {
				categoryOrder, ok := record.OrderMap[record.Category]
				if ok {
					addLeaf(rootNode, record.Category, categoryOrder, record.Category)
				}
			} else {
				addLabelLeaves(rootNode, record.Labels, record.OrderMap, record.Category)
			}
			continue
		}

		categoryOrder, ok := record.OrderMap[record.ShowCategoryName]
		if !ok {
			continue
		}
		categoryNode := findOrCreateNode(rootNode.Children, record.ShowCategoryName, categoryOrder)
		if categoryNode == nil {
			categoryNode = &forms.CategoryNode{
				Key:       categoryNodeKey(rootNode.Key, record.ShowCategoryName),
				ParentKey: rootNode.Key,
				Name:      record.ShowCategoryName,
				Order:     categoryOrder,
			}
			rootNode.Children = append(rootNode.Children, categoryNode)
		}
		addNodeCategory(categoryNode, record.Category)

		if len(record.Labels) > 0 {
			addLabelLeaves(categoryNode, record.Labels, record.OrderMap, record.Category)
		}
	}

	roots := make([]*forms.CategoryNode, 0, len(rootMap))
	for _, root := range rootMap {
		sortChildren(root)
		roots = append(roots, root)
	}
	sort.Slice(roots, func(i, j int) bool {
		return roots[i].Order < roots[j].Order
	})

	return roots
}

func categoryNodeKey(parentKey, name string) string {
	if parentKey == "" {
		return "kind:" + name
	}
	return parentKey + "/" + name
}

func addNodeCategory(node *forms.CategoryNode, category string) {
	if category == "" {
		return
	}
	for _, existing := range node.Categories {
		if existing == category {
			return
		}
	}
	node.Categories = append(node.Categories, category)
	if len(node.Categories) == 1 {
		node.Category = category
	} else {
		node.Category = ""
	}
}

func addLeaf(parent *forms.CategoryNode, name string, order int32, category string) {
	for _, child := range parent.Children {
		if child.Name == name {
			addNodeCategory(child, category)
			return
		}
	}
	node := &forms.CategoryNode{
		Key:       categoryNodeKey(parent.Key, name),
		ParentKey: parent.Key,
		Name:      name,
		Order:     order,
	}
	addNodeCategory(node, category)
	parent.Children = append(parent.Children, node)
}

func addLabelLeaves(parent *forms.CategoryNode, labels map[string]string, orderMap map[string]int32, category string) {
	valueSet := make(map[string]struct{})
	for _, v := range labels {
		valueSet[v] = struct{}{}
	}

	for value := range valueSet {
		if order, ok := orderMap[value]; ok {
			addLeaf(parent, value, order, category)
		}
	}
}

func findOrCreateNode(nodes []*forms.CategoryNode, name string, order int32) *forms.CategoryNode {
	for _, node := range nodes {
		if node.Name == name {
			return node
		}
	}
	return nil
}

func sortChildren(parent *forms.CategoryNode) {
	sort.Slice(parent.Children, func(i, j int) bool {
		return parent.Children[i].Order < parent.Children[j].Order
	})
	for _, child := range parent.Children {
		sortChildren(child)
	}
}

func GetAllCategoryItemInfoWithCache() map[string]entity.CategoryItemShow {
	cache := cachehandler.Get()
	res := make(map[string]entity.CategoryItemShow, 64)

	found := cache.Get("GetAllCategoryItemInfoWithCache", &res)
	if found {
		return res
	}
	var categoryItemInfos []entity.CategoryItemShow
	err := entity.NewCategoryItemShowQuerySet(db.GetDB()).All(&categoryItemInfos)
	if err != nil {
		log.Error("getAllCategoryItemInfo ", zap.Error(err))
		return nil
	}
	if len(categoryItemInfos) == 0 {
		return nil
	}
	for _, categoryItemInfo := range categoryItemInfos {
		res[categoryItemInfo.Category] = categoryItemInfo
	}
	cache.Set("GetAllCategoryItemInfoWithCache", res)

	return res
}

func getKeeyShowingCategory() []string {
	listValue := config.Get().GetString(config.KeeyShowingCategory)
	if listValue == "" {
		return []string{}
	}
	return strings.Split(config.Get().GetString(config.KeeyShowingCategory), ",")
}

func newCategoryCosts() map[string]forms.Price {
	keeyShowingCategory := getKeeyShowingCategory()
	res := map[string]forms.Price{}
	for _, category := range keeyShowingCategory {
		res[category] = forms.Price{}
	}
	return res
}

type costSummaryAccumulator struct {
	summaryLabels     map[string]bool
	defaultCategory   string
	defaultLabelValue string
	unitsCategory     map[string]string
	categoryLabels    map[string]entity.CategoryItemShow
	res               *forms.Costs
}

func newCostSummaryAccumulator(units []forms.UnitInfo) *costSummaryAccumulator {
	summaryLabels := getSummaryLabelFromConfig()
	res := &forms.Costs{}
	res.CategoryUnitsCosts = newCategoryCosts()
	res.CategoryItemUnitsCosts = map[string]forms.CategoryCosts{}
	res.LabelsUnitsCosts = map[string]forms.LabelCosts{}
	res.CategoryUseItem = true
	res.KindCategoryUnitsCost = make(map[string]forms.CategoryItemCosts)
	for label := range summaryLabels {
		res.LabelsUnitsCosts[label] = forms.LabelCosts{}
	}
	unitsCategory := map[string]string{}
	for _, unit := range units {
		unitsCategory[unit.UnitKey] = unit.Category
	}
	return &costSummaryAccumulator{
		summaryLabels:     summaryLabels,
		defaultCategory:   config.Get().GetString(config.DefaultUnitCategory),
		defaultLabelValue: config.Get().GetString(config.DefaultLabelValueCategory),
		unitsCategory:     unitsCategory,
		categoryLabels:    GetAllCategoryItemInfoWithCache(),
		res:               res,
	}
}

func (a *costSummaryAccumulator) add(cost forms.UnitCostSummary) {
	//******* todo 20221120 delete
	category := a.unitsCategory[cost.Unit]

	if category == "" {
		category = a.defaultCategory
	}
	a.res.CostPrice = a.res.CostPrice.Add(cost.CostPrice)
	a.res.RawPrice = a.res.RawPrice.Add(cost.RawPrice)
	a.res.DiscountPrice = a.res.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
	costCategory := a.res.CategoryUnitsCosts[category]
	costCategory.CostPrice = costCategory.CostPrice.Add(cost.CostPrice)
	costCategory.RawPrice = costCategory.RawPrice.Add(cost.RawPrice)
	costCategory.DiscountPrice = costCategory.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
	a.res.CategoryUnitsCosts[category] = costCategory

	categoryItemInfo, isCategoryItem := a.categoryLabels[category]

	for label := range a.summaryLabels {
		useDefault := false
		labelValue, ok := cost.Labels[label]
		if !ok {
			useDefault = true
			labelValue = a.defaultLabelValue
		}
		if a.summaryLabels[label] {
			costLabel := a.res.LabelsUnitsCosts[label][labelValue]
			if costLabel.CategoryUnitsCosts == nil {
				costLabel.CategoryUnitsCosts = newCategoryCosts()
			}
			costCategory := costLabel.CategoryUnitsCosts[category]
			costCategory.CostPrice = costCategory.CostPrice.Add(cost.CostPrice)
			costCategory.RawPrice = costCategory.RawPrice.Add(cost.RawPrice)
			costCategory.DiscountPrice = costCategory.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
			costLabel.CategoryUnitsCosts[category] = costCategory
			costLabel.CostPrice = costLabel.CostPrice.Add(cost.CostPrice)
			costLabel.RawPrice = costLabel.RawPrice.Add(cost.RawPrice)
			costLabel.DiscountPrice = costLabel.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))

			if isCategoryItem && categoryItemInfo.ShowCategoryName != "" {
				costLabel.Order = categoryItemInfo.OrderMap[categoryItemInfo.ShowCategoryName]
			}
			if servicePlan, is := cost.Labels[constant.ServicePlanLabelKey]; is {
				costLabel.ServicePlan = &servicePlan
				costLabel.Order = showOrderLowest90
			}
			if useDefault {
				costLabel.Order = showOrderLowest
			}
			a.res.LabelsUnitsCosts[label][labelValue] = costLabel

		}

	}

	// deal category item

	if !isCategoryItem {
		costCategoryItem := a.res.CategoryItemUnitsCosts[category]
		costCategoryItem.CostPrice = costCategoryItem.CostPrice.Add(cost.CostPrice)
		costCategoryItem.RawPrice = costCategoryItem.RawPrice.Add(cost.RawPrice)
		costCategoryItem.DiscountPrice = costCategoryItem.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
		a.res.CategoryItemUnitsCosts[category] = costCategoryItem
		//one
		categoryItemCosts := a.res.KindCategoryUnitsCost[category]
		categoryItemCosts.CostPrice = categoryItemCosts.CostPrice.Add(cost.CostPrice)
		categoryItemCosts.RawPrice = categoryItemCosts.RawPrice.Add(cost.RawPrice)
		categoryItemCosts.DiscountPrice = categoryItemCosts.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
		a.res.KindCategoryUnitsCost[category] = categoryItemCosts
		//ss

	} else {
		if categoryItemInfo.UseCateGory {
			//one
			categoryItemCosts := a.res.KindCategoryUnitsCost[categoryItemInfo.ShowKindName]
			categoryItemCosts.Order = categoryItemInfo.OrderMap[categoryItemInfo.ShowKindName]
			categoryItemCosts.CostPrice = categoryItemCosts.CostPrice.Add(cost.CostPrice)
			categoryItemCosts.RawPrice = categoryItemCosts.RawPrice.Add(cost.RawPrice)
			categoryItemCosts.DiscountPrice = categoryItemCosts.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))

			if len(categoryItemInfo.ShowCategoryName) > 0 {
				//two floor deal
				showName := categoryItemInfo.ShowCategoryName
				isBetaMeasures := billing.IsBetaMeasures(cost.Labels, 0, nil, false)
				if isBetaMeasures && len(categoryItemInfo.LabelKey) == 0 {
					showName = toBetaName(showName)
				}
				costCategoryItem2 := a.res.CategoryItemUnitsCosts[showName]
				if costCategoryItem2.CategoryUnitsCosts == nil {
					costCategoryItem2.CategoryUnitsCosts = newCategoryCosts()
				}
				kindCostCategoryItem2 := categoryItemCosts.CategoryItemUnitsCosts[showName]
				if kindCostCategoryItem2.CategoryUnitsCosts == nil {
					kindCostCategoryItem2.CategoryUnitsCosts = newCategoryCosts()
				}

				if len(categoryItemInfo.LabelKey) > 0 || isBetaMeasures {
					//three floor deal
					var labelKey string
					order := int32(showOrderLowest)
					hasBetaShowCategory := cost.Labels[constant.BetaCategoryLabelKey] != ""
					if len(categoryItemInfo.LabelKey) > 0 {
						labelKey = categoryItemInfo.Labels[categoryItemInfo.LabelKey]
						order = categoryItemInfo.OrderMap[labelKey]
						if isBetaMeasures {
							labelKey = toBetaName(labelKey)
							order = int32(showOrderLowest)
						}
					} else if hasBetaShowCategory {
						labelKey = cost.Labels[constant.BetaCategoryLabelKey]
					} else {
						labelKey = BetaNoCatName
					}
					costCategoryItem3 := costCategoryItem2.CategoryUnitsCosts[labelKey]
					costCategoryItem3.Order = order
					costCategoryItem3.CostPrice = costCategoryItem3.CostPrice.Add(cost.CostPrice)
					costCategoryItem3.RawPrice = costCategoryItem3.RawPrice.Add(cost.RawPrice)
					costCategoryItem3.DiscountPrice = costCategoryItem3.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
					costCategoryItem2.CategoryUnitsCosts[labelKey] = costCategoryItem3

					kindCostCategory3 := kindCostCategoryItem2.CategoryUnitsCosts[labelKey]
					kindCostCategory3.Order = order
					kindCostCategory3.CostPrice = kindCostCategory3.CostPrice.Add(cost.CostPrice)
					kindCostCategory3.RawPrice = kindCostCategory3.RawPrice.Add(cost.RawPrice)
					kindCostCategory3.DiscountPrice = kindCostCategory3.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
					kindCostCategoryItem2.CategoryUnitsCosts[labelKey] = kindCostCategory3

				}

				costCategoryItem2.Order = categoryItemInfo.OrderMap[showName]
				kindCostCategoryItem2.Order = categoryItemInfo.OrderMap[showName]
				if isBetaMeasures {
					costCategoryItem2.Order = showOrderLowest
					kindCostCategoryItem2.Order = showOrderLowest
				}
				costCategoryItem2.CostPrice = costCategoryItem2.CostPrice.Add(cost.CostPrice)
				costCategoryItem2.RawPrice = costCategoryItem2.RawPrice.Add(cost.RawPrice)
				costCategoryItem2.DiscountPrice = costCategoryItem2.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
				a.res.CategoryItemUnitsCosts[showName] = costCategoryItem2

				kindCostCategoryItem2.CostPrice = kindCostCategoryItem2.CostPrice.Add(cost.CostPrice)
				kindCostCategoryItem2.RawPrice = kindCostCategoryItem2.RawPrice.Add(cost.RawPrice)
				kindCostCategoryItem2.DiscountPrice = kindCostCategoryItem2.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))

				if categoryItemCosts.CategoryItemUnitsCosts == nil {
					categoryItemCosts.CategoryItemUnitsCosts = make(map[string]forms.CategoryCosts)
				}
				categoryItemCosts.CategoryItemUnitsCosts[showName] = kindCostCategoryItem2
			} else {
				costCategoryItemTemp := a.res.CategoryItemUnitsCosts[category]
				costCategoryItemTemp.CostPrice = costCategoryItemTemp.CostPrice.Add(cost.CostPrice)
				costCategoryItemTemp.RawPrice = costCategoryItemTemp.RawPrice.Add(cost.RawPrice)
				costCategoryItemTemp.DiscountPrice = costCategoryItemTemp.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
				a.res.CategoryItemUnitsCosts[category] = costCategoryItemTemp
			}
			a.res.KindCategoryUnitsCost[categoryItemInfo.ShowKindName] = categoryItemCosts

		} else {

			costCategoryItem := a.res.CategoryItemUnitsCosts[categoryItemInfo.ShowCategoryName]
			if nil == costCategoryItem.CategoryUnitsCosts {
				costCategoryItem.CategoryUnitsCosts = newCategoryCosts()
			}

			getLabelsKey, ok := cost.Labels[categoryItemInfo.LabelKey]
			if !ok {
				getLabelsKey = "default"
			}
			showName, ok := categoryItemInfo.Labels[getLabelsKey]
			if !ok {
				a.res.CategoryUseItem = false
				log.Info("categoryUseItem false", zap.String("unit", cost.Unit))
			}
			// three
			costCategory := costCategoryItem.CategoryUnitsCosts[showName]
			costCategory.Order = categoryItemInfo.OrderMap[showName]
			costCategory.CostPrice = costCategory.CostPrice.Add(cost.CostPrice)
			costCategory.RawPrice = costCategory.RawPrice.Add(cost.RawPrice)
			costCategory.DiscountPrice = costCategory.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
			costCategoryItem.CategoryUnitsCosts[showName] = costCategory
			//two
			costCategoryItem.Order = categoryItemInfo.OrderMap[categoryItemInfo.ShowCategoryName]
			costCategoryItem.CostPrice = costCategoryItem.CostPrice.Add(cost.CostPrice)
			costCategoryItem.RawPrice = costCategoryItem.RawPrice.Add(cost.RawPrice)
			costCategoryItem.DiscountPrice = costCategoryItem.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))
			a.res.CategoryItemUnitsCosts[categoryItemInfo.ShowCategoryName] = costCategoryItem
			//one
			categoryItemCosts := a.res.KindCategoryUnitsCost[categoryItemInfo.ShowKindName]
			categoryItemCosts.Order = categoryItemInfo.OrderMap[categoryItemInfo.ShowKindName]
			categoryItemCosts.CostPrice = categoryItemCosts.CostPrice.Add(cost.CostPrice)
			categoryItemCosts.RawPrice = categoryItemCosts.RawPrice.Add(cost.RawPrice)
			categoryItemCosts.DiscountPrice = categoryItemCosts.DiscountPrice.Add(cost.RawPrice.Sub(cost.CostPrice))

			if categoryItemCosts.CategoryItemUnitsCosts == nil {
				categoryItemCosts.CategoryItemUnitsCosts = make(map[string]forms.CategoryCosts)
			}
			kindCategoryItem := costCategoryItem
			if costCategoryItem.CategoryUnitsCosts != nil {
				kindCategoryItem.CategoryUnitsCosts = make(map[string]forms.Price, len(costCategoryItem.CategoryUnitsCosts))
				for k, v := range costCategoryItem.CategoryUnitsCosts {
					kindCategoryItem.CategoryUnitsCosts[k] = v
				}
			}
			categoryItemCosts.CategoryItemUnitsCosts[categoryItemInfo.ShowCategoryName] = kindCategoryItem
			a.res.KindCategoryUnitsCost[categoryItemInfo.ShowKindName] = categoryItemCosts

		}
	}

}

func (a *costSummaryAccumulator) result() *forms.Costs {
	removeNoSubChildBetaCost(a.res)
	return a.res
}

func CostSummaryFromUnitCosts(costs []forms.UnitCostSummary, units []forms.UnitInfo) *forms.Costs {
	acc := newCostSummaryAccumulator(units)
	for _, cost := range costs {
		acc.add(cost)
	}
	return acc.result()
}

func removeNoSubChildBetaCost(r *forms.Costs) {
	newCategoryItemUnitsCosts := make(map[string]forms.CategoryCosts)
	for showName, CategoryItemUnitsCosts := range r.CategoryItemUnitsCosts {
		_, hasBetaChild := CategoryItemUnitsCosts.CategoryUnitsCosts[BetaNoCatName]
		if isBetaName(showName) && hasBetaChild {
			if len(CategoryItemUnitsCosts.CategoryUnitsCosts) == 1 {
				CategoryItemUnitsCosts.CategoryUnitsCosts = nil
			} else {
				log.Error("Beta Item contains unknown category, using `Others` to fill", zap.String("showName", showName))
			}
		}
		newCategoryItemUnitsCosts[showName] = CategoryItemUnitsCosts
	}
	r.CategoryItemUnitsCosts = newCategoryItemUnitsCosts
	newKindCategoryUnitsCost := make(map[string]forms.CategoryItemCosts)
	for kindName, categoryItemCosts := range r.KindCategoryUnitsCost {
		newCategoryItemUnitsCosts := make(map[string]forms.CategoryCosts)
		for showName, CategoryItemUnitsCosts := range categoryItemCosts.CategoryItemUnitsCosts {
			_, hasBetaChild := CategoryItemUnitsCosts.CategoryUnitsCosts[BetaNoCatName]
			if isBetaName(showName) && hasBetaChild {
				if len(CategoryItemUnitsCosts.CategoryUnitsCosts) == 1 {
					CategoryItemUnitsCosts.CategoryUnitsCosts = nil
				} else {
					log.Error("Beta Item contains unknown category, using `Others` to fill", zap.String("showName", showName))
				}
			}
			newCategoryItemUnitsCosts[showName] = CategoryItemUnitsCosts
		}
		categoryItemCosts.CategoryItemUnitsCosts = newCategoryItemUnitsCosts
		newKindCategoryUnitsCost[kindName] = categoryItemCosts
	}
	r.KindCategoryUnitsCost = newKindCategoryUnitsCost
}

func (c *CostExplorerModel) GetDailyCostsByDateRange(tenantID uint, billingConfigID uint, startDate, endDate time.Time) ([]entity.CostsDetailsQuery, error) {
	var allDetailsCosts []entity.CostsDetailsQuery
	err := c.ForEachDailyCostByDateRange(tenantID, billingConfigID, startDate, endDate, func(cost entity.CostsDetailsQuery) error {
		allDetailsCosts = append(allDetailsCosts, cost)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return allDetailsCosts, nil
}

func (c *CostExplorerModel) GetDailyCostsByDateRangeExcludingUnits(tenantID uint, billingConfigID uint, startDate, endDate time.Time, excludeUnits []string) ([]entity.CostsDetailsQuery, error) {
	var allDetailsCosts []entity.CostsDetailsQuery
	err := c.ForEachDailyCostByDateRangeExcludingUnits(tenantID, billingConfigID, startDate, endDate, excludeUnits, func(cost entity.CostsDetailsQuery) error {
		allDetailsCosts = append(allDetailsCosts, cost)
		return nil
	})
	if err != nil {
		return nil, err
	}
	return allDetailsCosts, nil
}

func BillsDetailsFromUnitCosts(allDetailslCosts []entity.CostsDetailsQuery, units []forms.UnitInfo) (*forms.CostExplorerDetailsResult, error) {
	acc := newBillsDetailsAccumulator(units)
	for _, cost := range allDetailslCosts {
		if !acc.add(cost) {
			return nil, nil
		}
	}
	return acc.result(), nil
}

func S3BillsDetailsFromUnitCosts(allDetailsCosts []entity.CostsDetailsQuery, units []forms.UnitInfo) (*forms.S3BillsDetailResult, error) {
	acc := newS3BillsDetailsAccumulator(units)
	for _, cost := range allDetailsCosts {
		if !acc.add(cost) {
			return nil, nil
		}
	}
	return acc.result(), nil
}

func PartnerBillsDetailsFromUnitCosts(allDetailsCosts []entity.CostsDetailsQuery, units []forms.UnitInfo, tenantIDAsCustomer bool) ([]forms.PartnerBillsDetail, error) {
	acc := newPartnerBillsDetailsAccumulator(units, tenantIDAsCustomer)
	for _, cost := range allDetailsCosts {
		ok, err := acc.add(cost)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, nil
		}
	}
	return acc.result()
}

func BillsExportDetailsFromUnitCosts(allDetailslCosts []entity.CostsDetailsQuery, units []forms.UnitInfo) ([]forms.ExportUsageDetails, error) {
	acc := newBillsExportDetailsAccumulator(units, newDailyExportUsageContext())
	for _, cost := range allDetailslCosts {
		if !acc.add(cost) {
			return nil, nil
		}
	}
	return acc.result(), nil
}

func GetServiceName(detailslCosts entity.CostsDetailsQuery, category string) (string, bool) {
	return getServiceNameWithCategoryLabels(detailslCosts, category, GetAllCategoryItemInfoWithCache())
}

func getServiceNameWithCategoryLabels(detailslCosts entity.CostsDetailsQuery, category string, categoryLabels map[string]entity.CategoryItemShow) (string, bool) {
	if categoryLabels == nil {
		categoryLabels = GetAllCategoryItemInfoWithCache()
	}
	categoryItemInfo, ok := categoryLabels[category]
	if ok {
		showNamePre := categoryItemInfo.ShowKindName + " - "
		if categoryItemInfo.UseCateGory {
			showName := categoryItemInfo.ShowCategoryName
			if len(showName) == 0 {
				return categoryItemInfo.ShowKindName, true
			}
			isBetaMeasures := billing.IsBetaMeasures(detailslCosts.Labels, 0, nil, false)
			if isBetaMeasures && len(categoryItemInfo.LabelKey) == 0 {
				showName = toBetaName(showName)
			}
			hasBetaShowCategory := detailslCosts.Labels[constant.BetaCategoryLabelKey] != ""
			if len(categoryItemInfo.LabelKey) != 0 {
				labelName := categoryItemInfo.Labels[categoryItemInfo.LabelKey]
				if isBetaMeasures {
					labelName = toBetaName(labelName)
				}
				return showNamePre + showName + " - " + labelName, true
			} else if isBetaMeasures && hasBetaShowCategory {
				return showNamePre + showName + " - " + detailslCosts.Labels[constant.BetaCategoryLabelKey], true
			} else {
				return showNamePre + showName, true
			}
		} else {
			getLabelsKey, ok := detailslCosts.Labels[categoryItemInfo.LabelKey]
			if !ok {
				getLabelsKey = "default"
			}
			showName, ok := categoryItemInfo.Labels[getLabelsKey]
			log.Info("getServiceName", zap.String("getLabelsKey ", getLabelsKey), zap.String("showName", showName),
				zap.String("category", category))

			return showNamePre + categoryItemInfo.ShowServicePrefix + showName, ok
		}

	} else {
		return strings.TrimSuffix(category, "Cost"), true
	}

}

func getExportUsage(cost entity.CostsDetailsQuery, categoryItemInfo entity.CategoryItemShow, price decimal.Decimal, ctx exportUsageContext) (decimal.Decimal, string) {
	measureUnit := categoryItemInfo.MeasureUnit
	if measureUnit == "" {
		return decimal.Zero, ""
	}
	strategy, ok := exportUsageStrategies[measureUnit]
	if !ok {
		return decimal.Zero, ""
	}
	if !price.IsPositive() || !cost.RawPrice.IsPositive() {
		return decimal.Zero, ""
	}
	usage, ok := strategy.convert(cost.RawPrice.Div(price), ctx)
	if !ok {
		return decimal.Zero, ""
	}
	return usage, strategy.unit
}

func mergeExportUsage(detail *forms.ExportUsageDetails, usage decimal.Decimal, unit string) {
	if detail.Unit == "" && unit != "" {
		detail.Unit = unit
	}
	detail.Usage = detail.Usage.Add(usage)
}
