package metrics

import (
	"context"
	"reflect"

	"go.opencensus.io/tag"

	"github.com/filecoin-project/lotus/api"
	"github.com/filecoin-project/lotus/api/apistruct"
)

func MetricedStorMinerAPI(a api.StorageMiner) api.StorageMiner {
	var out apistruct.StorageMinerStruct
	proxy(a, &out.Internal)
	proxy(a, &out.CommonStruct.Internal)
	return &out
}

func MetricedFullAPI(a api.FullNode) api.FullNode {
	var out apistruct.FullNodeStruct
	proxy(a, &out.Internal)
	proxy(a, &out.CommonStruct.Internal)
	return &out
}

func MetricedWorkerAPI(a api.WorkerAPI) api.WorkerAPI {
	var out apistruct.WorkerStruct
	proxy(a, &out.Internal)
	return &out
}

func MetricedWalletAPI(a api.WalletAPI) api.WalletAPI {
	var out apistruct.WalletStruct
	proxy(a, &out.Internal)
	return &out
}

func MetricedGatewayAPI(a api.GatewayAPI) api.GatewayAPI {
	var out apistruct.GatewayStruct
	proxy(a, &out.Internal)
	return &out
}

func proxy(in interface{}, out interface{}) {
	rint := reflect.ValueOf(out).Elem()
	ra := reflect.ValueOf(in)

	for f := 0; f < rint.NumField(); f++ {
		field := rint.Type().Field(f)
		fn := ra.MethodByName(field.Name)

		rint.Field(f).Set(reflect.MakeFunc(field.Type, func(args []reflect.Value) (results []reflect.Value) {
			ctx := args[0].Interface().(context.Context)
			// upsert function name into context
			ctx, _ = tag.New(ctx, tag.Upsert(Endpoint, field.Name))
			stop := Timer(ctx, APIRequestDuration)
			defer stop()
			// pass tagged ctx back into function call
			args[0] = reflect.ValueOf(ctx)
			return fn.Call(args)
		}))

	}
}