package mapping

import (
	"errors"
	"fmt"
	historysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	"reflect"
	"testing"

	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	v1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	v2 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/proto"
)

const (
	layoutAuthoringV2RevisionA = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	layoutAuthoringV2RevisionB = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
)

func TestLayoutAuthoringV2WireRoundTripsSynchronousReadsAndMutation(t *testing.T) {
	receipts := v2.Receipts{ProtocolVersion: "1.0", SecurityEpoch: 7, CapabilityVersion: v2.CapabilityName}
	workspaceRequest := v2.WorkspaceRequest{ObjectID: "Account"}
	wireWorkspaceRequest, err := ToWireLayoutAuthoringV2ReadWorkspaceRequest(layoutAuthoringTestContext(t), workspaceRequest)
	if err != nil {
		t.Fatal(err)
	}
	if roundTrip, err := FromWireLayoutAuthoringV2ReadWorkspaceRequest(layoutAuthoringTestContext(t), wireWorkspaceRequest); err != nil || !reflect.DeepEqual(roundTrip, workspaceRequest) {
		t.Fatalf("workspace request round trip = %#v, %v", roundTrip, err)
	}
	workspace := v2.WorkspaceResponse{ObjectID: "Account", LayoutRevision: layoutAuthoringV2RevisionA, Receipts: receipts}
	wireWorkspace, err := ToWireLayoutAuthoringV2ReadWorkspaceResponse(workspaceRequest, workspace)
	if err != nil {
		t.Fatal(err)
	}
	if roundTrip, err := FromWireLayoutAuthoringV2ReadWorkspaceResponse(workspaceRequest, wireWorkspace); err != nil || !reflect.DeepEqual(roundTrip, workspace) {
		t.Fatalf("workspace response round trip = %#v, %v", roundTrip, err)
	}

	storedRequest := v2.StoredLayoutRequest{ObjectID: "Account", LayoutID: "layout-main"}
	stored := v2.StoredLayoutResponse{
		LayoutID: "layout-main", ObjectID: "Account", LayoutRevision: layoutAuthoringV2RevisionA,
		Document: layoutAuthoringTestDocument(), OrderedListFields: []string{},
		RequiredCreateFields: []string{}, Receipts: receipts,
	}
	wireStored, err := ToWireLayoutAuthoringV2ReadStoredLayoutResponse(storedRequest, stored)
	if err != nil {
		t.Fatal(err)
	}
	encodedStored, err := proto.Marshal(wireStored)
	if err != nil {
		t.Fatal(err)
	}
	wireStored = &runtimev1.LayoutAuthoringV2ServiceReadStoredLayoutResponse{}
	if err = proto.Unmarshal(encodedStored, wireStored); err != nil {
		t.Fatal(err)
	}
	if roundTrip, err := FromWireLayoutAuthoringV2ReadStoredLayoutResponse(storedRequest, wireStored); err != nil || !reflect.DeepEqual(roundTrip, stored) {
		t.Fatalf("stored response round trip = %#v, %v", roundTrip, err)
	}
	wireStored.OrderedListFields = nil
	if _, err = FromWireLayoutAuthoringV2ReadStoredLayoutResponse(storedRequest, wireStored); err == nil {
		t.Fatal("accepted stored Layout response with missing ordered ListFields state")
	}

	request := v2.Request{
		ObjectID: "Account", ExpectedLayoutRevision: layoutAuthoringV2RevisionA,
		Operation: v2.Operation{SetDefault: &v2.SetDefaultLayout{LayoutID: "layout-main"}},
		HistoryRelatedLists: []v2.HistoryRelatedListProjection{{ObjectID: "Changes", Columns: map[string]historysemantic.HistoryColumn{
			"Key": historysemantic.HistoryColumnID, "Parent": historysemantic.HistoryColumnParentID, "Field": historysemantic.HistoryColumnField, "Before": historysemantic.HistoryColumnOldValue,
			"After": historysemantic.HistoryColumnNewValue, "Type": historysemantic.HistoryColumnDataType, "Time": historysemantic.HistoryColumnCreatedDate, "Actor": historysemantic.HistoryColumnCreatedByID,
		}}},
	}
	wireRequest, err := ToWireLayoutAuthoringV2MutateRequest(layoutAuthoringTestContext(t), request)
	if err != nil {
		t.Fatal(err)
	}
	if roundTrip, err := FromWireLayoutAuthoringV2MutateRequest(layoutAuthoringTestContext(t), wireRequest); err != nil || !reflect.DeepEqual(roundTrip, request) {
		t.Fatalf("mutation request round trip = %#v, %v", roundTrip, err)
	}
	layoutID := "layout-main"
	response := v2.Response{Changed: true, LayoutRevision: layoutAuthoringV2RevisionB, LayoutID: &layoutID, Receipts: receipts}
	wireResponse, err := ToWireLayoutAuthoringV2MutateResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	if roundTrip, err := FromWireLayoutAuthoringV2MutateResponse(request, wireResponse); err != nil || !reflect.DeepEqual(roundTrip, response) {
		t.Fatalf("mutation response round trip = %#v, %v", roundTrip, err)
	}
}

func TestLayoutAuthoringV2WirePreservesLimitsAndPreflightsNestedDocuments(t *testing.T) {
	invalidFailure := &commonsemantic.Failure{
		Category:         commonsemantic.FailureInvalidRequest,
		InvalidComponent: commonsemantic.InvalidComponentCommandData,
	}
	decodedInvalid := FromPlatformStatusError(
		ToPlatformStatusError(invalidFailure, v2.CapabilityName),
		v2.CapabilityName,
	)
	var invalidRoundTrip *commonsemantic.Failure
	if !errors.As(decodedInvalid, &invalidRoundTrip) || invalidRoundTrip.Category != invalidFailure.Category ||
		invalidRoundTrip.InvalidComponent != invalidFailure.InvalidComponent {
		t.Fatalf("V2 invalid request round trip = %#v", decodedInvalid)
	}
	invalidFieldFailure := &commonsemantic.Failure{
		Category:         commonsemantic.FailureInvalidRequest,
		InvalidComponent: commonsemantic.InvalidComponentFieldValue,
	}
	decodedInvalidField := FromPlatformStatusError(
		ToPlatformStatusError(invalidFieldFailure, v2.CapabilityName),
		v2.CapabilityName,
	)
	if !errors.As(decodedInvalidField, &invalidRoundTrip) || invalidRoundTrip.Category != invalidFieldFailure.Category ||
		invalidRoundTrip.InvalidComponent != invalidFieldFailure.InvalidComponent {
		t.Fatalf("V2 invalid field round trip = %#v", decodedInvalidField)
	}

	limitFailure := &commonsemantic.Failure{
		Category:  commonsemantic.FailureResourceExhausted,
		LimitKind: commonsemantic.LimitFieldCount,
		Maximum:   v1.MaxCompactFields,
	}
	decoded := FromPlatformStatusError(
		ToPlatformStatusError(limitFailure, v2.CapabilityName),
		v2.CapabilityName,
	)
	var roundTripFailure *commonsemantic.Failure
	if !errors.As(decoded, &roundTripFailure) || roundTripFailure.Category != limitFailure.Category ||
		roundTripFailure.LimitKind != limitFailure.LimitKind || roundTripFailure.Maximum != limitFailure.Maximum {
		t.Fatalf("V2 limit round trip = %#v", decoded)
	}

	request := &runtimev1.LayoutAuthoringV2ServiceMutateRequest{
		ObjectId:               "Account",
		ExpectedLayoutRevision: layoutAuthoringV2RevisionA,
		Operation: &runtimev1.LayoutAuthoringV2ServiceMutateRequest_Create{Create: &runtimev1.LayoutAuthoringV2Create{
			Document: &runtimev1.LayoutAuthoringDocument{
				LayoutId: "layout-main", ObjectId: "Account", Name: "Main", Kind: "Detail",
				CompactFields: make([]string, v1.MaxCompactFields+1),
			},
			ListFields: &runtimev1.LayoutAuthoringStringListChange{},
		}},
	}
	assertLimit := func(source string, err error) {
		t.Helper()
		var failure *commonsemantic.Failure
		if !errors.As(err, &failure) || failure.Category != commonsemantic.FailureResourceExhausted ||
			failure.LimitKind != commonsemantic.LimitFieldCount || failure.Maximum != v1.MaxCompactFields {
			t.Fatalf("%s = %#v", source, err)
		}
	}
	_, err := FromWireLayoutAuthoringV2MutateRequest(layoutAuthoringTestContext(t), request)
	assertLimit("typed preflight", err)
	encoded, err := proto.Marshal(request)
	if err != nil {
		t.Fatal(err)
	}
	assertLimit("raw preflight", PreflightPlatformWirePayload(encoded, &runtimev1.LayoutAuthoringV2ServiceMutateRequest{}))
}

func TestLayoutAuthoringV2HistoryLimitPaths(t *testing.T) {
	for _, tc := range []struct {
		name           string
		count, columns int
		kind           commonsemantic.LimitKind
		maximum        int
	}{
		{"projections", v2.MaxHistoryRelatedListProjections + 1, 8, commonsemantic.LimitOperationCount, v2.MaxHistoryRelatedListProjections},
		{"columns", 1, historysemantic.HistoryResultColumnCount + 1, commonsemantic.LimitFieldCount, historysemantic.HistoryResultColumnCount},
	} {
		t.Run(tc.name, func(t *testing.T) {
			request := &runtimev1.LayoutAuthoringV2ServiceMutateRequest{ObjectId: "Issue", ExpectedLayoutRevision: layoutAuthoringV2RevisionA}
			for i := 0; i < tc.count; i++ {
				columns := map[string]string{}
				for j := 0; j < tc.columns; j++ {
					columns[fmt.Sprintf("Field%d", j)] = "id"
				}
				request.HistoryRelatedLists = append(request.HistoryRelatedLists, &runtimev1.LayoutAuthoringHistoryRelatedListProjection{ObjectId: fmt.Sprintf("Changes%d", i), Columns: columns})
			}
			semanticRequest := v2.Request{ObjectID: request.ObjectId, ExpectedLayoutRevision: request.ExpectedLayoutRevision}
			for _, projection := range request.HistoryRelatedLists {
				columns := map[string]historysemantic.HistoryColumn{}
				for name, member := range projection.Columns {
					columns[name] = historysemantic.HistoryColumn(member)
				}
				semanticRequest.HistoryRelatedLists = append(semanticRequest.HistoryRelatedLists, v2.HistoryRelatedListProjection{ObjectID: projection.ObjectId, Columns: columns})
			}
			_, err := ToWireLayoutAuthoringV2MutateRequest(layoutAuthoringTestContext(t), semanticRequest)
			assertPlatformLimitFailure(t, err, tc.kind, tc.maximum)
			_, err = FromWireLayoutAuthoringV2MutateRequest(layoutAuthoringTestContext(t), request)
			assertPlatformLimitFailure(t, err, tc.kind, tc.maximum)
			encoded, err := proto.Marshal(request)
			if err != nil {
				t.Fatal(err)
			}
			err = PreflightPlatformWirePayload(encoded, &runtimev1.LayoutAuthoringV2ServiceMutateRequest{})
			assertPlatformLimitFailure(t, err, tc.kind, tc.maximum)
			assertPlatformLimitFailure(t, FromPlatformStatusError(ToPlatformStatusError(err, v2.CapabilityName), v2.CapabilityName), tc.kind, tc.maximum)
		})
	}
}
