package loki

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"time"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
	"github.com/grafana/grafana/pkg/promlib/models"
	"github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery"
	"github.com/grafana/loki/v3/pkg/logql/syntax"
	"github.com/prometheus/prometheus/promql/parser"
)

// SuggestionRequest is the request body for the GetSuggestions resource.
type SuggestionRequest struct {
	// LabelName, if provided, will result in label values being returned for the given label name.
	LabelName string `json:"labelName"`

	Query string `json:"query"`

	Scopes       []scope.ScopeFilter `json:"scopes"`
	AdhocFilters []scope.ScopeFilter `json:"adhocFilters"`

	// Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp)
	Start string `json:"start"`
	End   string `json:"end"`
}

// GetSuggestions returns label names or label values for the given queries and scopes.
func GetSuggestions(ctx context.Context, lokiAPI *LokiAPI, req *backend.CallResourceRequest) (RawLokiResponse, error) {
	sugReq := SuggestionRequest{}
	err := json.Unmarshal(req.Body, &sugReq)
	if err != nil {
		return RawLokiResponse{}, fmt.Errorf("error unmarshalling suggestion request: %v", err)
	}

	values := url.Values{}
	if sugReq.Query != "" {
		// the query is used to find label/labelvalues the duration and interval does not matter.
		// If the user want to filter values based on time it should used the `start` and `end` fields
		interpolatedQuery := interpolateVariables(sugReq.Query, time.Minute, time.Minute, dataquery.LokiQueryTypeRange, time.Minute)

		if len(sugReq.Scopes) > 0 {
			rewrittenQuery, err := ApplyScopes(interpolatedQuery, sugReq.Scopes)
			if err == nil {
				values.Add("query", rewrittenQuery)
			} else {
				values.Add("query", interpolatedQuery)
			}
		}
	} else if len(sugReq.Scopes) > 0 {
		matchers, err := models.FiltersToMatchers(sugReq.Scopes, sugReq.AdhocFilters)
		if err != nil {
			return RawLokiResponse{}, fmt.Errorf("error converting filters to matchers: %v", err)
		}
		vs := parser.VectorSelector{LabelMatchers: matchers}
		values.Add("query", vs.String())
	}

	if sugReq.Start != "" {
		values.Add("start", sugReq.Start)
	}

	if sugReq.End != "" {
		values.Add("end", sugReq.End)
	}

	var path string
	if sugReq.LabelName != "" {
		path = "/loki/api/v1/label/" + url.QueryEscape(sugReq.LabelName) + "/values?" + values.Encode()
	} else {
		path = "/loki/api/v1/labels?" + values.Encode()
	}

	return lokiAPI.RawQuery(ctx, path)
}

// ApplyScopes applies the given scope filters to the given raw expression.
func ApplyScopes(rawExpr string, scopeFilters []scope.ScopeFilter) (string, error) {
	if len(scopeFilters) == 0 {
		return rawExpr, nil
	}

	scopeMatchers, err := models.FiltersToMatchers(scopeFilters, nil)
	if err != nil {
		return "", fmt.Errorf("failed to convert filters to matchers: %w", err)
	}

	// Need WithoutValidation to allow empty `{}` expressions
	syntaxTree, err := syntax.ParseExprWithoutValidation(rawExpr)
	if err != nil {
		return "", fmt.Errorf("failed to parse raw expression: %w", err)
	}

	syntaxTree.Walk(func(e syntax.Expr) {
		switch e := e.(type) {
		case *syntax.MatchersExpr:
			// TODO: Key Collisions?
			e.Mts = append(e.Mts, scopeMatchers...)
		default:
		}
	})
	return syntaxTree.String(), nil
}
