package costexplorer

import (
	"sort"
	"strconv"
	"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/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/cachehandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
)

func (c *CostExplorerModel) CalculateMspCustomerOpenAPIMonthlyCostWithCache(tenantID uint, billingConfigID uint, billedMonth time.Time) (*forms.BillsExplorer, error) {

	ret := &forms.BillsExplorer{}
	ok := cachehandler.Get().Get("CalculateMspCustomerOpenAPIMonthlyCostWithCache", ret, tenantID, billedMonth.String())
	if ok {
		return ret, nil
	}
	ret, err := c.CalculateMspCustomerOpenAPIMonthlyCost(tenantID, billingConfigID, billedMonth)
	if err != nil {
		log.Info("CalculateMspCustomerOpenAPIMonthlyCostWithCache error ", zap.Uint("mspTenantId", tenantID), zap.String("billedMonth", billedMonth.String()))
		return nil, err
	}
	cachehandler.Get().Set("CalculateMspCustomerOpenAPIMonthlyCostWithCache", ret, tenantID, billedMonth.String())
	return ret, err
}

func (c *CostExplorerModel) CalculateMspCustomerOpenAPIMonthlyCost(tenantID uint, billingConfigID uint, billedMonth time.Time) (*forms.BillsExplorer, error) {
	queryParam := getCostQueryV2Time(billedMonth.Year(), int(billedMonth.Month()))
	queryParam.IgnoreInvoiceId = true
	// June 2026 bills freeze: re-introduce the per-day-split + per-row-truncate
	// streaming path for 2026-06, and only when June2026DailySplitEnabled is
	// set. All other months — and June 2026 with the flag off — use the
	// single-query scan. See GetMonthlyCost in costexplorer.go for the same
	// logic on the non-MSP path.
	if config.Get().GetBool(config.June2026DailySplitEnabled) &&
		billedMonth.Year() == 2026 && billedMonth.Month() == time.June {
		queryParam.June2026DailySplit = true
	}
	//MspCustomer need billingConfigID
	if billingConfigID == 0 {
		log.Error("msp customer query but billing config id is 0")
		return nil, billing_errors.SystemError
	}
	billsExplorer, err := c.calculateBillsExplorerStreaming([]uint{tenantID}, []uint{billingConfigID}, queryParam, true)
	if err != nil {
		return nil, err
	}
	billsExplorer.BillsOverView.BilledMonth = billedMonth.Format("2006-01")
	return billsExplorer, nil

}

func (c *CostExplorerModel) CalculateMspOpenAPIMonthlyCostWithCache(tenantID uint, billedMonth time.Time) (*forms.MspBills, error) {

	ret := &forms.MspBills{}
	ok := cachehandler.Get().Get("CalculateMspOpenAPIMonthlyCostWithCache", ret, tenantID, billedMonth.String())
	if ok {
		return ret, nil
	}
	ret, err := c.CalculateMspOpenAPIMonthlyCost(tenantID, billedMonth)
	if err != nil {
		log.Info("CalculateMspOpenAPIMonthlyCost error ", zap.Uint("mspTenantId", tenantID), zap.String("billedMonth", billedMonth.String()))
		return nil, err
	}
	cachehandler.Get().Set("CalculateMspOpenAPIMonthlyCost", ret, tenantID, billedMonth.String())
	return ret, err
}

func (c *CostExplorerModel) CalculateMspOpenAPIMonthlyCost(tenantID uint, billedMonth time.Time) (*forms.MspBills, error) {
	queryParam := getCostQueryV2Time(billedMonth.Year(), int(billedMonth.Month()))
	// June 2026 bills freeze — see CalculateMspCustomerOpenAPIMonthlyCost.
	if config.Get().GetBool(config.June2026DailySplitEnabled) &&
		billedMonth.Year() == 2026 && billedMonth.Month() == time.June {
		queryParam.June2026DailySplit = true
	}
	//don't need billingConfigID
	factory, err := newBillsExplorerAccumulatorFactory()
	if err != nil {
		return nil, err
	}
	billsAccumulator := factory.newAccumulator(false)
	customerAccumulators := map[uint]*billsExplorerAccumulator{}
	err = c.queryCostStreaming([]uint{tenantID}, nil, queryParam, func(cost entity.UnitCostSummary) error {
		formCost := unitCostSummaryFromEntity(cost)
		billsAccumulator.add(formCost)
		if cost.Internal {
			return nil
		}
		customerIDStr, ok := cost.Labels[constant.MSPCustomerIDLabel]
		if !ok {
			return nil
		}
		customerID, err := strconv.ParseUint(customerIDStr, 10, 0)
		if err != nil {
			return nil
		}
		customerAccumulator := customerAccumulators[uint(customerID)]
		if customerAccumulator == nil {
			customerAccumulator = factory.newAccumulator(true)
			customerAccumulators[uint(customerID)] = customerAccumulator
		}
		customerAccumulator.add(formCost)
		return nil
	})
	if err != nil {
		return nil, err
	}
	billsExplorer := billsAccumulator.result()
	mspBills := &forms.MspBills{
		BillsOverView:     billsExplorer.BillsOverView,
		BillsServiceCosts: billsExplorer.BillsServiceCosts,
	}
	mspBills.BillsOverView.BilledMonth = billedMonth.Format("2006-01")

	customerIDs := make([]uint, 0, len(customerAccumulators))
	for customerID := range customerAccumulators {
		customerIDs = append(customerIDs, customerID)
	}
	sort.Slice(customerIDs, func(i, j int) bool {
		return customerIDs[i] < customerIDs[j]
	})
	for _, customerID := range customerIDs {
		customerBillsExplorer := customerAccumulators[customerID].result()
		customerBillsExplorer.BillsOverView.BilledMonth = billedMonth.Format("2006-01")
		mspBills.SummaryByCustomer = append(mspBills.SummaryByCustomer, forms.SummaryByCustomer{
			CustomerID:       customerID,
			MspCustomerBills: *customerBillsExplorer,
		})

	}

	return mspBills, err
}

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

func (c *CostExplorerModel) CalculateCostToBillsExplorer(allCosts []entity.UnitCostSummary, isProjectProcessing bool) (*forms.BillsExplorer, error) {
	factory, err := newBillsExplorerAccumulatorFactory()
	if err != nil {
		return nil, err
	}
	accumulator := factory.newAccumulator(isProjectProcessing)
	for _, cost := range allCosts {
		accumulator.add(unitCostSummaryFromEntity(cost))
	}
	return accumulator.result(), nil
}

func (c *CostExplorerModel) calculateBillsExplorerStreaming(tenantIDs []uint, billingConfigIDs []uint, queryParam *forms.CostExplorerQueryV2, isProjectProcessing bool) (*forms.BillsExplorer, error) {
	factory, err := newBillsExplorerAccumulatorFactory()
	if err != nil {
		return nil, err
	}
	accumulator := factory.newAccumulator(isProjectProcessing)
	err = c.queryCostStreaming(tenantIDs, billingConfigIDs, queryParam, func(cost entity.UnitCostSummary) error {
		accumulator.add(unitCostSummaryFromEntity(cost))
		return nil
	})
	if err != nil {
		return nil, err
	}
	return accumulator.result(), nil
}

type billsExplorerAccumulatorFactory struct {
	unitsCategory  map[string]string
	categoryLabels map[string]entity.CategoryItemShow
}

func newBillsExplorerAccumulatorFactory() (*billsExplorerAccumulatorFactory, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	unitsCategory := map[string]string{}
	for _, unit := range units {
		unitsCategory[unit.UnitKey] = unit.Category
	}
	return &billsExplorerAccumulatorFactory{
		unitsCategory:  unitsCategory,
		categoryLabels: GetAllCategoryItemInfoWithCache(),
	}, nil
}

func (f *billsExplorerAccumulatorFactory) newAccumulator(isProjectProcessing bool) *billsExplorerAccumulator {
	return &billsExplorerAccumulator{
		isProjectProcessing: isProjectProcessing,
		unitsCategory:       f.unitsCategory,
		categoryLabels:      f.categoryLabels,
		precision:           config.Get().GetInt32(config.BillingCostsPrecisionDigits),
		layer1Map:           map[string]int{},
		layer2Map:           map[string]map[string]int{},
		layer3Map:           map[string]map[string]map[string]int{},
		projectMap:          map[string]int{},
		otherMap:            map[string]int{},
	}
}

type billsExplorerAccumulator struct {
	isProjectProcessing bool
	unitsCategory       map[string]string
	categoryLabels      map[string]entity.CategoryItemShow
	precision           int32
	bills               forms.BillsExplorer
	layer1Map           map[string]int
	layer2Map           map[string]map[string]int
	layer3Map           map[string]map[string]map[string]int
	projectMap          map[string]int
	otherMap            map[string]int
}

func (a *billsExplorerAccumulator) add(cost forms.UnitCostSummary) {
	if cost.Internal {
		return
	}
	rawPrice := cost.RawPrice.Truncate(a.precision)
	costPrice := cost.CostPrice.Truncate(a.precision)

	discountPrice := rawPrice.Sub(costPrice)
	var credits = decimal.Zero
	var totalCost = decimal.Zero
	if cost.ConsumedSource == entity.ConsumedSource_Credits {
		credits = costPrice
	} else {
		totalCost = costPrice
	}
	// overView
	updateCosts(&a.bills.BillsOverView.BillsCosts, rawPrice, discountPrice, credits, totalCost)

	categoryItemShow := a.categoryLabels[a.unitsCategory[cost.Unit]]

	// project
	if a.isProjectProcessing {
		label := "project"
		labelValue, isProject := cost.Labels[label]

		if isProject {
			indexProject, ok := a.projectMap[labelValue]
			if !ok {
				a.bills.SummaryByProject.Projects = append(a.bills.SummaryByProject.Projects, forms.BillsProjects{
					ProjectName: labelValue,
					BillsCosts:  forms.BillsCosts{},
				})
				indexProject = len(a.bills.SummaryByProject.Projects) - 1
				a.projectMap[labelValue] = indexProject
			}
			updateCosts(&a.bills.SummaryByProject.Projects[indexProject].BillsCosts, rawPrice, discountPrice, credits, totalCost)
		} else {
			labelValue = categoryItemShow.ShowKindName
			indexOther, ok := a.otherMap[labelValue]
			if !ok {
				a.bills.SummaryByProject.OtherCharges = append(a.bills.SummaryByProject.OtherCharges, forms.BillsOtherName{
					ChargeName: labelValue,
					BillsCosts: forms.BillsCosts{},
				})
				indexOther = len(a.bills.SummaryByProject.OtherCharges) - 1
				a.otherMap[labelValue] = indexOther
			}
			updateCosts(&a.bills.SummaryByProject.OtherCharges[indexOther].BillsCosts, rawPrice, discountPrice, credits, totalCost)

		}
	}

	// service
	layer1ServiceName, layer2ServiceName, layer3ServiceName := get3LayerServiceNameFromeCost(categoryItemShow, cost)

	index1, ok := a.layer1Map[layer1ServiceName]
	if !ok {
		a.bills.BillsServiceCosts = append(a.bills.BillsServiceCosts, forms.BillsServiceCosts{
			ServiceName: layer1ServiceName,
			BillsCosts:  forms.BillsCosts{},
		})
		index1 = len(a.bills.BillsServiceCosts) - 1
		a.layer1Map[layer1ServiceName] = index1

	}
	updateCosts(&a.bills.BillsServiceCosts[index1].BillsCosts, rawPrice, discountPrice, credits, totalCost)
	if layer2ServiceName == "" {
		return
	}
	if a.layer2Map[layer1ServiceName] == nil {
		a.layer2Map[layer1ServiceName] = make(map[string]int)
	}
	index2, ok := a.layer2Map[layer1ServiceName][layer2ServiceName]
	if !ok {
		billsServiceCosts := a.bills.BillsServiceCosts[index1]
		billsServiceCosts.ServiceCosts = append(billsServiceCosts.ServiceCosts, forms.BillsServiceCosts{
			ServiceName: layer2ServiceName,
			BillsCosts:  forms.BillsCosts{},
		})
		index2 = len(billsServiceCosts.ServiceCosts) - 1
		a.layer2Map[layer1ServiceName][layer2ServiceName] = index2
		a.bills.BillsServiceCosts[index1] = billsServiceCosts

	}

	updateCosts(&a.bills.BillsServiceCosts[index1].ServiceCosts[index2].BillsCosts, rawPrice, discountPrice, credits, totalCost)

	if layer3ServiceName == "" {
		return
	}

	if a.layer3Map[layer1ServiceName] == nil {
		a.layer3Map[layer1ServiceName] = make(map[string]map[string]int)
	}
	if a.layer3Map[layer1ServiceName][layer2ServiceName] == nil {
		a.layer3Map[layer1ServiceName][layer2ServiceName] = make(map[string]int)
	}
	index3, ok := a.layer3Map[layer1ServiceName][layer2ServiceName][layer3ServiceName]
	if !ok {
		billsServiceCosts3 := a.bills.BillsServiceCosts[index1].ServiceCosts[index2]
		billsServiceCosts3.ServiceCosts = append(billsServiceCosts3.ServiceCosts, forms.BillsServiceCosts{
			ServiceName: layer3ServiceName,
			BillsCosts:  forms.BillsCosts{},
		})
		index3 = len(billsServiceCosts3.ServiceCosts) - 1
		a.layer3Map[layer1ServiceName][layer2ServiceName][layer3ServiceName] = index3
		a.bills.BillsServiceCosts[index1].ServiceCosts[index2] = billsServiceCosts3
	}
	updateCosts(&a.bills.BillsServiceCosts[index1].ServiceCosts[index2].ServiceCosts[index3].BillsCosts, rawPrice, discountPrice, credits, totalCost)
}

func (a *billsExplorerAccumulator) result() *forms.BillsExplorer {
	orderBillsServiceCosts(a.bills.BillsServiceCosts, a.categoryLabels)
	return &a.bills
}

func get3LayerServiceNameFromeCost(categoryItemInfo entity.CategoryItemShow, cost forms.UnitCostSummary) (string, string, string) {
	// service
	var layer1ServiceName, layer2ServiceName, layer3ServiceName string
	layer1ServiceName = categoryItemInfo.ShowKindName
	if len(categoryItemInfo.ShowCategoryName) > 0 {
		if categoryItemInfo.UseCateGory {
			layer2ServiceName = categoryItemInfo.ShowCategoryName
			isBetaMeasures := billing.IsBetaMeasures(cost.Labels, 0, nil, false)
			if isBetaMeasures && len(categoryItemInfo.LabelKey) == 0 {
				layer2ServiceName = toBetaName(layer2ServiceName)
			}
			if len(categoryItemInfo.LabelKey) > 0 {
				hasBetaShowCategory := cost.Labels[constant.BetaCategoryLabelKey] != ""
				if len(categoryItemInfo.LabelKey) > 0 {
					layer3ServiceName = categoryItemInfo.Labels[categoryItemInfo.LabelKey]
					if isBetaMeasures {
						layer3ServiceName = toBetaName(layer3ServiceName)
					}
				} else if hasBetaShowCategory {
					layer3ServiceName = cost.Labels[constant.BetaCategoryLabelKey]
				} else {
					layer3ServiceName = BetaNoCatName
				}

			}
		} else {
			layer2ServiceName = categoryItemInfo.ShowCategoryName
			getLabelsKey, ok := cost.Labels[categoryItemInfo.LabelKey]
			if !ok {
				getLabelsKey = "default"
			}
			layer3ServiceName, ok = categoryItemInfo.Labels[getLabelsKey]
			if !ok {
			}
		}
	}
	return layer1ServiceName, layer2ServiceName, layer3ServiceName
}

func orderBillsServiceCosts(billsServiceCosts []forms.BillsServiceCosts, categoryLabels map[string]entity.CategoryItemShow) {
	layer1OrderMap, layer2OrderMap, layer3OrderMap := Get3LayerServiceOrder(categoryLabels)
	sortFunction := func(slice []forms.BillsServiceCosts, orderMap map[string]int32) {
		sort.Slice(slice, func(i, j int) bool {
			nameI := slice[i].ServiceName
			nameJ := slice[j].ServiceName
			orderI, okI := orderMap[nameI]
			orderJ, okJ := orderMap[nameJ]
			if !okI {
				orderI = showOrderLowest
			}

			if !okJ {
				orderJ = showOrderLowest
			}

			return orderI < orderJ
		})
	}

	//order 1
	sortFunction(billsServiceCosts, layer1OrderMap)
	for _, serviceCost1 := range billsServiceCosts {
		// order2
		sortFunction(serviceCost1.ServiceCosts, layer2OrderMap[serviceCost1.ServiceName])

		for _, serviceCost2 := range serviceCost1.ServiceCosts {
			//order3
			sortFunction(serviceCost2.ServiceCosts, layer3OrderMap[serviceCost1.ServiceName][serviceCost2.ServiceName])
		}
	}
}

func Get3LayerServiceOrder(categoryLabels map[string]entity.CategoryItemShow) (map[string]int32, map[string]map[string]int32, map[string]map[string]map[string]int32) {
	// order
	var layer1OrderMap = make(map[string]int32)
	var layer2OrderMap = make(map[string]map[string]int32)
	var layer3OrderMap = make(map[string]map[string]map[string]int32)
	for _, categoryItemShow := range categoryLabels {
		layer1OrderMap[categoryItemShow.ShowKindName] = categoryItemShow.OrderMap[categoryItemShow.ShowKindName]
		if categoryItemShow.ShowCategoryName == "" {
			continue
		}
		if _, ok := layer2OrderMap[categoryItemShow.ShowKindName]; !ok {
			layer2OrderMap[categoryItemShow.ShowKindName] = make(map[string]int32)
		}

		layer2OrderMap[categoryItemShow.ShowKindName][categoryItemShow.ShowCategoryName] = categoryItemShow.OrderMap[categoryItemShow.ShowCategoryName]

		if categoryItemShow.LabelKey == "" {
			continue
		}

		if _, ok := layer3OrderMap[categoryItemShow.ShowKindName]; !ok {
			layer3OrderMap[categoryItemShow.ShowKindName] = make(map[string]map[string]int32)
		}
		if _, ok := layer3OrderMap[categoryItemShow.ShowKindName][categoryItemShow.ShowCategoryName]; !ok {
			layer3OrderMap[categoryItemShow.ShowKindName][categoryItemShow.ShowCategoryName] = make(map[string]int32)
		}
		for _, layer3Name := range categoryItemShow.Labels {
			layer3OrderMap[categoryItemShow.ShowKindName][categoryItemShow.ShowCategoryName][layer3Name] = categoryItemShow.OrderMap[layer3Name]
		}
	}
	return layer1OrderMap, layer2OrderMap, layer3OrderMap
}

func updateCosts(billsCosts *forms.BillsCosts, rawPrice, discountPrice, credits, totalCost decimal.Decimal) {
	billsCosts.RunningTotal = billsCosts.RunningTotal.Add(rawPrice)
	billsCosts.Discounts = billsCosts.Discounts.Add(discountPrice)
	billsCosts.Credits = billsCosts.Credits.Add(credits)
	billsCosts.TotalCost = billsCosts.TotalCost.Add(totalCost)
}
