// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.

package ocgremlin

import (
	"context"
	"strconv"
	"time"

	"github.com/facebook/ent/dialect/gremlin"

	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
	"go.opencensus.io/tag"
)

// The following measures are supported for use in custom views.
var (
	RequestCount = stats.Int64(
		"gremlin/request_count",
		"Number of Gremlin requests started",
		stats.UnitDimensionless,
	)
	ResponseBytes = stats.Int64(
		"gremlin/response_bytes",
		"Total number of bytes in response data",
		stats.UnitBytes,
	)
	RoundTripLatency = stats.Float64(
		"gremlin/roundtrip_latency",
		"End-to-end latency",
		stats.UnitMilliseconds,
	)
)

// The following tags are applied to stats recorded by this package.
var (
	// StatusCode is the numeric Gremlin response status code,
	// or "error" if a transport error occurred and no status code was read.
	StatusCode, _ = tag.NewKey("gremlin_status_code")
)

// Default distributions used by views in this package.
var (
	DefaultSizeDistribution    = view.Distribution(32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576)
	DefaultLatencyDistribution = view.Distribution(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
)

// Package ocgremlin provides some convenience views for measures.
// You still need to register these views for data to actually be collected.
var (
	RequestCountView = &view.View{
		Name:        "gremlin/request_count",
		Measure:     RequestCount,
		Aggregation: view.Count(),
		Description: "Count of Gremlin requests started",
	}

	ResponseCountView = &view.View{
		Name:        "gremlin/response_count",
		Measure:     RoundTripLatency,
		Aggregation: view.Count(),
		Description: "Count of responses received, by response status",
		TagKeys:     []tag.Key{StatusCode},
	}

	ResponseBytesView = &view.View{
		Name:        "gremlin/response_bytes",
		Measure:     ResponseBytes,
		Aggregation: DefaultSizeDistribution,
		Description: "Total number of bytes in response data",
	}

	RoundTripLatencyView = &view.View{
		Name:        "gremlin/roundtrip_latency",
		Measure:     RoundTripLatency,
		Aggregation: DefaultLatencyDistribution,
		Description: "End-to-end latency, by response code",
		TagKeys:     []tag.Key{StatusCode},
	}
)

// Views are the default views provided by this package.
func Views() []*view.View {
	return []*view.View{
		RequestCountView,
		ResponseCountView,
		ResponseBytesView,
		RoundTripLatencyView,
	}
}

// statsTransport is an gremlin.RoundTripper that collects stats for the outgoing requests.
type statsTransport struct {
	base gremlin.RoundTripper
}

func (t statsTransport) RoundTrip(ctx context.Context, req *gremlin.Request) (*gremlin.Response, error) {
	stats.Record(ctx, RequestCount.M(1))
	start := time.Now()
	rsp, err := t.base.RoundTrip(ctx, req)
	latency := float64(time.Since(start)) / float64(time.Millisecond)
	var (
		tags = make([]tag.Mutator, 1)
		ms   = []stats.Measurement{RoundTripLatency.M(latency)}
	)
	if err == nil {
		tags[0] = tag.Upsert(StatusCode, strconv.Itoa(rsp.Status.Code))
		ms = append(ms, ResponseBytes.M(int64(len(rsp.Result.Data))))
	} else {
		tags[0] = tag.Upsert(StatusCode, "error")
	}
	_ = stats.RecordWithTags(ctx, tags, ms...)
	return rsp, err
}
