package query_test

import (
	"bytes"
	"context"
	"io"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/influxdata/flux"
	"github.com/influxdata/flux/metadata"
	platform2 "github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/query"
	"github.com/influxdata/influxdb/v2/query/mock"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	"go.uber.org/zap"
)

var orgID = MustIDBase16("ba55ba55ba55ba55")

// MustIDBase16 is an helper to ensure a correct ID is built during testing.
func MustIDBase16(s string) platform2.ID {
	id, err := platform2.IDFromString(s)
	if err != nil {
		panic(err)
	}
	return *id
}

var opts = []cmp.Option{
	cmpopts.IgnoreUnexported(query.ProxyRequest{}),
	cmpopts.IgnoreUnexported(query.Request{}),
}

type contextKey string

const loggingCtxKey contextKey = "do-logging"

func TestLoggingProxyQueryService(t *testing.T) {
	// Set a Jaeger in-memory tracer to get span information in the query log.
	oldTracer := opentracing.GlobalTracer()
	defer opentracing.SetGlobalTracer(oldTracer)
	sampler := jaeger.NewConstSampler(true)
	reporter := jaeger.NewInMemoryReporter()
	tracer, closer := jaeger.NewTracer(t.Name(), sampler, reporter)
	defer closer.Close()
	opentracing.SetGlobalTracer(tracer)

	wantStats := flux.Statistics{
		TotalDuration:   time.Second,
		CompileDuration: time.Second,
		QueueDuration:   time.Second,
		PlanDuration:    time.Second,
		RequeueDuration: time.Second,
		ExecuteDuration: time.Second,
		Concurrency:     2,
		MaxAllocated:    2048,
		Metadata:        make(metadata.Metadata),
	}
	wantStats.Metadata.Add("some-mock-metadata", 42)
	wantBytes := 10
	pqs := &mock.ProxyQueryService{
		QueryF: func(ctx context.Context, w io.Writer, req *query.ProxyRequest) (flux.Statistics, error) {
			w.Write(make([]byte, wantBytes))
			return wantStats, nil
		},
	}
	var logs []query.Log
	logger := &mock.QueryLogger{
		LogFn: func(l query.Log) error {
			logs = append(logs, l)
			return nil
		},
	}

	req := &query.ProxyRequest{
		Request: query.Request{
			Authorization:  nil,
			OrganizationID: orgID,
			Compiler:       nil,
		},
		Dialect: nil,
	}

	t.Run("log", func(t *testing.T) {
		defer func() {
			logs = nil
		}()
		wantTime := time.Now()
		lpqs := query.NewLoggingProxyQueryService(zap.NewNop(), logger, pqs)
		lpqs.SetNowFunctionForTesting(func() time.Time {
			return wantTime
		})

		var buf bytes.Buffer
		stats, err := lpqs.Query(context.Background(), &buf, req)
		if err != nil {
			t.Fatal(err)
		}
		if !cmp.Equal(wantStats, stats, opts...) {
			t.Errorf("unexpected query stats: -want/+got\n%s", cmp.Diff(wantStats, stats, opts...))
		}
		traceID := reporter.GetSpans()[0].Context().(jaeger.SpanContext).TraceID().String()
		wantLogs := []query.Log{{
			Time:           wantTime,
			OrganizationID: orgID,
			TraceID:        traceID,
			Sampled:        true,
			Error:          nil,
			ProxyRequest:   req,
			ResponseSize:   int64(wantBytes),
			Statistics:     wantStats,
		}}
		if !cmp.Equal(wantLogs, logs, opts...) {
			t.Errorf("unexpected query logs: -want/+got\n%s", cmp.Diff(wantLogs, logs, opts...))
		}
	})

	t.Run("conditional logging", func(t *testing.T) {
		defer func() {
			logs = nil
		}()

		condLog := query.ConditionalLogging(func(ctx context.Context) bool {
			return ctx.Value(loggingCtxKey) != nil
		})

		lpqs := query.NewLoggingProxyQueryService(zap.NewNop(), logger, pqs, condLog)
		_, err := lpqs.Query(context.Background(), io.Discard, req)
		if err != nil {
			t.Fatal(err)
		}

		if len(logs) != 0 {
			t.Fatal("expected query service not to log")
		}

		ctx := context.WithValue(context.Background(), loggingCtxKey, true)
		_, err = lpqs.Query(ctx, io.Discard, req)
		if err != nil {
			t.Fatal(err)
		}

		if len(logs) != 1 {
			t.Fatal("expected query service to log")
		}
	})

	t.Run("require metadata key", func(t *testing.T) {
		defer func() {
			logs = nil
		}()

		reqMeta1 := query.RequireMetadataKey("this-metadata-wont-be-found")
		lpqs1 := query.NewLoggingProxyQueryService(zap.NewNop(), logger, pqs, reqMeta1)

		_, err := lpqs1.Query(context.Background(), io.Discard, req)
		if err != nil {
			t.Fatal(err)
		}

		if len(logs) != 0 {
			t.Fatal("expected query service not to log")
		}

		reqMeta2 := query.RequireMetadataKey("some-mock-metadata")
		lpqs2 := query.NewLoggingProxyQueryService(zap.NewNop(), logger, pqs, reqMeta2)

		_, err = lpqs2.Query(context.Background(), io.Discard, req)
		if err != nil {
			t.Fatal(err)
		}

		if len(logs) != 1 {
			t.Fatal("expected query service to log")
		}
	})
}
