package sfcrmappservice

import (
	"context"
	"fmt"
	permissions "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
	historysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	"net/http"
	"reflect"
	"strings"
	"testing"
)

func TestHistorySchemaIsSharedByDescribeCandidatesAndAuthoring(t *testing.T) {
	for _, test := range []struct{ parent, object, lookup string }{
		{"Account", "AccountHistory", "AccountId"}, {"Opportunity", "OpportunityFieldHistory", "OpportunityId"}, {"Delivery__c", "Delivery__History", "ParentId"},
	} {
		t.Run(test.parent, func(t *testing.T) {
			table := "standard_object"
			parent := searchObjectSpec{APIName: test.parent, Label: test.parent, TableType: &table, Fields: []searchFieldSpec{{APIName: "Name", Type: "string"}}}
			specs := map[string]searchObjectSpec{strings.ToLower(test.parent): parent}
			if resolved, found := virtualHistoryParent(test.object, specs); !found || resolved.APIName != test.parent {
				t.Fatal("history did not resolve to current parent")
			}
			projections := historyLayoutProjections(test.parent)
			if len(projections) != 1 || projections[0].ObjectID != test.object || projections[0].Columns[test.lookup] != historysemantic.HistoryColumnParentID {
				t.Fatalf("projection = %#v", projections)
			}
			if err := semantic.ValidateHistoryRelatedListProjections(test.parent, projections); err != nil {
				t.Fatal(err)
			}
			fields := historyObjectFields(test.object, parent, permissions.Response{})
			names := make(map[string]bool)
			for _, field := range fields {
				names[field["name"].(string)] = true
			}
			if len(names) != 8 {
				t.Fatalf("Describe fields = %#v", fields)
			}
			for name := range projections[0].Columns {
				if !names[name] {
					t.Fatalf("authoring accepts non-Describe field %q", name)
				}
			}
			candidate, found := historyRelatedListCandidate(parent, specs)
			if !found || candidate.ID != test.object+"-"+test.lookup || !candidate.IsSystem || !reflect.DeepEqual(candidate.Fields, []string{"Field", "OldValue", "NewValue", "CreatedDate", "CreatedById"}) {
				t.Fatalf("candidate = %#v found=%v", candidate, found)
			}
			specs[strings.ToLower(test.object)] = searchObjectSpec{APIName: test.object, TableType: &table}
			if _, found := historyRelatedListCandidate(parent, specs); found {
				t.Fatal("virtual history shadowed a physical object")
			}
		})
	}
	for _, parent := range []string{"Task", "Event", "OpportunityHistory", "AccountHistory"} {
		if projections := historyLayoutProjections(parent); len(projections) != 0 {
			t.Fatalf("unsupported parent %s has projections %#v", parent, projections)
		}
	}
}

func TestLayoutMutationAssemblesHistorySourceWithoutCatalogOrCallerClaims(t *testing.T) {
	service := layoutTestService()
	calls := 0
	service.layoutAuthoring.mutate = func(_ context.Context, _ string, request semantic.Request) (semantic.Response, error) {
		calls++
		if len(request.HistoryRelatedLists) != 1 || request.HistoryRelatedLists[0].ObjectID != "AccountHistory" {
			t.Fatalf("missing history source: %#v", request)
		}
		id := "layout-a"
		return semantic.Response{Changed: false, LayoutRevision: layoutTestRevision, LayoutID: &id, Receipts: layoutTestReceipts()}, nil
	}
	body := fmt.Sprintf(`{"objectId":"Account","expectedLayoutRevision":%q,"operation":{"set_default":{"layout_id":"layout-a"}}}`, layoutTestRevision)
	if response := layoutTestRequest(service, "mutate", body, "user"); response.Code != http.StatusOK {
		t.Fatalf("mutation HTTP=%d %s", response.Code, response.Body.String())
	}
	injected := strings.TrimSuffix(body, "}") + `,"history_related_lists":[{"object_id":"Secret"}]}`
	if response := layoutTestRequest(service, "mutate", injected, "user"); response.Code != http.StatusBadRequest || calls != 1 {
		t.Fatalf("caller supplied source reached Runtime: HTTP=%d calls=%d", response.Code, calls)
	}
}
