package internal

import (
	"encoding/json"
	"fmt"
	"runtime"
)

var (
	// Unfortunately, the resolution of time.Now() on Windows is coarse: Two
	// sequential calls to time.Now() may return the same value, and tests
	// which expect non-zero durations may fail.  To avoid adding sleep
	// statements or mocking time.Now(), those tests are skipped on Windows.
	doDurationTests = runtime.GOOS != `windows`
)

// Validator is used for testing.
type Validator interface {
	Error(...interface{})
}

func validateStringField(v Validator, fieldName, v1, v2 string) {
	if v1 != v2 {
		v.Error(fieldName, v1, v2)
	}
}

type addValidatorField struct {
	field    interface{}
	original Validator
}

func (a addValidatorField) Error(fields ...interface{}) {
	fields = append([]interface{}{a.field}, fields...)
	a.original.Error(fields...)
}

// ExtendValidator is used to add more context to a validator.
func ExtendValidator(v Validator, field interface{}) Validator {
	return addValidatorField{
		field:    field,
		original: v,
	}
}

// WantMetric is a metric expectation.  If Data is nil, then any data values are
// acceptable.
type WantMetric struct {
	Name   string
	Scope  string
	Forced interface{} // true, false, or nil
	Data   []float64
}

// WantError is a traced error expectation.
type WantError struct {
	TxnName         string
	Msg             string
	Klass           string
	Caller          string
	URL             string
	UserAttributes  map[string]interface{}
	AgentAttributes map[string]interface{}
}

func uniquePointer() *struct{} {
	s := struct{}{}
	return &s
}

var (
	// MatchAnything is for use when matching attributes.
	MatchAnything = uniquePointer()
)

// WantEvent is a transaction or error event expectation.
type WantEvent struct {
	Intrinsics      map[string]interface{}
	UserAttributes  map[string]interface{}
	AgentAttributes map[string]interface{}
}

// WantTxnTrace is a transaction trace expectation.
type WantTxnTrace struct {
	MetricName      string
	CleanURL        string
	NumSegments     int
	UserAttributes  map[string]interface{}
	AgentAttributes map[string]interface{}
}

// WantSlowQuery is a slowQuery expectation.
type WantSlowQuery struct {
	Count        int32
	MetricName   string
	Query        string
	TxnName      string
	TxnURL       string
	DatabaseName string
	Host         string
	PortPathOrID string
	Params       map[string]interface{}
}

// HarvestTestinger is implemented by the app.  It sets an empty test harvest
// and modifies the connect reply if a callback is provided.
type HarvestTestinger interface {
	HarvestTesting(replyfn func(*ConnectReply))
}

// HarvestTesting allows integration packages to test instrumentation.
func HarvestTesting(app interface{}, replyfn func(*ConnectReply)) {
	ta, ok := app.(HarvestTestinger)
	if !ok {
		panic("HarvestTesting type assertion failure")
	}
	ta.HarvestTesting(replyfn)
}

// Expect exposes methods that allow for testing whether the correct data was
// captured.
type Expect interface {
	ExpectCustomEvents(t Validator, want []WantEvent)
	ExpectErrors(t Validator, want []WantError)
	ExpectErrorEvents(t Validator, want []WantEvent)
	ExpectErrorEventsPresent(t Validator, want []WantEvent)
	ExpectErrorEventsAbsent(t Validator, names []string)

	ExpectTxnEvents(t Validator, want []WantEvent)
	ExpectTxnEventsPresent(t Validator, want []WantEvent)
	ExpectTxnEventsAbsent(t Validator, names []string)

	ExpectMetrics(t Validator, want []WantMetric)
	ExpectMetricsPresent(t Validator, want []WantMetric)

	ExpectTxnTraces(t Validator, want []WantTxnTrace)
	ExpectSlowQueries(t Validator, want []WantSlowQuery)

	ExpectSpanEvents(t Validator, want []WantEvent)
	ExpectSpanEventsPresent(t Validator, want []WantEvent)
	ExpectSpanEventsAbsent(t Validator, names []string)
	ExpectSpanEventsCount(t Validator, c int)
}

func expectMetricField(t Validator, id metricID, v1, v2 float64, fieldName string) {
	if v1 != v2 {
		t.Error("metric fields do not match", id, v1, v2, fieldName)
	}
}

// ExpectMetricsPresent allows testing of metrics with requiring an exact match
func ExpectMetricsPresent(t Validator, mt *metricTable, expect []WantMetric) {
	expectedIds := make(map[metricID]struct{})
	for _, e := range expect {
		id := metricID{Name: e.Name, Scope: e.Scope}
		expectedIds[id] = struct{}{}
		m := mt.metrics[id]
		if nil == m {
			t.Error("unable to find metric", id)
			continue
		}

		if b, ok := e.Forced.(bool); ok {
			if b != (forced == m.forced) {
				t.Error("metric forced incorrect", b, m.forced, id)
			}
		}

		if nil != e.Data {
			expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied")
			expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated")
			expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed")
			expectMetricField(t, id, e.Data[3], m.data.min, "min")
			expectMetricField(t, id, e.Data[4], m.data.max, "max")
			expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares")
		}
	}
}

// ExpectMetrics allows testing of metrics.  It passes if mt exactly matches expect.
func ExpectMetrics(t Validator, mt *metricTable, expect []WantMetric) {
	if len(mt.metrics) != len(expect) {
		t.Error("metric counts do not match expectations", len(mt.metrics), len(expect))
	}
	expectedIds := make(map[metricID]struct{})
	for _, e := range expect {
		id := metricID{Name: e.Name, Scope: e.Scope}
		expectedIds[id] = struct{}{}
		m := mt.metrics[id]
		if nil == m {
			t.Error("unable to find metric", id)
			continue
		}

		if b, ok := e.Forced.(bool); ok {
			if b != (forced == m.forced) {
				t.Error("metric forced incorrect", b, m.forced, id)
			}
		}

		if nil != e.Data {
			expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied")
			expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated")
			expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed")
			expectMetricField(t, id, e.Data[3], m.data.min, "min")
			expectMetricField(t, id, e.Data[4], m.data.max, "max")
			expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares")
		}
	}
	for id := range mt.metrics {
		if _, ok := expectedIds[id]; !ok {
			t.Error("expected metrics does not contain", id.Name, id.Scope)
		}
	}
}

func expectAttributesPresent(v Validator, exists map[string]interface{}, expect map[string]interface{}) {
	for key, val := range expect {
		found, ok := exists[key]
		if !ok {
			v.Error("expected attribute not found: ", key)
			continue
		}
		if val == MatchAnything {
			continue
		}
		v1 := fmt.Sprint(found)
		v2 := fmt.Sprint(val)
		if v1 != v2 {
			v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2)
		}
	}
}

func expectAttributes(v Validator, exists map[string]interface{}, expect map[string]interface{}) {
	// TODO: This params comparison can be made smarter: Alert differences
	// based on sub/super set behavior.
	if len(exists) != len(expect) {
		v.Error("attributes length difference", len(exists), len(expect))
	}
	for key, val := range expect {
		found, ok := exists[key]
		if !ok {
			v.Error("expected attribute not found: ", key)
			continue
		}
		if val == MatchAnything {
			continue
		}
		v1 := fmt.Sprint(found)
		v2 := fmt.Sprint(val)
		if v1 != v2 {
			v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2)
		}
	}
	for key, val := range exists {
		_, ok := expect[key]
		if !ok {
			v.Error("unexpected attribute present: ", key, val)
			continue
		}
	}
}

// ExpectCustomEvents allows testing of custom events.  It passes if cs exactly matches expect.
func ExpectCustomEvents(v Validator, cs *customEvents, expect []WantEvent) {
	if len(cs.events.events) != len(expect) {
		v.Error("number of custom events does not match", len(cs.events.events),
			len(expect))
		return
	}
	for i, e := range expect {
		event, ok := cs.events.events[i].jsonWriter.(*CustomEvent)
		if !ok {
			v.Error("wrong custom event")
		} else {
			expectEvent(v, event, e)
		}
	}
}

func expectEventAbsent(v Validator, e json.Marshaler, names []string) {
	js, err := e.MarshalJSON()
	if nil != err {
		v.Error("unable to marshal event", err)
		return
	}

	var event []map[string]interface{}
	err = json.Unmarshal(js, &event)
	if nil != err {
		v.Error("unable to parse event json", err)
		return
	}

	intrinsics := event[0]
	userAttributes := event[1]
	agentAttributes := event[2]

	for _, name := range names {
		if _, ok := intrinsics[name]; ok {
			v.Error("unexpected key found", name)
		}

		if _, ok := userAttributes[name]; ok {
			v.Error("unexpected key found", name)
		}

		if _, ok := agentAttributes[name]; ok {
			v.Error("unexpected key found", name)
		}
	}
}

func expectEventPresent(v Validator, e json.Marshaler, expect WantEvent) {
	js, err := e.MarshalJSON()
	if nil != err {
		v.Error("unable to marshal event", err)
		return
	}
	var event []map[string]interface{}
	err = json.Unmarshal(js, &event)
	if nil != err {
		v.Error("unable to parse event json", err)
		return
	}
	intrinsics := event[0]
	userAttributes := event[1]
	agentAttributes := event[2]

	if nil != expect.Intrinsics {
		expectAttributesPresent(v, intrinsics, expect.Intrinsics)
	}
	if nil != expect.UserAttributes {
		expectAttributesPresent(v, userAttributes, expect.UserAttributes)
	}
	if nil != expect.AgentAttributes {
		expectAttributesPresent(v, agentAttributes, expect.AgentAttributes)
	}
}

func expectEvent(v Validator, e json.Marshaler, expect WantEvent) {
	js, err := e.MarshalJSON()
	if nil != err {
		v.Error("unable to marshal event", err)
		return
	}
	var event []map[string]interface{}
	err = json.Unmarshal(js, &event)
	if nil != err {
		v.Error("unable to parse event json", err)
		return
	}
	intrinsics := event[0]
	userAttributes := event[1]
	agentAttributes := event[2]

	if nil != expect.Intrinsics {
		expectAttributes(v, intrinsics, expect.Intrinsics)
	}
	if nil != expect.UserAttributes {
		expectAttributes(v, userAttributes, expect.UserAttributes)
	}
	if nil != expect.AgentAttributes {
		expectAttributes(v, agentAttributes, expect.AgentAttributes)
	}
}

// Second attributes have priority.
func mergeAttributes(a1, a2 map[string]interface{}) map[string]interface{} {
	a := make(map[string]interface{})
	for k, v := range a1 {
		a[k] = v
	}
	for k, v := range a2 {
		a[k] = v
	}
	return a
}

// ExpectErrorEventsPresent allows testing of events with requiring an exact match
func ExpectErrorEventsPresent(v Validator, events *errorEvents, expect []WantEvent) {
	for i, e := range expect {
		event, ok := events.events.events[i].jsonWriter.(*ErrorEvent)
		if !ok {
			v.Error("wrong span event in ExpectErrorEventsPresent")
		} else {
			expectEventPresent(v, event, e)
		}
	}
}

// ExpectErrorEventsAbsent allows testing that a set of attribute names are absent from the event data
func ExpectErrorEventsAbsent(v Validator, events *errorEvents, names []string) {
	for _, eventHarvested := range events.events.events {
		event, ok := eventHarvested.jsonWriter.(*ErrorEvent)
		if !ok {
			v.Error("wrong span event in ExpectErrorEventsAbsent")
		} else {
			expectEventAbsent(v, event, names)
		}
	}
}

// ExpectErrorEvents allows testing of error events.  It passes if events exactly matches expect.
func ExpectErrorEvents(v Validator, events *errorEvents, expect []WantEvent) {
	if len(events.events.events) != len(expect) {
		v.Error("number of custom events does not match",
			len(events.events.events), len(expect))
		return
	}
	for i, e := range expect {
		event, ok := events.events.events[i].jsonWriter.(*ErrorEvent)
		if !ok {
			v.Error("wrong error event")
		} else {
			if nil != e.Intrinsics {
				e.Intrinsics = mergeAttributes(map[string]interface{}{
					// The following intrinsics should always be present in
					// error events:
					"type":      "TransactionError",
					"timestamp": MatchAnything,
					"duration":  MatchAnything,
				}, e.Intrinsics)
			}
			expectEvent(v, event, e)
		}
	}
}

// ExpectSpanEventsCount allows us to count how many events the system generated
func ExpectSpanEventsCount(v Validator, events *spanEvents, c int) {
	len := len(events.events.events)
	if len != c {
		v.Error(fmt.Sprintf("expected %d span events, found %d", c, len))
	}
}

// ExpectSpanEventsPresent allows us to test for the presence and value of events
// without also requiring an exact match
func ExpectSpanEventsPresent(v Validator, events *spanEvents, expect []WantEvent) {
	for i, e := range expect {
		event, ok := events.events.events[i].jsonWriter.(*SpanEvent)
		if !ok {
			v.Error("wrong span event in ExpectSpanEventsPresent")
		} else {
			expectEventPresent(v, event, e)
		}
	}
}

// ExpectSpanEventsAbsent allows us to ensure that a set of attribute names are absent
// from the event data
func ExpectSpanEventsAbsent(v Validator, events *spanEvents, names []string) {
	for _, eventHarvested := range events.events.events {
		event, ok := eventHarvested.jsonWriter.(*SpanEvent)
		if !ok {
			v.Error("wrong span event in ExpectSpanEventsAbsent")
		} else {
			expectEventAbsent(v, event, names)
		}
	}
}

// ExpectSpanEvents allows testing of span events.  It passes if events exactly matches expect.
func ExpectSpanEvents(v Validator, events *spanEvents, expect []WantEvent) {
	if len(events.events.events) != len(expect) {
		v.Error("number of span events does not match",
			len(events.events.events), len(expect))
		return
	}
	for i, e := range expect {
		event, ok := events.events.events[i].jsonWriter.(*SpanEvent)
		if !ok {
			v.Error("wrong span event")
		} else {
			if nil != e.Intrinsics {
				e.Intrinsics = mergeAttributes(map[string]interface{}{
					// The following intrinsics should always be present in
					// span events:
					"type":      "Span",
					"timestamp": MatchAnything,
					"duration":  MatchAnything,
				}, e.Intrinsics)
			}
			expectEvent(v, event, e)
		}
	}
}

// ExpectTxnEventsPresent allows us to test for the presence and value of events
// without also requiring an exact match
func ExpectTxnEventsPresent(v Validator, events *txnEvents, expect []WantEvent) {
	for i, e := range expect {
		event, ok := events.events.events[i].jsonWriter.(*TxnEvent)
		if !ok {
			v.Error("wrong txn event in ExpectTxnEventsPresent")
		} else {
			expectEventPresent(v, event, e)
		}
	}
}

// ExpectTxnEventsAbsent allows us to ensure that a set of attribute names are absent
// from the event data
func ExpectTxnEventsAbsent(v Validator, events *txnEvents, names []string) {
	for _, eventHarvested := range events.events.events {
		event, ok := eventHarvested.jsonWriter.(*TxnEvent)
		if !ok {
			v.Error("wrong txn event in ExpectTxnEventsAbsent")
		} else {
			expectEventAbsent(v, event, names)
		}
	}
}

// ExpectTxnEvents allows testing of txn events.
func ExpectTxnEvents(v Validator, events *txnEvents, expect []WantEvent) {
	if len(events.events.events) != len(expect) {
		v.Error("number of txn events does not match",
			len(events.events.events), len(expect))
		return
	}
	for i, e := range expect {
		event, ok := events.events.events[i].jsonWriter.(*TxnEvent)
		if !ok {
			v.Error("wrong txn event")
		} else {
			if nil != e.Intrinsics {
				e.Intrinsics = mergeAttributes(map[string]interface{}{
					// The following intrinsics should always be present in
					// txn events:
					"type":      "Transaction",
					"timestamp": MatchAnything,
					"duration":  MatchAnything,
					"error":     MatchAnything,
				}, e.Intrinsics)
			}
			expectEvent(v, event, e)
		}
	}
}

func expectError(v Validator, err *tracedError, expect WantError) {
	caller := topCallerNameBase(err.ErrorData.Stack)
	validateStringField(v, "caller", expect.Caller, caller)
	validateStringField(v, "txnName", expect.TxnName, err.FinalName)
	validateStringField(v, "klass", expect.Klass, err.Klass)
	validateStringField(v, "msg", expect.Msg, err.Msg)
	validateStringField(v, "URL", expect.URL, err.CleanURL)
	js, errr := err.MarshalJSON()
	if nil != errr {
		v.Error("unable to marshal error json", errr)
		return
	}
	var unmarshalled []interface{}
	errr = json.Unmarshal(js, &unmarshalled)
	if nil != errr {
		v.Error("unable to unmarshal error json", errr)
		return
	}
	attributes := unmarshalled[4].(map[string]interface{})
	agentAttributes := attributes["agentAttributes"].(map[string]interface{})
	userAttributes := attributes["userAttributes"].(map[string]interface{})

	if nil != expect.UserAttributes {
		expectAttributes(v, userAttributes, expect.UserAttributes)
	}
	if nil != expect.AgentAttributes {
		expectAttributes(v, agentAttributes, expect.AgentAttributes)
	}
}

// ExpectErrors allows testing of errors.
func ExpectErrors(v Validator, errors harvestErrors, expect []WantError) {
	if len(errors) != len(expect) {
		v.Error("number of errors mismatch", len(errors), len(expect))
		return
	}
	for i, e := range expect {
		expectError(v, errors[i], e)
	}
}

func countSegments(node []interface{}) int {
	count := 1
	children := node[4].([]interface{})
	for _, c := range children {
		node := c.([]interface{})
		count += countSegments(node)
	}
	return count
}

func expectTxnTrace(v Validator, got json.Marshaler, expect WantTxnTrace) {
	js, err := got.MarshalJSON()
	if nil != err {
		v.Error("unable to marshal txn trace json", err)
		return
	}
	var unmarshalled []interface{}
	err = json.Unmarshal(js, &unmarshalled)
	if nil != err {
		v.Error("unable to unmarshal error json", err)
		return
	}
	duration := unmarshalled[1].(float64)
	name := unmarshalled[2].(string)
	cleanURL := unmarshalled[3].(string)
	traceData := unmarshalled[4].([]interface{})

	rootNode := traceData[3].([]interface{})
	attributes := traceData[4].(map[string]interface{})
	userAttributes := attributes["userAttributes"].(map[string]interface{})
	agentAttributes := attributes["agentAttributes"].(map[string]interface{})

	validateStringField(v, "metric name", expect.MetricName, name)
	validateStringField(v, "request url", expect.CleanURL, cleanURL)

	if doDurationTests && 0 == duration {
		v.Error("zero trace duration")
	}

	if nil != expect.UserAttributes {
		expectAttributes(v, userAttributes, expect.UserAttributes)
	}
	if nil != expect.AgentAttributes {
		expectAttributes(v, agentAttributes, expect.AgentAttributes)
	}
	numSegments := countSegments(rootNode)
	// The expectation segment count does not include the two root nodes.
	numSegments -= 2
	if expect.NumSegments != numSegments {
		v.Error("wrong number of segments", expect.NumSegments, numSegments)
	}
}

// ExpectTxnTraces allows testing of transaction traces.
func ExpectTxnTraces(v Validator, traces *harvestTraces, want []WantTxnTrace) {
	if len(want) != traces.Len() {
		v.Error("number of traces do not match", len(want), traces.Len())
	}

	actual := traces.slice()
	for i, expected := range want {
		expectTxnTrace(v, actual[i], expected)
	}
}

func expectSlowQuery(t Validator, slowQuery *slowQuery, want WantSlowQuery) {
	if slowQuery.Count != want.Count {
		t.Error("wrong Count field", slowQuery.Count, want.Count)
	}
	validateStringField(t, "MetricName", slowQuery.DatastoreMetric, want.MetricName)
	validateStringField(t, "Query", slowQuery.ParameterizedQuery, want.Query)
	validateStringField(t, "TxnEvent.FinalName", slowQuery.TxnEvent.FinalName, want.TxnName)
	validateStringField(t, "TxnEvent.CleanURL", slowQuery.TxnEvent.CleanURL, want.TxnURL)
	validateStringField(t, "DatabaseName", slowQuery.DatabaseName, want.DatabaseName)
	validateStringField(t, "Host", slowQuery.Host, want.Host)
	validateStringField(t, "PortPathOrID", slowQuery.PortPathOrID, want.PortPathOrID)
	expectAttributes(t, map[string]interface{}(slowQuery.QueryParameters), want.Params)
}

// ExpectSlowQueries allows testing of slow queries.
func ExpectSlowQueries(t Validator, slowQueries *slowQueries, want []WantSlowQuery) {
	if len(want) != len(slowQueries.priorityQueue) {
		t.Error("wrong number of slow queries",
			"expected", len(want), "got", len(slowQueries.priorityQueue))
		return
	}
	for _, s := range want {
		idx, ok := slowQueries.lookup[s.Query]
		if !ok {
			t.Error("unable to find slow query", s.Query)
			continue
		}
		expectSlowQuery(t, slowQueries.priorityQueue[idx], s)
	}
}
