package metricsutil

import (
	"time"

	"github.com/milvus-io/milvus/pkg/v3/util/timerecord"
)

var (
	_ labeledRecord = QuerySegmentAccessRecord{}
	_ labeledRecord = SearchSegmentAccessRecord{}
	_ labeledRecord = &CacheLoadRecord{}
	_ labeledRecord = &CacheEvictRecord{}
)

// SegmentLabel is the label of a segment.
type SegmentLabel struct {
	DatabaseName  string `expr:"DatabaseName"`
	ResourceGroup string `expr:"ResourceGroup"`
}

// CacheLoadRecord records the metrics of a cache load.
type CacheLoadRecord struct {
	numBytes uint64
	baseRecord
}

// NewCacheLoadRecord creates a new CacheLoadRecord.
func NewCacheLoadRecord(label SegmentLabel) *CacheLoadRecord {
	return &CacheLoadRecord{
		baseRecord: newBaseRecord(label),
	}
}

// WithBytes sets the bytes of the record.
func (r *CacheLoadRecord) WithBytes(bytes uint64) *CacheLoadRecord {
	r.numBytes = bytes
	return r
}

// getBytes returns the bytes of the record.
func (r *CacheLoadRecord) getBytes() float64 {
	return float64(r.numBytes)
}

// Finish finishes the record.
func (r *CacheLoadRecord) Finish(err error) {
	r.finish(err)
	getGlobalObserver().Observe(r)
}

type CacheEvictRecord struct {
	bytes uint64
	baseRecord
}

// NewCacheEvictRecord creates a new CacheEvictRecord.
func NewCacheEvictRecord(label SegmentLabel) *CacheEvictRecord {
	return &CacheEvictRecord{
		baseRecord: newBaseRecord(label),
	}
}

// WithBytes sets the bytes of the record.
func (r *CacheEvictRecord) WithBytes(bytes uint64) *CacheEvictRecord {
	r.bytes = bytes
	return r
}

// getBytes returns the bytes of the record.
func (r *CacheEvictRecord) getBytes() float64 {
	return float64(r.bytes)
}

// Finish finishes the record.
func (r *CacheEvictRecord) Finish(err error) {
	r.finish(err)
	getGlobalObserver().Observe(r)
}

// NewQuerySegmentAccessRecord creates a new QuerySegmentMetricRecorder.
func NewQuerySegmentAccessRecord(label SegmentLabel, queryLabel string) QuerySegmentAccessRecord {
	return QuerySegmentAccessRecord{
		segmentAccessRecord: newSegmentAccessRecord(label),
		queryLabel:          queryLabel,
	}
}

// NewSearchSegmentAccessRecord creates a new SearchSegmentMetricRecorder.
func NewSearchSegmentAccessRecord(label SegmentLabel) SearchSegmentAccessRecord {
	return SearchSegmentAccessRecord{
		segmentAccessRecord: newSegmentAccessRecord(label),
	}
}

// QuerySegmentAccessRecord records the metrics of a query segment.
type QuerySegmentAccessRecord struct {
	*segmentAccessRecord
	queryLabel string
}

func (r QuerySegmentAccessRecord) Finish(err error) {
	r.finish(err)
	getGlobalObserver().Observe(r)
}

// SearchSegmentAccessRecord records the metrics of a search segment.
type SearchSegmentAccessRecord struct {
	*segmentAccessRecord
}

func (r SearchSegmentAccessRecord) Finish(err error) {
	r.finish(err)
	getGlobalObserver().Observe(r)
}

// segmentAccessRecord records the metrics of the segment.
type segmentAccessRecord struct {
	isCacheMiss  bool          // whether the access is a cache miss.
	waitLoadCost time.Duration // time cost of waiting for loading data.
	baseRecord
}

// newSegmentAccessRecord creates a new accessMetricRecorder.
func newSegmentAccessRecord(label SegmentLabel) *segmentAccessRecord {
	return &segmentAccessRecord{
		baseRecord: newBaseRecord(label),
	}
}

// CacheMissing records the cache missing.
func (r *segmentAccessRecord) CacheMissing() {
	r.isCacheMiss = true
	r.waitLoadCost = r.timeRecorder.RecordSpan()
}

// getWaitLoadMilliseconds returns the wait load seconds of the recorder.
func (r *segmentAccessRecord) getWaitLoadMilliseconds() float64 {
	return r.waitLoadCost.Seconds() * 1000
}

// getWaitLoadDuration returns the wait load duration of the recorder.
func (r *segmentAccessRecord) getWaitLoadDuration() time.Duration {
	return r.waitLoadCost
}

// newBaseRecord returns a new baseRecord.
func newBaseRecord(label SegmentLabel) baseRecord {
	return baseRecord{
		label:        label,
		timeRecorder: timerecord.NewTimeRecorder(""),
	}
}

// baseRecord records the metrics of the segment.
type baseRecord struct {
	label        SegmentLabel
	duration     time.Duration
	err          error
	timeRecorder *timerecord.TimeRecorder
}

// Label returns the label of the recorder.
func (r *baseRecord) Label() SegmentLabel {
	return r.label
}

// getError returns the error of the recorder.
func (r *baseRecord) getError() error {
	return r.err
}

// getDuration returns the duration of the recorder.
func (r *baseRecord) getDuration() time.Duration {
	return r.duration
}

// getMilliseconds returns the duration of the recorder in seconds.
func (r *baseRecord) getMilliseconds() float64 {
	return r.duration.Seconds() * 1000
}

// finish finishes the record.
func (r *baseRecord) finish(err error) {
	r.err = err
	r.duration = r.timeRecorder.ElapseSpan()
}
