package compatibility_test

import (
	"context"
	"testing"
	"time"

	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/metadataadminreadv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestGeneratedMetadataAdminReadClientUsesFourCoarseRPCs(t *testing.T) {
	service := &metadataAdminReadService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterMetadataAdminReadServiceServer(server, service)
	})
	defer cleanup()
	client := runtimev1.NewMetadataAdminReadServiceClient(connection)
	ctx, cancel := context.WithTimeout(
		withCapability(context.Background(), semantic.CapabilityName),
		time.Second,
	)
	defer cancel()

	index, err := client.ReadObjectAdminIndex(ctx, &runtimev1.ReadObjectAdminIndexRequest{})
	if err != nil || len(index.GetObjects()) != 1 || index.GetObjects()[0].GetObjectId() != "Account" {
		t.Fatalf("object index = %#v, error = %v", index, err)
	}
	workspace, err := client.ReadLayoutAdminWorkspace(ctx, &runtimev1.ReadLayoutAdminWorkspaceRequest{ObjectId: "Account"})
	if err != nil || len(workspace.GetLayouts()) != 1 || len(workspace.GetAssignments()) != 1 {
		t.Fatalf("layout workspace = %#v, error = %v", workspace, err)
	}
	stored, err := client.ReadStoredLayout(ctx, &runtimev1.ReadStoredLayoutRequest{LayoutId: "layout-default"})
	if err != nil || stored.GetLayoutId() != "layout-default" || len(stored.GetStoredDocument()) == 0 {
		t.Fatalf("stored layout = %#v, error = %v", stored, err)
	}
	rules, err := client.ReadValidationRuleAdminIndex(ctx, &runtimev1.ReadValidationRuleAdminIndexRequest{ObjectId: "Account"})
	if err != nil || len(rules.GetRules()) != 1 || rules.GetRules()[0].GetRuleId() != "rule-a" {
		t.Fatalf("validation-rule index = %#v, error = %v", rules, err)
	}
	if service.indexCalls != 1 || service.workspaceCalls != 1 || service.storedCalls != 1 || service.ruleCalls != 1 {
		t.Fatalf(
			"metadata admin calls index/workspace/stored/rules = %d/%d/%d/%d",
			service.indexCalls,
			service.workspaceCalls,
			service.storedCalls,
			service.ruleCalls,
		)
	}
}

func TestMetadataAdminReadServiceNAndNMinusOneRules(t *testing.T) {
	t.Run("N server preserves existing service", func(t *testing.T) {
		connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
			runtimev1.RegisterPrivateListViewServiceServer(server, &privateListViewService{})
			runtimev1.RegisterMetadataAdminReadServiceServer(server, &metadataAdminReadService{})
		})
		defer cleanup()
		ctx, cancel := context.WithTimeout(
			withCapability(context.Background(), listviewsemantic.CapabilityName),
			time.Second,
		)
		defer cancel()
		response, err := runtimev1.NewPrivateListViewServiceClient(connection).ReadPrivateListViews(
			ctx,
			&runtimev1.ReadPrivateListViewsRequest{
				Selector: runtimev1.PrivateListViewReadSelector_PRIVATE_LIST_VIEW_READ_SELECTOR_LIST,
				ObjectId: "Account",
			},
		)
		if err != nil || len(response.GetViews()) != 1 || response.GetViews()[0].GetViewId() != "view-1" {
			t.Fatalf("existing private-list-view service = %#v, error = %v", response, err)
		}
	})

	t.Run("N client fails closed against N-1 endpoint", func(t *testing.T) {
		connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
			runtimev1.RegisterPrivateListViewServiceServer(server, &privateListViewService{})
		})
		defer cleanup()
		client := runtimev1.NewMetadataAdminReadServiceClient(connection)
		ctx, cancel := context.WithTimeout(
			withCapability(context.Background(), semantic.CapabilityName),
			time.Second,
		)
		defer cancel()
		calls := []struct {
			name string
			call func() (bool, error)
		}{
			{name: "object index", call: func() (bool, error) {
				response, err := client.ReadObjectAdminIndex(ctx, &runtimev1.ReadObjectAdminIndexRequest{})
				return response == nil, err
			}},
			{name: "layout workspace", call: func() (bool, error) {
				response, err := client.ReadLayoutAdminWorkspace(ctx, &runtimev1.ReadLayoutAdminWorkspaceRequest{ObjectId: "Account"})
				return response == nil, err
			}},
			{name: "stored layout", call: func() (bool, error) {
				response, err := client.ReadStoredLayout(ctx, &runtimev1.ReadStoredLayoutRequest{LayoutId: "layout-default"})
				return response == nil, err
			}},
			{name: "validation-rule index", call: func() (bool, error) {
				response, err := client.ReadValidationRuleAdminIndex(ctx, &runtimev1.ReadValidationRuleAdminIndexRequest{ObjectId: "Account"})
				return response == nil, err
			}},
		}
		for _, test := range calls {
			t.Run(test.name, func(t *testing.T) {
				nilResponse, err := test.call()
				if !nilResponse || status.Code(err) != codes.Unimplemented {
					t.Fatalf("N/N-1 nil response = %t, error = %v", nilResponse, err)
				}
			})
		}
	})
}

type metadataAdminReadService struct {
	runtimev1.UnimplementedMetadataAdminReadServiceServer
	indexCalls     int
	workspaceCalls int
	storedCalls    int
	ruleCalls      int
}

func (service *metadataAdminReadService) ReadObjectAdminIndex(
	ctx context.Context,
	request *runtimev1.ReadObjectAdminIndexRequest,
) (*runtimev1.ReadObjectAdminIndexResponse, error) {
	service.indexCalls++
	semanticRequest, err := mapping.FromWireReadObjectAdminIndexRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	description := "Customer accounts"
	return mapping.ToWireReadObjectAdminIndexResponse(
		semanticRequest,
		semantic.ObjectAdminIndexResponse{
			Objects: []semantic.ObjectAdminMetadata{{
				ObjectID: "Account", Description: &description, DeleteMode: "soft",
			}},
			Receipts: metadataAdminReadReceipts(),
		},
	)
}

func (service *metadataAdminReadService) ReadLayoutAdminWorkspace(
	ctx context.Context,
	request *runtimev1.ReadLayoutAdminWorkspaceRequest,
) (*runtimev1.ReadLayoutAdminWorkspaceResponse, error) {
	service.workspaceCalls++
	semanticRequest, err := mapping.FromWireReadLayoutAdminWorkspaceRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireReadLayoutAdminWorkspaceResponse(
		semanticRequest,
		semantic.LayoutAdminWorkspaceResponse{
			ObjectID: "Account",
			Layouts: []semantic.LayoutSummary{{
				LayoutID: "layout-default", ObjectID: "Account", Name: "Account Layout", IsDefault: true,
			}},
			Assignments: []semantic.LayoutAssignment{{
				AssignmentID: "assignment-default", ObjectID: "Account", LayoutID: "layout-default",
			}},
			Receipts: metadataAdminReadReceipts(),
		},
	)
}

func (service *metadataAdminReadService) ReadStoredLayout(
	ctx context.Context,
	request *runtimev1.ReadStoredLayoutRequest,
) (*runtimev1.ReadStoredLayoutResponse, error) {
	service.storedCalls++
	semanticRequest, err := mapping.FromWireReadStoredLayoutRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireReadStoredLayoutResponse(
		semanticRequest,
		semantic.StoredLayoutResponse{
			LayoutID: "layout-default", ObjectID: "Account",
			StoredDocument: []byte(`{"id":"layout-default","objectApiName":"Account"}`),
			Receipts:       metadataAdminReadReceipts(),
		},
	)
}

func (service *metadataAdminReadService) ReadValidationRuleAdminIndex(
	ctx context.Context,
	request *runtimev1.ReadValidationRuleAdminIndexRequest,
) (*runtimev1.ReadValidationRuleAdminIndexResponse, error) {
	service.ruleCalls++
	semanticRequest, err := mapping.FromWireReadValidationRuleAdminIndexRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireReadValidationRuleAdminIndexResponse(
		semanticRequest,
		semantic.ValidationRuleAdminIndexResponse{
			ObjectID: semanticRequest.ObjectID,
			Rules: []semantic.ValidationRuleMetadata{{
				RuleID: "rule-a", ObjectID: semanticRequest.ObjectID, Name: "RequiredName",
				Condition: "Name = null", ErrorMessage: "Name is required",
			}},
			Receipts: metadataAdminReadReceipts(),
		},
	)
}

func metadataAdminReadReceipts() semantic.Receipts {
	return semantic.Receipts{
		ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName,
		CatalogRevision: 1, SchemaGeneration: 2, EngineGeneration: 3, SecurityEpoch: 4,
	}
}
