package querycodec

import (
	"context"
	"net/http"
	"net/url"
	"testing"
)

func TestToolingQueryUsesNeutralQueryWithL1Aliases(t *testing.T) {
	var observed QueryRequest
	handler := mustHandler(t, Executor{
		Query: func(_ context.Context, request QueryRequest, _ int) (Result, error) {
			observed = request
			return Result{TotalSize: 1, Done: true, Records: []Record{{
				ObjectType: "_System_ValidationRule",
				Fields:     map[string]any{"Id": "0VR000000000001AAA", "Name": "BlockLead", "ObjectApiName": "Lead"},
			}}}, nil
		},
		History: unavailableHistory,
	})
	route, ok := Match("/services/data/v63.0/tooling/query")
	if !ok {
		t.Fatal("Tooling query route did not match")
	}
	response := handler.Handle(context.Background(), Request{Route: route, Query: url.Values{
		"q": {"SELECT Id, ValidationName, EntityDefinition.DeveloperName FROM ValidationRule WHERE ValidationName = 'BlockLead' AND EntityDefinition.DeveloperName = 'Lead' ORDER BY ValidationName DESC, EntityDefinition.DeveloperName ASC"},
	}})
	if response.Status != http.StatusOK {
		t.Fatalf("status=%d body=%#v", response.Status, response.Body)
	}
	if observed.ObjectAPIName != "_System_ValidationRule" {
		t.Fatalf("ObjectAPIName=%q", observed.ObjectAPIName)
	}
	wantFields := []string{"Id", "Name", "ObjectApiName"}
	for index, want := range wantFields {
		if observed.SelectFields[index] != want {
			t.Fatalf("SelectFields=%#v", observed.SelectFields)
		}
	}
	if observed.Filter.Kind != PredicateAnd || len(observed.Filter.Children) != 2 ||
		observed.Filter.Children[0].Left.Field != "Name" ||
		observed.Filter.Children[1].Left.Field != "ObjectApiName" {
		t.Fatalf("Filter=%#v", observed.Filter)
	}
	if observed.SortField != "Name" || len(observed.OrderBy) != 2 ||
		observed.OrderBy[0].Field != "Name" ||
		observed.OrderBy[1].Field != "ObjectApiName" {
		t.Fatalf("SortField=%q OrderBy=%#v", observed.SortField, observed.OrderBy)
	}
	want := `{"done":true,"records":[{"Id":"0VR000000000001AAA","Name":"BlockLead","ObjectApiName":"Lead","attributes":{"type":"ValidationRule","url":"/services/data/v63.0/tooling/sobjects/ValidationRule/0VR000000000001AAA"}}],"totalSize":1}`
	if got := marshalBody(t, response.Body); got != want {
		t.Fatalf("body=%s\nwant=%s", got, want)
	}
}

func TestToolingQueryLowersAggregateAndGroupByAliases(t *testing.T) {
	var observed QueryRequest
	handler := mustHandler(t, Executor{
		Query: func(_ context.Context, request QueryRequest, _ int) (Result, error) {
			observed = request
			return Result{TotalSize: 1, Done: true, Aggregate: true, Records: []Record{{
				Fields: map[string]any{"expr0": int64(1), "ObjectApiName": "Lead"},
			}}}, nil
		},
		History: unavailableHistory,
	})
	route, _ := Match("/services/data/v63.0/tooling/query")
	response := handler.Handle(context.Background(), Request{Route: route, Query: url.Values{
		"q": {"SELECT COUNT(ValidationName), EntityDefinition.DeveloperName FROM ValidationRule GROUP BY EntityDefinition.DeveloperName"},
	}})
	if response.Status != http.StatusOK {
		t.Fatalf("status=%d body=%#v", response.Status, response.Body)
	}
	if len(observed.SelectFields) != 2 || observed.SelectFields[0] != "COUNT(Name)" ||
		observed.SelectFields[1] != "ObjectApiName" || observed.GroupBy != "ObjectApiName" {
		t.Fatalf("SelectFields=%#v GroupBy=%q", observed.SelectFields, observed.GroupBy)
	}
}

func TestToolingQueryPreservesAggregateAliasAndLowersSemiJoin(t *testing.T) {
	var observed QueryRequest
	handler := mustHandler(t, Executor{
		Query: func(_ context.Context, request QueryRequest, _ int) (Result, error) {
			observed = request
			return Result{TotalSize: 0, Done: true, Aggregate: true}, nil
		},
		History: unavailableHistory,
	})
	response := handler.Handle(context.Background(), Request{
		Route: Route{Kind: RouteToolingQuery, Version: "v63.0"},
		Query: url.Values{"q": {"SELECT COUNT(ValidationName) AS ValidationName FROM ValidationRule WHERE ValidationName IN (SELECT ValidationName FROM ValidationRule)"}},
	})

	if response.Status != http.StatusOK {
		t.Fatalf("status=%d body=%#v", response.Status, response.Body)
	}
	if len(observed.SelectFields) != 1 || observed.SelectFields[0] != "COUNT(Name) ValidationName" {
		t.Fatalf("SelectFields=%#v", observed.SelectFields)
	}
	if observed.Filter.Kind != PredicateCorrelatedMembership || observed.Filter.CorrelatedMembership == nil {
		t.Fatalf("Filter=%#v", observed.Filter)
	}
	membership := observed.Filter.CorrelatedMembership
	if membership.OuterField != "Name" ||
		membership.InnerObjectAPIName != "_System_ValidationRule" ||
		membership.InnerSelectField != "Name" {
		t.Fatalf("membership=%#v", membership)
	}
}

func TestToolingQueryFailsClosedInsteadOfInventingQueryMoreRoute(t *testing.T) {
	handler := mustHandler(t, Executor{
		Query: func(_ context.Context, _ QueryRequest, _ int) (Result, error) {
			return Result{TotalSize: 2, Done: false, Records: []Record{{Fields: map[string]any{"Id": "0VR1"}}}}, nil
		},
		History: unavailableHistory,
	})
	route, _ := Match("/services/data/v63.0/tooling/query")
	response := handler.Handle(context.Background(), Request{Route: route, Query: url.Values{"q": {"SELECT Id FROM ValidationRule"}}})
	if response.Status != http.StatusBadRequest ||
		marshalBody(t, response.Body) != `[{"errorCode":"EXCEEDED_ID_LIMIT","message":"`+toolingQueryMoreUnsupportedMessage+`"}]` {
		t.Fatalf("status=%d body=%#v", response.Status, response.Body)
	}
}

func TestToolingQueryPreservesMissingQueryWireError(t *testing.T) {
	handler := mustHandler(t, Executor{
		Query: func(_ context.Context, _ QueryRequest, _ int) (Result, error) {
			t.Fatal("missing Tooling query reached Runtime")
			return Result{}, nil
		},
		History: unavailableHistory,
	})
	route, _ := Match("/services/data/v63.0/tooling/query")
	response := handler.Handle(context.Background(), Request{Route: route})
	if response.Status != http.StatusBadRequest {
		t.Fatalf("status=%d body=%#v", response.Status, response.Body)
	}
	want := `[{"errorCode":"INVALID_QUERY","message":"q parameter is required"}]`
	if got := marshalBody(t, response.Body); got != want {
		t.Fatalf("body=%s want=%s", got, want)
	}
}
