package metrics

import (
	"context"

	structpb "github.com/golang/protobuf/ptypes/struct"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

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

func init() {
	info := &pb.MetricInfo{
		MetricId:        "has_test_results",
		MetricName:      "Has Test Results",
		Description:     "Is this project collecting test results in Jenkins?",
		ValidForProject: true,
		KeyMetric:       false,
	}
	Registry().Register(info, NewHasTestResultsCalculator)
}

// HasTestResultsCalculator to calculate a true/false on whether a project is collecting test results.
type HasTestResultsCalculator struct {
	ProjectMetadataServer pb.ProjectMetadataServiceServer
	EventServer           pb.EventServiceServer
}

// NewHasTestResultsCalculator factory for HasTestResultsCalculator structs.
func NewHasTestResultsCalculator(projectMetadataServer pb.ProjectMetadataServiceServer, eventServer pb.EventServiceServer) (Calculator, error) {
	return &HasTestResultsCalculator{
		ProjectMetadataServer: projectMetadataServer,
		EventServer:           eventServer,
	}, nil
}

func (p *HasTestResultsCalculator) calculateEntry(ctx context.Context, req *pb.GetMetricRequest, entry *pb.GetMetricResponse_TimeSeriesEntry, jenkinsJobs []string) error {
	hasTestResults := false

	for _, jenkinsJob := range jenkinsJobs {
		_, err := p.EventServer.QueryEvents(ctx, &pb.QueryEventsRequest{
			Timerange: entry.Timerange,
			Type:      "JenkinsTestResults",
			Filters: []*pb.QueryEventsRequest_AttributeFilter{
				&pb.QueryEventsRequest_AttributeFilter{
					Key:   "jenkins_job_name",
					Value: jenkinsJob,
				},
			},
		})
		if err != nil {
			if grpc.Code(err) == codes.NotFound {
				continue
			} else {
				return err
			}
		}
		hasTestResults = true
		break
	}

	entry.Value = &structpb.Value{
		Kind: &structpb.Value_BoolValue{
			BoolValue: hasTestResults,
		},
	}

	return nil
}

// Calculate the time series and fill out the response.
func (p *HasTestResultsCalculator) Calculate(ctx context.Context, req *pb.GetMetricRequest, resp *pb.GetMetricResponse) error {
	timeSeries, err := makeTimeSeries(req.Timerange, req.BucketSize, req.IanaTimeZone)
	if err != nil {
		return errf(codes.InvalidArgument,
			"Found inappropriate time range: %v", err)
	}
	resp.TimeSeries = timeSeries
	resp.TimeSeriesUnits = "T/F"

	jenkinsJobs, err := getJenkinsJobsForProject(ctx, p.ProjectMetadataServer, req.ProjectId)
	if err != nil {
		return err
	}
	if len(jenkinsJobs) == 0 {
		return nil
	}

	sem := make(chan error, len(timeSeries))
	for _, bucket := range timeSeries {
		go func(ctx context.Context, req *pb.GetMetricRequest, entry *pb.GetMetricResponse_TimeSeriesEntry, jenkinsJobs []string) {
			sem <- p.calculateEntry(ctx, req, entry, jenkinsJobs)
		}(ctx, req, bucket, jenkinsJobs)
	}
	for i := 0; i < len(timeSeries); i++ {
		err := <-sem
		if err != nil {
			// TODO: cancel the context to stop any still-processing work?
			return err
		}
	}

	return nil
}
