package sfcrmappservice

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	querycodec "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/querycodec"
	catalog "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	query "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
)

func mismatchQueryAuthority(r query.Receipts, axis string) query.Receipts {
	switch axis {
	case "schema":
		r.SchemaGeneration++
	case "engine":
		r.EngineGeneration++
	case "catalog":
		r.CatalogRevision++
	case "security":
		r.SecurityEpoch++
	}
	return r
}

func TestDateAuthorityFailuresThroughHTTP(t *testing.T) {
	for _, phase := range []string{"calendar", "records"} {
		for _, axis := range []string{"schema", "engine", "catalog", "security", "matching"} {
			t.Run(phase+"/"+axis, func(t *testing.T) {
				view := searchTestView(t, []searchObjectSpec{{APIName: "Item", Label: "Item", Fields: []searchFieldSpec{{APIName: "Due", Label: "Due", Type: "date"}}}})
				calls := 0
				service, err := NewService(ServiceConfig{
					RuntimeEvaluate:             (&fakeRuntime{}).Evaluate,
					RuntimeGetAppMetadataViewV3: func(context.Context, catalog.GetRequest) (catalog.AppMetadataViewV3, error) { return view, nil },
					RuntimeQuery: func(_ context.Context, _ string, r query.Request) (query.Response, error) {
						calls++
						receipts := view.Receipts
						receipts.CapabilityVersion = query.CapabilityName
						if (phase == "calendar" && r.ObjectID == "User") || (phase == "records" && r.ObjectID == "Item") {
							receipts = mismatchQueryAuthority(receipts, axis)
						}
						response := query.Response{Done: true, Receipts: receipts}
						if r.ObjectID == "User" {
							zone := "UTC"
							response.TotalSize = 1
							response.Records = []query.Record{{ObjectID: "User", Fields: []query.Field{{Name: "TimeZoneSidKey", Value: query.Value{Kind: query.ValueString, String: &zone}}}}}
						}
						return response, nil
					},
					RuntimeHistoryQuery: func(context.Context, string, query.HistoryRequest) (query.HistoryResponse, error) {
						t.Fatal("unexpected history")
						return query.HistoryResponse{}, nil
					},
					RuntimeReady: func() bool { return true }, RuntimeMetrics: func() runtimegrpc.RuntimeClientMetrics { return runtimegrpc.RuntimeClientMetrics{} }, RequestTimeout: 5 * time.Second, MaxRequestBytes: 65536,
				})
				if err != nil {
					t.Fatal(err)
				}
				req := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/query?q="+url.QueryEscape("SELECT Id FROM Item WHERE Due = TODAY"), nil)
				req.Header.Set("Authorization", "Bearer test-token")
				out := httptest.NewRecorder()
				service.ServeHTTP(out, req)
				want, wantCalls := 503, 2
				if phase == "calendar" {
					wantCalls = 1
				}
				if axis == "matching" {
					want, wantCalls = 200, 2
				}
				if out.Code != want || calls != wantCalls {
					t.Fatalf("calls=%d HTTP=%d want=%d body=%s", calls, out.Code, want, out.Body.String())
				}
				if want == 503 && !strings.Contains(out.Body.String(), `"errorCode":"UNKNOWN_EXCEPTION"`) {
					t.Fatalf("wrong failure body: %s", out.Body.String())
				}
			})
		}
	}
}

func TestHistoryAuthorityFailureRemainsUnavailable(t *testing.T) {
	view := searchTestView(t, []searchObjectSpec{{APIName: "Item", Label: "Item", Fields: []searchFieldSpec{{APIName: "Due", Label: "Due", Type: "date"}}}})
	for _, axis := range []string{"schema", "engine", "catalog", "security"} {
		t.Run(axis, func(t *testing.T) {
			calls := 0
			adapter := &RuntimeQueryAdapter{dates: &runtimeQueryDates{}, history: func(context.Context, string, query.HistoryRequest) (query.HistoryResponse, error) {
				calls++
				r := mismatchQueryAuthority(view.Receipts, axis)
				r.CapabilityVersion = query.CapabilityName
				return query.HistoryResponse{Done: true, Receipts: r}, nil
			}}
			ctx := context.WithValue(queryDateTestContext(t), queryDateMetadataKey{}, view)
			_, err := adapter.queryHistory(ctx, querycodec.HistoryRequest{ParentObjectAPIName: "Item", SelectFields: []string{"Id"}, Limit: 1})
			var failure *querycodec.Failure
			if calls != 1 || !errors.As(err, &failure) || failure.Category != querycodec.FailureUnavailable {
				t.Fatalf("calls=%d error=%v", calls, err)
			}
		})
	}
	// A wrong app is another early branch in the shared authority validator.
	view.AppID = "other"
	err := validateSearchMetadataAuthority(context.Background(), nil, view, view.Receipts)
	var failure *query.Failure
	if !errors.As(err, &failure) || failure.Category != query.FailureUnavailable {
		t.Fatalf("wrong-app failure=%v", err)
	}
}
