package server

import (
	"context"
	"errors"
	"net/http"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/gitlab"
	gapi "gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/gitlab/api"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modserver"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/runner/rpc"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/httpz"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

const (
	requestIDMetadataKey = "x-request-id" // lowercase version of X-Request-ID
)

type server struct {
	rpc.UnsafeJobRouterServer
	gitlabClient gitlab.ClientInterface
}

func (s *server) GetJob(ctx context.Context, req *rpc.GetJobRequest) (*rpc.GetJobResponse, error) {
	// NOTE: we don't do any authentication here,
	// we just pass the request to GitLab along with the Runner-supplied authentication token.
	rpcAPI := modserver.AgentRPCAPIFromContext(ctx)

	var opts []gitlab.DoOption

	reqID := metadata.ValueFromIncomingContext(ctx, requestIDMetadataKey)
	if len(reqID) > 0 {
		opts = append(opts,
			gitlab.WithHeader(http.Header{
				httpz.RequestIDHeader: reqID,
			}),
		)
	}
	respBody, respID, err := gapi.GetRawCIJobs( // handles HTTP 201 and 204
		ctx,
		s.gitlabClient,
		rpcAPI.AgentTokenWithType().Token,
		req.JobRequest,
		opts...,
	)
	if err != nil {
		code := codes.Unavailable
		var ce *gitlab.ClientError
		if errors.As(err, &ce) {
			switch ce.StatusCode {
			case http.StatusForbidden:
				code = codes.PermissionDenied
			case http.StatusTooManyRequests:
				code = codes.ResourceExhausted
			case http.StatusConflict, http.StatusUnprocessableEntity:
				code = codes.FailedPrecondition
			}
		}
		return nil, status.Error(code, err.Error())
	}
	defer respBody.Free()

	if respID != "" {
		err = grpc.SetHeader(ctx, metadata.Pairs(
			requestIDMetadataKey, respID,
		))
		if err != nil { // this should never happen
			rpcAPI.HandleProcessingError(rpcAPI.Log(), "Failed to set response metadata", err)
			// continue anyway
		}
	}
	return &rpc.GetJobResponse{
		JobResponse: respBody.Materialize(),
	}, nil
}
