package metrics

import (
	"fmt"
	"strings"

	telemetry "code.justin.tv/amzn/TwitchTelemetry"
)

// StubSampleObserver is a telemetry.SampleObserver that we can use in tests to inspect metrics that are sent.
// It should be injected into the component's SampleBuilder.
type StubSampleObserver struct {
	Samples []*telemetry.Sample
}

// StubSampleObserver implements the telemetry.SampleObserver interface.
var _ telemetry.SampleObserver = (*StubSampleObserver)(nil)

func (s *StubSampleObserver) ObserveSample(sample *telemetry.Sample) {
	s.Samples = append(s.Samples, sample)
}

func (s *StubSampleObserver) Flush() {}

func (s *StubSampleObserver) Stop() {}

// GetSamplesWithValue returns a slice of the samples that have the given metric name, dimensions and value.
// If no samples match, GetSamplesWithValue returns an error.
func (s *StubSampleObserver) GetSamplesWithValue(
	metricName string, dimensions map[string]string, expectedValue float64) ([]*telemetry.Sample, error) {

	return s.getSamples(
		metricName,
		dimensions,
		func(value float64) bool {
			return value == expectedValue
		},
	)
}

// GetSamplesWithPositiveValue returns a slice of the samples that have the given metric name, dimensions and a
// positive (> 0) value.
// If no samples match, GetSamplesWithPositiveValue returns an error.
func (s *StubSampleObserver) GetSamplesWithPositiveValue(metricName string, dimensions map[string]string) ([]*telemetry.Sample, error) {
	return s.getSamples(
		metricName,
		dimensions,
		func(value float64) bool {
			return value > 0
		},
	)
}

func (s *StubSampleObserver) getSamples(
	metricName string, dimensions map[string]string, valueCriteria func(value float64) bool) ([]*telemetry.Sample, error) {

	samples := s.Samples
	filteredSamples := make([]*telemetry.Sample, 0, len(samples))
	for _, sample := range samples {
		if sample.MetricID.Name == metricName {
			filteredSamples = append(filteredSamples, sample)
		}
	}
	if len(filteredSamples) == 0 {
		return nil, fmt.Errorf("could not find sample with metric name \"%s\"", metricName)
	}

	for expectedKey, expectedVal := range dimensions {
		samples = filteredSamples
		filteredSamples = make([]*telemetry.Sample, 0, len(samples))

		for _, sample := range samples {
			actualVal := sample.MetricID.Dimensions[expectedKey]

			if actualVal == expectedVal {
				filteredSamples = append(filteredSamples, sample)
			}
		}

		if len(filteredSamples) == 0 {
			return nil, fmt.Errorf("could not find sample with dimension key=\"%s\", value=\"%s\"", expectedKey, expectedVal)
		}
	}

	samples = filteredSamples
	filteredSamples = make([]*telemetry.Sample, 0, len(samples))
	for _, sample := range samples {
		if valueCriteria(sample.Value) {
			filteredSamples = append(filteredSamples, sample)
		}
	}
	if len(filteredSamples) == 0 {
		return nil, fmt.Errorf("could not find sample that matched criteria")
	}

	return filteredSamples, nil
}

func (s *StubSampleObserver) String() string {
	var b strings.Builder

	_, _ = fmt.Fprintln(&b, "Samples:")
	for _, sample := range s.Samples {
		_, _ = fmt.Fprintf(&b, " -metric name: %s\n", sample.MetricID.Name)
		_, _ = fmt.Fprintf(&b, "  value: %v\n", sample.Value)
		_, _ = fmt.Fprintln(&b, "  Dimensions")

		for key, val := range sample.MetricID.Dimensions {
			_, _ = fmt.Fprintf(&b, "    %s: %s\n", key, val)
		}
	}

	return b.String()
}
