package measure_report

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/tidbcloud/aipower/consts"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/measure"
	"go.uber.org/zap"
)

type MeasureReporter interface {
	InitMeasureSQS(info string, url string) error
	PutMeasure(info measure.BillingMeasure) error
	SendMeasure() error
}

type FakeReportImpl struct{}

func (f *FakeReportImpl) InitMeasureSQS(info string, url string) error {
	return nil
}

func (f *FakeReportImpl) PutMeasure(info measure.BillingMeasure) error {
	return nil
}

func (f *FakeReportImpl) SendMeasure() error {
	return nil
}

var Report MeasureReporter = &MeasureReportImpl{
	handler: &measure.MeasureSQSImpl{},
}

type MeasureReportImpl struct {
	handler measure.MeasureSQS
	mu      sync.Mutex
}

const retryTimes = 20

var tempMeasureMap = make(map[int64]map[string]measure.BillingMeasure, 256)

func InitMeasure(env string, url string) error {
	err := Report.InitMeasureSQS(env, url)
	if err != nil {
		return err
	}
	err = RunWorker()
	if err != nil {
		return err
	}
	return nil

}

func (m *MeasureReportImpl) InitMeasureSQS(info string, url string) error {
	err := m.handler.InitMeasureSqsHandler(info, url)
	if err != nil {
		return err
	}
	return nil

}

func (m *MeasureReportImpl) PutMeasure(info measure.BillingMeasure) error {
	err := m.handler.CheckMeasure(info)
	if err != nil {
		return err
	}
	// key = unit + appid
	strKey := fmt.Sprintf("%s:%s", info.Unit, info.Labels[consts.MeasureLabelAppID])
	m.mu.Lock()
	defer m.mu.Unlock()
	tenantID := int64(info.TenantID)

	if tempMeasureMap[tenantID] == nil {
		tempMeasureMap[tenantID] = make(map[string]measure.BillingMeasure)
		tempMeasureMap[tenantID][strKey] = info
	} else {
		value, ok := tempMeasureMap[tenantID][strKey]

		if ok {
			info.Cost += value.Cost
			tempMeasureMap[tenantID][strKey] = info
		} else {
			tempMeasureMap[tenantID][strKey] = info
		}

	}

	return nil

}

func (m *MeasureReportImpl) SendMeasure() error {
	ctx := context.Background()
	cntMeasure := len(tempMeasureMap)
	var errMeasureMap = make(map[int64]map[string]measure.BillingMeasure, 256)
	sendMeasure := func(tenantID int64, strKey string, billingMeasure measure.BillingMeasure) {
		err := m.handler.SendMeasure(billingMeasure)
		if err != nil {
			tilog.Error(ctx, "measure SDK send QSQ err ", zap.Error(err), zap.Any("billingMeasure", billingMeasure))
			if errMeasureMap[tenantID] == nil {
				errMeasureMap[tenantID] = make(map[string]measure.BillingMeasure)
			}
			errMeasureMap[tenantID][strKey] = billingMeasure
		}

	}
	m.mu.Lock()
	for tenantID, innerMap := range tempMeasureMap {
		for strKey, billingMeasure := range innerMap {
			sendMeasure(tenantID, strKey, billingMeasure)
			delete(tempMeasureMap[tenantID], strKey)

			if len(tempMeasureMap[tenantID]) == 0 {
				delete(tempMeasureMap, tenantID)

			}
		}
	}
	m.mu.Unlock()

	// retry
	count := 1
	if len(errMeasureMap) > 0 {
		for tenantID, innerMap := range errMeasureMap {
			for strKey, billingMeasure := range innerMap {
				for {
					sendMeasure(tenantID, strKey, billingMeasure)
					delete(tempMeasureMap[tenantID], strKey)
					if len(tempMeasureMap[tenantID]) == 0 {
						delete(tempMeasureMap, tenantID)
						break
					}
					if count > retryTimes {
						break
					}
					time.Sleep(time.Second * time.Duration(count))
					count++

				}
			}
		}
	}

	tilog.Info(ctx, "send measure ", zap.Int("cnt", cntMeasure-len(errMeasureMap)))

	// printf
	if len(errMeasureMap) > 0 {
		tilog.Info(ctx, "errMeasureMap ", zap.Any("send failed :", errMeasureMap))
		return errors.New("some measures failed to send")
	}

	return nil
}
