package payment

import (
	"fmt"
	"strings"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/cachehandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
)

type CostExplorerModel struct{}

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

func (*CostExplorerModel) CalculateCost(tenantID uint, costQuery *forms.CostExplorerQuery) (*forms.CostExplorerResult, error) {
	units, err := (&billing.UnitModel{}).GetAllUnits()
	if err != nil {
		return nil, err
	}
	var allCosts []entity.UnitCostSummary
	var creditsCosts, bindedInvoiceCosts, unbindInvoiceCosts, invoiceCosts []entity.UnitCostSummary

	invoiceID := costQuery.InvoiceID
	filter := []string{}
	params := map[string]interface{}{}

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

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

	if costQuery.InvoiceID != 0 {
		filter = append(filter, "invoice_id = @invoice_id")
		params["invoice_id"] = invoiceID
	}

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

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

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

	// use > and < instead of >= and <= to avoid the tidb index issues.
	err = db.GetDB().Raw(query, params).Find(&allCosts).Error

	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}

	log.Info("query costs done")

	precision := viper.GetInt32(config.BillingCostsPrecisionDigits)

	for _, cost := range allCosts {
		cost.RawPrice = cost.RawPrice.Truncate(precision)
		cost.CostPrice = cost.CostPrice.Truncate(precision)
		if cost.ConsumedSource == entity.ConsumedSource_Credits {
			creditsCosts = append(creditsCosts, cost)
			continue
		}
		invoiceCosts = append(invoiceCosts, cost)
		if cost.InvoiceID == entity.InvoiceUnbindID {
			unbindInvoiceCosts = append(unbindInvoiceCosts, cost)
		} else {
			bindedInvoiceCosts = append(bindedInvoiceCosts, cost)
		}
	}
	log.Info("split costs done")

	res := &forms.CostExplorerResult{
		CreditsCosts:         *CostFromEntity(creditsCosts, units),
		BindedInvoiceCosts:   *CostFromEntity(bindedInvoiceCosts, units),
		UnbindedInvoiceCosts: *CostFromEntity(unbindInvoiceCosts, units),
		InvoiceCostsCosts:    *CostFromEntity(invoiceCosts, units),
	}
	log.Info("calculate costs done")

	return res, nil
}

func (c *CostExplorerModel) GetInvoiceCosts(tenantID, invoiceID uint) (*forms.Costs, error) {
	costs, err := c.CalculateCost(tenantID, &forms.CostExplorerQuery{InvoiceID: invoiceID})
	if err != nil {
		return nil, err
	}
	return &costs.BindedInvoiceCosts, nil
}

func (c *CostExplorerModel) GetMonthlyCost(tenantID uint, year int, month int) (*forms.CostExplorerResult, error) {
	cache := cachehandler.Get()
	result := &forms.CostExplorerResult{}
	found := cache.Get("GetMonthlyCost", &result, tenantID, year, month)
	if found {
		return result, nil
	}
	start := timeutils.GetMonthDateByYearMonth(year, month)
	query := &forms.CostExplorerQuery{
		StartDate: start,
		EndDate:   start.AddDate(0, 1, 0),
	}
	result, err := c.CalculateCost(tenantID, query)
	if err == nil {
		cache.Set("GetMonthlyCost", result, tenantID, year, month)
	}
	return result, err
}
