package compatibility_test

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

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionadminv1"
	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 TestGeneratedPermissionAdministrationClientUsesTwoCoarseRPCs(t *testing.T) {
	client, service, cleanup := startPermissionAdministrationTestServer(t)
	defer cleanup()
	ctx, cancel := context.WithTimeout(withCapability(context.Background(), semantic.CapabilityName), time.Second)
	defer cancel()

	read, err := client.ReadPermissionAdministration(ctx, &runtimev1.ReadPermissionAdministrationRequest{
		Selector: runtimev1.PermissionAdministrationReadSelector_PERMISSION_ADMINISTRATION_READ_SELECTOR_SYSTEM_CATALOG,
	})
	if err != nil || len(read.GetSystemCatalog()) != 1 ||
		read.GetSystemCatalog()[0].GetPermissionId() != "ManageProfilesPermSets" || service.readCalls != 1 {
		t.Fatalf("read = %#v, error = %v, calls = %d", read, err, service.readCalls)
	}
	mutated, err := client.MutatePermissionAdministration(ctx, &runtimev1.MutatePermissionAdministrationRequest{
		Selector:        runtimev1.PermissionAdministrationMutationSelector_PERMISSION_ADMINISTRATION_MUTATION_SELECTOR_SYSTEM_GRANTS,
		PermissionSetId: "permset-1",
		Grants:          []*runtimev1.PermissionAdministrationGrant{{PermissionId: "ViewSetup", Enabled: true}},
	})
	if err != nil || mutated.GetReceipts().GetCapabilityVersion().GetName() != semantic.CapabilityName || service.mutateCalls != 1 {
		t.Fatalf("mutate = %#v, error = %v, calls = %d", mutated, err, service.mutateCalls)
	}
}

func TestPermissionAdministrationServiceNAndNMinusOneRules(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.RegisterPermissionAdministrationServiceServer(server, &permissionAdministrationService{})
		go func() { _ = server.Serve(listener) }()
		connection := dialPermissionAdministrationTestServer(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 := dialPermissionAdministrationTestServer(t, listener)
		defer connection.Close()
		defer server.Stop()
		defer listener.Close()

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

type permissionAdministrationService struct {
	runtimev1.UnimplementedPermissionAdministrationServiceServer
	readCalls   int
	mutateCalls int
}

func (service *permissionAdministrationService) ReadPermissionAdministration(ctx context.Context, request *runtimev1.ReadPermissionAdministrationRequest) (*runtimev1.ReadPermissionAdministrationResponse, error) {
	service.readCalls++
	semanticRequest, err := mapping.FromWireReadPermissionAdministrationRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireReadPermissionAdministrationResponse(semanticRequest, semantic.ReadResponse{
		SystemCatalog: []semantic.SystemPermission{{
			PermissionID: "ManageProfilesPermSets", Label: "Manage Profiles", Category: "Setup",
		}},
		Receipts: permissionAdministrationReceipts(),
	})
}

func (service *permissionAdministrationService) MutatePermissionAdministration(ctx context.Context, request *runtimev1.MutatePermissionAdministrationRequest) (*runtimev1.MutatePermissionAdministrationResponse, error) {
	service.mutateCalls++
	semanticRequest, err := mapping.FromWireMutatePermissionAdministrationRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireMutatePermissionAdministrationResponse(semanticRequest, semantic.MutateResponse{
		Receipts: permissionAdministrationReceipts(),
	})
}

func startPermissionAdministrationTestServer(t *testing.T) (runtimev1.PermissionAdministrationServiceClient, *permissionAdministrationService, func()) {
	t.Helper()
	service := &permissionAdministrationService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterPermissionAdministrationServiceServer(server, service)
	})
	return runtimev1.NewPermissionAdministrationServiceClient(connection), service, cleanup
}

func dialPermissionAdministrationTestServer(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 permissionAdministrationReceipts() semantic.Receipts {
	return semantic.Receipts{
		ProtocolVersion: "1.0", CatalogRevision: 1, SchemaGeneration: 2,
		EngineGeneration: 3, SecurityEpoch: 4, CapabilityVersion: semantic.CapabilityName,
	}
}
