package compatibility_test

import (
	"context"
	"net"
	"testing"
	"time"

	semantic "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/credentials/insecure"
	"google.golang.org/grpc/status"
	"google.golang.org/grpc/test/bufconn"
)

func TestGeneratedPrivateListViewClientUsesTwoCoarseRPCs(t *testing.T) {
	client, service, cleanup := startPrivateListViewTestServer(t)
	defer cleanup()
	ctx, cancel := context.WithTimeout(withCapability(context.Background(), semantic.CapabilityName), time.Second)
	defer cancel()

	read, err := client.ReadPrivateListViews(ctx, &runtimev1.ReadPrivateListViewsRequest{
		Selector: runtimev1.PrivateListViewReadSelector_PRIVATE_LIST_VIEW_READ_SELECTOR_LIST,
		ObjectId: "Account",
	})
	if err != nil || len(read.GetViews()) != 1 || read.GetViews()[0].GetViewId() != "view-1" || service.readCalls != 1 {
		t.Fatalf("read = %#v, error = %v, calls = %d", read, err, service.readCalls)
	}
	mutated, err := client.MutatePrivateListView(ctx, &runtimev1.MutatePrivateListViewRequest{
		Operation: runtimev1.PrivateListViewMutationOperation_PRIVATE_LIST_VIEW_MUTATION_OPERATION_CREATE,
		ObjectId:  "Account",
		Patch:     &runtimev1.PrivateListViewPatch{Label: stringPointer("My accounts")},
	})
	if err != nil || mutated.GetView().GetViewId() != "view-1" || service.mutateCalls != 1 {
		t.Fatalf("mutate = %#v, error = %v, calls = %d", mutated, err, service.mutateCalls)
	}
}

func TestPrivateListViewServiceNAndNMinusOneRules(t *testing.T) {
	t.Run("N server preserves existing service", func(t *testing.T) {
		listener := bufconn.Listen(1024 * 1024)
		server := grpc.NewServer()
		runtimev1.RegisterCommandAcceptanceServiceServer(server, &commandOnlyServer{})
		runtimev1.RegisterPrivateListViewServiceServer(server, &privateListViewService{})
		go func() { _ = server.Serve(listener) }()
		connection := dialPrivateListViewTestServer(t, listener)
		defer connection.Close()
		defer server.Stop()
		defer listener.Close()

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		accepted, err := runtimev1.NewCommandAcceptanceServiceClient(connection).AcceptCommand(ctx, &runtimev1.AcceptCommandRequest{CommandId: "command-1"})
		if err != nil || accepted.GetCommandId() != "command-1" {
			t.Fatalf("existing service = %#v, %v", accepted, err)
		}
	})

	t.Run("N client fails closed against N-1 endpoint", func(t *testing.T) {
		listener := bufconn.Listen(1024 * 1024)
		server := grpc.NewServer()
		runtimev1.RegisterCommandAcceptanceServiceServer(server, &commandOnlyServer{})
		go func() { _ = server.Serve(listener) }()
		connection := dialPrivateListViewTestServer(t, listener)
		defer connection.Close()
		defer server.Stop()
		defer listener.Close()

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		response, err := runtimev1.NewPrivateListViewServiceClient(connection).ReadPrivateListViews(ctx, &runtimev1.ReadPrivateListViewsRequest{})
		if response != nil || status.Code(err) != codes.Unimplemented {
			t.Fatalf("N/N-1 response = %#v, error = %v", response, err)
		}
	})
}

type privateListViewService struct {
	runtimev1.UnimplementedPrivateListViewServiceServer
	readCalls   int
	mutateCalls int
}

func (service *privateListViewService) ReadPrivateListViews(ctx context.Context, request *runtimev1.ReadPrivateListViewsRequest) (*runtimev1.ReadPrivateListViewsResponse, error) {
	service.readCalls++
	semanticRequest, err := mapping.FromWireReadPrivateListViewsRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	response := semantic.ReadResponse{Views: []semantic.View{privateListView("view-1")}, Receipts: privateListViewReceipts()}
	wireResponse, err := mapping.ToWireReadPrivateListViewsResponse(semanticRequest, response)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return wireResponse, nil
}

func (service *privateListViewService) MutatePrivateListView(ctx context.Context, request *runtimev1.MutatePrivateListViewRequest) (*runtimev1.MutatePrivateListViewResponse, error) {
	service.mutateCalls++
	semanticRequest, err := mapping.FromWireMutatePrivateListViewRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	view := privateListView("view-1")
	response := semantic.MutateResponse{View: &view, Receipts: privateListViewReceipts()}
	wireResponse, err := mapping.ToWireMutatePrivateListViewResponse(semanticRequest, response)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return wireResponse, nil
}

func startPrivateListViewTestServer(t *testing.T) (runtimev1.PrivateListViewServiceClient, *privateListViewService, func()) {
	t.Helper()
	service := &privateListViewService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterPrivateListViewServiceServer(server, service)
	})
	return runtimev1.NewPrivateListViewServiceClient(connection), service, cleanup
}

func dialPrivateListViewTestServer(t *testing.T, listener *bufconn.Listener) *grpc.ClientConn {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	connection, err := grpc.DialContext(ctx, "passthrough:///bufnet",
		grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultCallOptions(testDefaultCallOptions()...),
	)
	if err != nil {
		t.Fatal(err)
	}
	return connection
}

func privateListView(id string) semantic.View {
	return semantic.View{ViewID: id, ObjectID: "Account", Label: "My accounts", FieldIDs: []string{"Name"}, StableName: "My_Accounts"}
}

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

func stringPointer(value string) *string { return &value }
