package sfcrmappservice

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

	authoring "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
)

const layoutTestRevision = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

func layoutTestDocument() semantic.LayoutDocument {
	return authoring.LayoutDocument{
		LayoutID: "layout-a", ObjectID: "Account", Name: "Main", Kind: "Detail",
		CompactFields: []string{}, Tabs: []string{}, Sections: []authoring.LayoutSection{},
		RelatedLists: []authoring.RelatedList{}, RecordPanels: []authoring.RecordPanel{},
		HeaderActions: authoring.ActionPlacement{Actions: []authoring.LayoutAction{}},
		QuickActions:  authoring.ActionPlacement{Actions: []authoring.LayoutAction{}},
		RecordStates:  []authoring.RecordStateRule{}, PrimaryHeaderActions: []string{},
	}
}

func layoutTestReceipts() semantic.Receipts {
	return semantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName}
}

func layoutTestService() *Service {
	return &Service{
		requestTimeout: time.Second, maxRequestBytes: 3 * 1024 * 1024,
		layoutAuthoring: &runtimeLayoutAuthoringClient{},
	}
}

func layoutTestRequest(service *Service, action, body, bearer string) *httptest.ResponseRecorder {
	request := httptest.NewRequest(http.MethodPost, LayoutAuthoringPath+action, strings.NewReader(body))
	if bearer != "" {
		request.Header.Set("Authorization", "Bearer "+bearer)
	}
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	return response
}

func TestLayoutAuthoringUsesCurrentWorkspaceAndSynchronousMutation(t *testing.T) {
	service := layoutTestService()
	service.layoutAuthoring.workspace = func(_ context.Context, bearer string, request semantic.WorkspaceRequest) (semantic.WorkspaceResponse, error) {
		if bearer != "user" || request.ObjectID != "Account" {
			t.Fatalf("workspace request = %#v bearer=%q", request, bearer)
		}
		return semantic.WorkspaceResponse{ObjectID: "Account", LayoutRevision: layoutTestRevision, Layouts: []semantic.LayoutSummary{}, Assignments: []semantic.LayoutAssignment{}, Receipts: layoutTestReceipts()}, nil
	}
	service.layoutAuthoring.stored = func(_ context.Context, bearer string, request semantic.StoredLayoutRequest) (semantic.StoredLayoutResponse, error) {
		if bearer != "user" || request.ObjectID != "Account" || request.LayoutID != "layout-a" {
			t.Fatalf("stored request = %#v bearer=%q", request, bearer)
		}
		return semantic.StoredLayoutResponse{LayoutID: "layout-a", ObjectID: "Account", LayoutRevision: layoutTestRevision, Document: layoutTestDocument(), OrderedListFields: []string{"Name"}, RequiredCreateFields: []string{"Name"}, Receipts: layoutTestReceipts()}, nil
	}
	service.layoutAuthoring.mutate = func(_ context.Context, bearer string, request semantic.Request) (semantic.Response, error) {
		if bearer != "user" || request.ExpectedLayoutRevision != layoutTestRevision || request.Operation.SetDefault == nil {
			t.Fatalf("mutation request = %#v bearer=%q", request, bearer)
		}
		layoutID := "layout-a"
		return semantic.Response{Changed: false, LayoutRevision: layoutTestRevision, LayoutID: &layoutID, Receipts: layoutTestReceipts()}, nil
	}

	for action, test := range map[string][2]string{
		"workspace": {`{"objectId":"Account"}`, `"layoutRevision":"` + layoutTestRevision + `"`},
		"stored":    {`{"objectId":"Account","layoutId":"layout-a"}`, `"requiredCreateFields":["Name"]`},
		"mutate":    {`{"objectId":"Account","expectedLayoutRevision":"` + layoutTestRevision + `","operation":{"set_default":{"layout_id":"layout-a"}}}`, `"changed":false`},
	} {
		response := layoutTestRequest(service, action, test[0], "user")
		if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), test[1]) {
			t.Fatalf("%s = %d %s", action, response.Code, response.Body.String())
		}
	}
}

func TestLayoutAuthoringHTTPNormalizesEmptyWireCollections(t *testing.T) {
	service := layoutTestService()
	service.layoutAuthoring.workspace = func(
		context.Context, string, semantic.WorkspaceRequest,
	) (semantic.WorkspaceResponse, error) {
		return semantic.WorkspaceResponse{
			ObjectID: "Account", LayoutRevision: layoutTestRevision,
			Layouts: nil, Assignments: nil, Receipts: layoutTestReceipts(),
		}, nil
	}
	workspace := layoutTestRequest(service, "workspace", `{"objectId":"Account"}`, "user")
	if workspace.Code != http.StatusOK ||
		!strings.Contains(workspace.Body.String(), `"layouts":[]`) ||
		!strings.Contains(workspace.Body.String(), `"assignments":[]`) {
		t.Fatalf("empty workspace = %d %s", workspace.Code, workspace.Body.String())
	}

	service.layoutAuthoring.stored = func(
		context.Context, string, semantic.StoredLayoutRequest,
	) (semantic.StoredLayoutResponse, error) {
		return semantic.StoredLayoutResponse{
			LayoutID: "layout-a", ObjectID: "Account", LayoutRevision: layoutTestRevision,
			Document: layoutTestDocument(), OrderedListFields: []string{}, RequiredCreateFields: []string{},
			Receipts: layoutTestReceipts(),
		}, nil
	}
	stored := layoutTestRequest(
		service, "stored", `{"objectId":"Account","layoutId":"layout-a"}`, "user",
	)
	if stored.Code != http.StatusOK ||
		!strings.Contains(stored.Body.String(), `"orderedListFields":[]`) ||
		!strings.Contains(stored.Body.String(), `"requiredCreateFields":[]`) {
		t.Fatalf("empty stored layout = %d %s", stored.Code, stored.Body.String())
	}
}

func TestLayoutAuthoringRejectsRetiredCommandRouteAndInvalidAdmission(t *testing.T) {
	service := layoutTestService()
	if response := layoutTestRequest(service, "submit", `{}`, "user"); response.Code != http.StatusNotFound {
		t.Fatalf("retired command route = %d", response.Code)
	}
	if response := layoutTestRequest(service, "workspace", `{"objectId":"Account"}`, ""); response.Code != http.StatusUnauthorized {
		t.Fatalf("unauthorized workspace = %d", response.Code)
	}
	if response := layoutTestRequest(service, "workspace", `{"objectId":"Account","extra":true}`, "user"); response.Code != http.StatusBadRequest {
		t.Fatalf("unknown field = %d", response.Code)
	}
}
