package plugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
	"k8s.io/component-base/tracing"
	"k8s.io/klog/v2"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/datasource/v0alpha1"
	aggregationv0alpha1 "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1"
	"github.com/grafana/grafana/pkg/aggregator/apiserver/util"
	grafanasemconv "github.com/grafana/grafana/pkg/semconv"
)

func (h *PluginHandler) QueryDataHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		ctx := req.Context()
		span := tracing.SpanFromContext(ctx)
		span.AddEvent("QueryDataHandler")
		responder := &util.Responder{ResponseWriter: w}
		dqr := data.QueryDataRequest{}
		if err := json.NewDecoder(req.Body).Decode(&dqr); err != nil {
			responder.Error(w, req, err)
			return
		}

		dsUID := req.PathValue("uid")
		dsType := h.dataplaneService.Spec.PluginID

		queries, dsRef, err := data.ToDataSourceQueries(dqr)
		if err != nil {
			responder.Error(w, req, err)
			return
		}

		// this shouldn't happen, but just in case
		if dsRef == nil {
			responder.Error(w, req, fmt.Errorf("missing datasource reference"))
			return
		}

		// the datasource type in the query body should match the plugin ID
		if dsRef.Type != dsType {
			err := errors.New("invalid datasource type")
			klog.ErrorS(err, err.Error(), "dsType", dsType, "refType", dsRef.Type)
			responder.Error(w, req, err)
			return
		}

		// the UID in the query body should match the UID in the URL
		if dsRef.UID != dsUID {
			err := errors.New("invalid datasource UID")
			klog.ErrorS(err, err.Error(), "path", dsUID, "refUID", dsRef.UID)
			responder.Error(w, req, err)
			return
		}

		span.AddEvent("GetPluginContext",
			grafanasemconv.GrafanaDatasourceUid(dsRef.UID),
			grafanasemconv.GrafanaDatasourceType(dsRef.Type),
		)
		pluginContext, err := h.pluginContextProvider.GetPluginContext(ctx, dsRef.Type, dsRef.UID)
		if err != nil {
			responder.Error(w, req, fmt.Errorf("unable to get plugin context: %w", err))
			return
		}

		ctx = backend.WithGrafanaConfig(ctx, pluginContext.GrafanaConfig)
		span.AddEvent("QueryData start", grafanasemconv.GrafanaDatasourceRequestQueryCount(len(queries)))
		rsp, err := h.client.QueryData(ctx, &backend.QueryDataRequest{
			Queries:       queries,
			PluginContext: pluginContext,
		})
		if err != nil {
			responder.Error(w, req, err)
			return
		}
		statusCode := 200
		span.AddEvent("QueryData end", semconv.HTTPResponseStatusCode(statusCode))
		responder.Object(statusCode,
			&aggregationv0alpha1.QueryDataResponse{QueryDataResponse: *rsp},
		)
	}
}
