package internal

import (
	"strings"
	"sync"
	"time"
)

// Harvestable is something that can be merged into a Harvest.
type Harvestable interface {
	MergeIntoHarvest(h *Harvest)
}

// Harvest contains collected data.
type Harvest struct {
	Metrics      *metricTable
	CustomEvents *customEvents
	TxnEvents    *txnEvents
	ErrorEvents  *errorEvents
	ErrorTraces  harvestErrors
	TxnTraces    *harvestTraces
	SlowSQLs     *slowQueries
}

// Payloads returns a map from expected collector method name to data type.
func (h *Harvest) Payloads() map[string]PayloadCreator {
	return map[string]PayloadCreator{
		cmdMetrics:      h.Metrics,
		cmdCustomEvents: h.CustomEvents,
		cmdTxnEvents:    h.TxnEvents,
		cmdErrorEvents:  h.ErrorEvents,
		cmdErrorData:    h.ErrorTraces,
		cmdTxnTraces:    h.TxnTraces,
		cmdSlowSQLs:     h.SlowSQLs,
	}
}

// NewHarvest returns a new Harvest.
func NewHarvest(now time.Time) *Harvest {
	return &Harvest{
		Metrics:      newMetricTable(maxMetrics, now),
		CustomEvents: newCustomEvents(maxCustomEvents),
		TxnEvents:    newTxnEvents(maxTxnEvents),
		ErrorEvents:  newErrorEvents(maxErrorEvents),
		ErrorTraces:  newHarvestErrors(maxHarvestErrors),
		TxnTraces:    newHarvestTraces(),
		SlowSQLs:     newSlowQueries(maxHarvestSlowSQLs),
	}
}

var (
	trackMutex   sync.Mutex
	trackMetrics []string
)

// TrackUsage helps track which integration packages are used.
func TrackUsage(s ...string) {
	trackMutex.Lock()
	defer trackMutex.Unlock()

	m := "Supportability/" + strings.Join(s, "/")
	trackMetrics = append(trackMetrics, m)
}

func createTrackUsageMetrics(metrics *metricTable) {
	trackMutex.Lock()
	defer trackMutex.Unlock()

	for _, m := range trackMetrics {
		metrics.addSingleCount(m, forced)
	}
}

// CreateFinalMetrics creates extra metrics at harvest time.
func (h *Harvest) CreateFinalMetrics() {
	h.Metrics.addSingleCount(instanceReporting, forced)

	h.Metrics.addCount(customEventsSeen, h.CustomEvents.numSeen(), forced)
	h.Metrics.addCount(customEventsSent, h.CustomEvents.numSaved(), forced)

	h.Metrics.addCount(txnEventsSeen, h.TxnEvents.numSeen(), forced)
	h.Metrics.addCount(txnEventsSent, h.TxnEvents.numSaved(), forced)

	h.Metrics.addCount(errorEventsSeen, h.ErrorEvents.numSeen(), forced)
	h.Metrics.addCount(errorEventsSent, h.ErrorEvents.numSaved(), forced)

	if h.Metrics.numDropped > 0 {
		h.Metrics.addCount(supportabilityDropped, float64(h.Metrics.numDropped), forced)
	}

	createTrackUsageMetrics(h.Metrics)
}

// PayloadCreator is a data type in the harvest.
type PayloadCreator interface {
	// In the event of a rpm request failure (hopefully simply an
	// intermittent collector issue) the payload may be merged into the next
	// time period's harvest.
	Harvestable
	// Data prepares JSON in the format expected by the collector endpoint.
	// This method should return (nil, nil) if the payload is empty and no
	// rpm request is necessary.
	Data(agentRunID string, harvestStart time.Time) ([]byte, error)
}

// CreateTxnMetrics creates metrics for a transaction.
func CreateTxnMetrics(args *TxnData, metrics *metricTable) {
	// Duration Metrics
	rollup := backgroundRollup
	if args.IsWeb {
		rollup = webRollup
		metrics.addDuration(dispatcherMetric, "", args.Duration, 0, forced)
	}

	metrics.addDuration(args.FinalName, "", args.Duration, args.Exclusive, forced)
	metrics.addDuration(rollup, "", args.Duration, args.Exclusive, forced)

	// Apdex Metrics
	if args.Zone != ApdexNone {
		metrics.addApdex(apdexRollup, "", args.ApdexThreshold, args.Zone, forced)

		mname := apdexPrefix + removeFirstSegment(args.FinalName)
		metrics.addApdex(mname, "", args.ApdexThreshold, args.Zone, unforced)
	}

	// Error Metrics
	if args.HasErrors() {
		metrics.addSingleCount(errorsRollupMetric.all, forced)
		metrics.addSingleCount(errorsRollupMetric.webOrOther(args.IsWeb), forced)
		metrics.addSingleCount(errorsPrefix+args.FinalName, forced)
	}

	// Queueing Metrics
	if args.Queuing > 0 {
		metrics.addDuration(queueMetric, "", args.Queuing, args.Queuing, forced)
	}
}
