package httptransport

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptrace"
	"path"

	"github.com/quay/claircore"
	je "github.com/quay/claircore/pkg/jsonerr"
	oteltrace "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"

	"github.com/quay/clair/v4/indexer"
	"github.com/quay/clair/v4/internal/codec"
	"github.com/quay/clair/v4/matcher"
)

// VulnerabilityReportHandler utilizes a Service to serialize
// and return a claircore.VulnerabilityReport
func VulnerabilityReportHandler(service matcher.Service, indexer indexer.Service) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			resp := &je.Response{
				Code:    "method-not-allowed",
				Message: "endpoint only allows GET",
			}
			je.Error(w, resp, http.StatusMethodNotAllowed)
			return
		}
		ctx, done := context.WithCancel(r.Context())
		defer done()
		ctx = httptrace.WithClientTrace(ctx, oteltrace.NewClientTrace(ctx))

		manifestStr := path.Base(r.URL.Path)
		if manifestStr == "" {
			resp := &je.Response{
				Code:    "bad-request",
				Message: "malformed path. provide a single manifest hash",
			}
			je.Error(w, resp, http.StatusBadRequest)
			return
		}
		manifest, err := claircore.ParseDigest(manifestStr)
		if err != nil {
			resp := &je.Response{
				Code:    "bad-request",
				Message: "malformed path: " + err.Error(),
			}
			je.Error(w, resp, http.StatusBadRequest)
			return
		}

		initd, err := service.Initialized(ctx)
		if err != nil {
			resp := &je.Response{
				Code:    "internal-server-error",
				Message: err.Error(),
			}
			je.Error(w, resp, http.StatusInternalServerError)
			return
		}
		if !initd {
			w.WriteHeader(http.StatusAccepted)
			return
		}

		indexReport, ok, err := indexer.IndexReport(ctx, manifest)
		// check err first
		if err != nil {
			resp := &je.Response{
				Code:    "internal-server-error",
				Message: fmt.Sprintf("experienced a server side error: %v", err),
			}
			je.Error(w, resp, http.StatusInternalServerError)
			return
		}
		// now check bool only after confirming no err
		if !ok {
			resp := &je.Response{
				Code:    "not-found",
				Message: fmt.Sprintf("index report for manifest %q not found", manifest.String()),
			}
			je.Error(w, resp, http.StatusNotFound)
			return

		}

		vulnReport, err := service.Scan(ctx, indexReport)
		if err != nil {
			resp := &je.Response{
				Code:    "match-error",
				Message: fmt.Sprintf("failed to start scan: %v", err),
			}
			je.Error(w, resp, http.StatusInternalServerError)
			return
		}

		w.Header().Set("content-type", "application/json")

		defer writerError(w, &err)()
		enc := codec.GetEncoder(w)
		defer codec.PutEncoder(enc)
		err = enc.Encode(vulnReport)
	}
}
