package compatibility_test

import (
	"context"
	"errors"
	"testing"
	"time"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalnotificationflowv1"
	approvalsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalv1"
	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	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 TestGeneratedApprovalNotificationFlowReadClientUsesOneCoarseRPC(t *testing.T) {
	service := &approvalNotificationFlowReadService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterApprovalNotificationFlowReadServiceServer(server, service)
	})
	defer cleanup()
	client := runtimev1.NewApprovalNotificationFlowReadServiceClient(connection)
	ctx, cancel := context.WithTimeout(
		withCapability(context.Background(), semantic.CapabilityName),
		time.Second,
	)
	defer cancel()

	response, err := client.ReadApprovalNotificationFlows(
		ctx,
		&runtimev1.ReadApprovalNotificationFlowsRequest{},
	)
	if err != nil || service.calls != 1 || len(response.GetFlows()) != 1 ||
		response.GetFlows()[0].GetName() != "Notify_Approver" {
		t.Fatalf("response = %#v, error = %v, calls = %d", response, err, service.calls)
	}
}

func TestApprovalNotificationFlowReadServiceNAndNMinusOneRules(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.RegisterApprovalNotificationFlowReadServiceServer(
				server,
				&approvalNotificationFlowReadService{},
			)
		})
		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()
		ctx, cancel := context.WithTimeout(
			withCapability(context.Background(), semantic.CapabilityName),
			time.Second,
		)
		defer cancel()
		response, err := runtimev1.NewApprovalNotificationFlowReadServiceClient(connection).
			ReadApprovalNotificationFlows(ctx, &runtimev1.ReadApprovalNotificationFlowsRequest{})
		if response != nil || status.Code(err) != codes.Unimplemented {
			t.Fatalf("N/N-1 response = %#v, error = %v", response, err)
		}
	})
}

func TestApprovalNotificationFlowReadRejectsAnotherApprovalCapability(t *testing.T) {
	service := &approvalNotificationFlowReadService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterApprovalNotificationFlowReadServiceServer(server, service)
	})
	defer cleanup()
	ctx, cancel := context.WithTimeout(
		withCapability(context.Background(), approvalsemantic.ReadCapabilityName),
		time.Second,
	)
	defer cancel()

	response, err := runtimev1.NewApprovalNotificationFlowReadServiceClient(connection).
		ReadApprovalNotificationFlows(ctx, &runtimev1.ReadApprovalNotificationFlowsRequest{})
	if response != nil || status.Code(err) != codes.FailedPrecondition || service.calls != 0 {
		t.Fatalf("response = %#v, error = %v, calls = %d", response, err, service.calls)
	}
	var failure *semantic.Failure
	if decoded := mapping.FromPlatformStatusError(err, semantic.CapabilityName); !errors.As(decoded, &failure) ||
		failure.Category != semantic.FailureIncompatible {
		t.Fatalf("decoded = %#v", decoded)
	}
}

type approvalNotificationFlowReadService struct {
	runtimev1.UnimplementedApprovalNotificationFlowReadServiceServer
	calls int
}

func (service *approvalNotificationFlowReadService) ReadApprovalNotificationFlows(
	ctx context.Context,
	request *runtimev1.ReadApprovalNotificationFlowsRequest,
) (*runtimev1.ReadApprovalNotificationFlowsResponse, error) {
	service.calls++
	semanticRequest, err := mapping.FromWireReadApprovalNotificationFlowsRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireReadApprovalNotificationFlowsResponse(
		semanticRequest,
		semantic.Response{
			Flows: []semantic.FlowDescriptor{{
				Name: "Notify_Approver", Label: "Notify Approver", Description: "Sends the notification.",
			}},
			Receipts: semantic.Receipts{
				ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName,
				CatalogRevision: 1, SchemaGeneration: 2, EngineGeneration: 3, SecurityEpoch: 4,
			},
		},
	)
}
