package metering

import (
	"context"

	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/billing"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	billingrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/metering"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type ReportMetering2billingSvc struct {
	trans                   db.TransProvider
	billingReportRecordRepo billingrepo.ReportRecordRepo
	billingCli              billing.Client
	cfg                     *config.BillingConfig
}

func NewReportMetering2billingSvc(
	trans db.TransProvider,
	billingReportRecordRepo billingrepo.ReportRecordRepo,
	billingCli billing.Client,
	cfg *config.Config,
) ReportMetering2billingSvc {
	return ReportMetering2billingSvc{
		trans:                   trans,
		billingReportRecordRepo: billingReportRecordRepo,
		billingCli:              billingCli,
		cfg:                     &cfg.BillingConfig,
	}
}

func (svc ReportMetering2billingSvc) Report(ctx context.Context, batchSize int) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "ReportMetering2billingSvc")
	defer span.End()

	logger := tilog.WithContextS(ctx)
	cursor := uint64(0)

	for {
		records, err := svc.billingReportRecordRepo.LoadUnsyncedRecords(ctx, cursor, batchSize)
		if err != nil {
			return err
		}
		logger.Infof("try to sync records to billing, size=%d", len(records))
		if len(records) == 0 {
			break
		}
		span.AddEvent("LoadUnsyncedRecords")

		ids := make([]uint64, 0, len(records))
		for _, each := range records {
			ids = append(ids, each.ID)
		}

		if err := svc.trans.Transaction(ctx, func(ctx context.Context) error {
			if err := svc.sync2billing(ctx, records); err != nil {
				return err.Chain("sync billing report record to billing")
			}
			if err := svc.billingReportRecordRepo.MarkSynced(ctx, ids...); err != nil {
				return err.Chain("mark billing report record synced")
			}
			return nil
		}); err != nil {
			logger.Error(err, "report record to billing failed, retry later", zap.Error(err))
		}
		span.AddEvent("sync2billing")

		if len(records) < batchSize {
			logger.Infof("records sync finished")
			break
		}

		cursor = records[len(records)-1].ID
	}

	return nil
}

func (svc ReportMetering2billingSvc) sync2billing(
	ctx context.Context,
	records []*entity.BillingReportRecord,
) bizerr.BizError {
	facadeRecords := make([]*billing.Report, 0, len(records))

	for _, record := range records {
		for _, item := range record.Data {
			// Stop Measure metering record after the switch time
			ok := svc.skipSync2billing(record, item)
			if ok {
				continue
			}
			facadeRecords = append(facadeRecords, &billing.Report{
				CostID:        item.CostID,
				TenantID:      record.TenantID,
				CostTimeStamp: item.CostTimeStamp,
				Sku:           item.SKU,
				Cost:          item.Cost,
				Labels:        item.Labels,
			})
		}
	}

	return svc.billingCli.Measure(ctx, facadeRecords)
}

func (svc ReportMetering2billingSvc) skipSync2billing(
	record *entity.BillingReportRecord,
	item *entity.BillingMeteringReport,
) bool {
	// Stop Measure instance metering record after the switch time
	if record.ClusterMeteringID > 0 && svc.cfg.InstanceMeteringSwitchTime > 0 {
		if uint64(item.CostTimeStamp) >= svc.cfg.InstanceMeteringSwitchTime {
			// if some tenant in block list then we should check the tenant id and switch time
			tenantid := record.TenantID
			for _, inner := range svc.cfg.InstanceMeteringSwitchTimeBlockList {
				if tenantid == inner.TenantID && uint64(item.CostTimeStamp) < inner.SwitchTime {
					// if the tenant id is in the block list and the switch time is not reached, we should not stop the record
					return false
				}
			}
			return true
		}
		if len(svc.cfg.InstanceTenantSwitchFilter) > 0 {
			// Stop Measure instance metering record after the switch time for specific tenant
			tenantid := record.TenantID
			for _, inner := range svc.cfg.InstanceTenantSwitchFilter {
				if tenantid == inner.TenantID && uint64(item.CostTimeStamp) >= inner.SwitchTime {
					return true
				}
			}
		}
	}
	return false
}
