package httptransport

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

	"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 produces VulnerabilityReports.
type VulnerabilityReportHandler struct {
	Matcher matcher.Service
	Indexer indexer.Service
	Cache   time.Duration
}

var _ http.Handler = (*VulnerabilityReportHandler)(nil)

// ServeHTTP implements http.Handler.
func (h *VulnerabilityReportHandler) ServeHTTP(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 := h.Matcher.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 := h.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 := h.Matcher.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")
	setCacheControl(w, h.Cache)

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