package metric

import (
	xnetcontext "golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	"code.justin.tv/dta/rockpaperscissors/internal/metrics"
	pb "code.justin.tv/dta/rockpaperscissors/proto"
)

var (
	errf = grpc.Errorf

	// ErrMetricNotFound is returned when the given metric doesn't exist.
	ErrMetricNotFound = errf(codes.NotFound, "Metric not found.")

	// ErrMetricIDNotSet is returned when metric_id isn't set in GetMetric request.
	ErrMetricIDNotSet = errf(
		codes.InvalidArgument, "Required field metric_id wasn't set.")
	// ErrTimerangeStartNotSet is returned when timerange start isn't set in GetMetric request.
	ErrTimerangeStartNotSet = errf(
		codes.InvalidArgument, "Required field start wasn't set.")
	// ErrTimerangeEndNotSet is returned when timerange end isn't set in GetMetric request.
	ErrTimerangeEndNotSet = errf(
		codes.InvalidArgument, "Required field end wasn't set.")
	// ErrBucketSizeNotSet is returned when bucket_size isn't set in GetMetric request.
	ErrBucketSizeNotSet = errf(
		codes.InvalidArgument, "Required field bucket_size (or bucket_size_name) wasn't set.")
	// ErrSelectorNotSet is returned when a selector isn't set in GetMetric request.
	ErrSelectorNotSet = errf(
		codes.InvalidArgument, "One of project_id, developer, team, or org must be set.")
)

// Server is a gRPC service server implementing metricServiceServer.
type Server struct {
	dispatcher metrics.DispatcherInterface
}

// NewServer constructs a metric gRPC Server.
func NewServer(dispatcher metrics.DispatcherInterface) *Server {
	return &Server{
		dispatcher: dispatcher,
	}
}

// ListMetrics returns a list of available metrics.
func (s *Server) ListMetrics(ctx xnetcontext.Context, req *pb.ListMetricsRequest) (*pb.ListMetricsResponse, error) {
	return &pb.ListMetricsResponse{
		Metrics: s.dispatcher.List(),
	}, nil
}

// GetMetricInfo to get information about specific metrics.
func (s *Server) GetMetricInfo(ctx xnetcontext.Context, req *pb.GetMetricInfoRequest) (*pb.GetMetricInfoResponse, error) {
	info := s.dispatcher.Get(req.MetricId)
	if info == nil {
		return nil, ErrMetricNotFound
	}
	return &pb.GetMetricInfoResponse{Metric: info}, nil
}

// GetMetric gets a timeseries about a specific metric.
func (s *Server) GetMetric(ctx xnetcontext.Context, req *pb.GetMetricRequest) (*pb.GetMetricResponse, error) {
	if len(req.MetricId) == 0 {
		return nil, ErrMetricIDNotSet
	}

	if req.Timerange.Start.Seconds == 0 {
		return nil, ErrTimerangeStartNotSet
	}
	if req.Timerange.End.Seconds == 0 {
		return nil, ErrTimerangeEndNotSet
	}

	// grpc-gateway doesn't yet accept protobuf enums in paths so we allow setting
	// bucket_size_name to the enum name as a string and convert it back here so
	// calculators don't need to deal with it.
	if len(req.BucketSizeName) > 0 {
		req.BucketSize = pb.GetMetricRequest_BucketSize(
			pb.GetMetricRequest_BucketSize_value[req.BucketSizeName])
	}
	if req.BucketSize == pb.GetMetricRequest_UNSET {
		return nil, ErrBucketSizeNotSet
	}

	if len(req.ProjectId) == 0 && len(req.Developer) == 0 && len(req.Team) == 0 && len(req.Org) == 0 {
		return nil, ErrSelectorNotSet
	}

	return s.dispatcher.Calculate(ctx, req)
}
