package compatibility_test

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

	approvalnotificationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalnotificationflowv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
	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 TestGeneratedPageLayoutResolutionClientUsesOneCoarseRPC(t *testing.T) {
	service := &pageLayoutResolutionService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterPageLayoutResolutionServiceServer(server, service)
	})
	defer cleanup()
	client := runtimev1.NewPageLayoutResolutionServiceClient(connection)
	ctx, cancel := context.WithTimeout(withCapability(context.Background(), semantic.CapabilityName), time.Second)
	defer cancel()

	response, err := client.ResolvePageLayout(ctx, &runtimev1.ResolvePageLayoutRequest{ObjectId: "Account"})
	if err != nil || service.calls != 1 || len(response.GetSelections()) != 1 ||
		response.GetSelections()[0].GetLayoutId() != "layout-account" || len(response.GetLayouts()) != 1 {
		t.Fatalf("response = %#v, error = %v, calls = %d", response, err, service.calls)
	}
}

func TestPageLayoutResolutionServiceNAndNMinusOneRules(t *testing.T) {
	t.Run("N server preserves existing service", func(t *testing.T) {
		connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
			runtimev1.RegisterApprovalNotificationFlowReadServiceServer(server, &approvalNotificationFlowReadService{})
			runtimev1.RegisterPageLayoutResolutionServiceServer(server, &pageLayoutResolutionService{})
		})
		defer cleanup()
		ctx, cancel := context.WithTimeout(
			withCapability(context.Background(), approvalnotificationsemantic.CapabilityName), time.Second,
		)
		defer cancel()
		response, err := runtimev1.NewApprovalNotificationFlowReadServiceClient(connection).
			ReadApprovalNotificationFlows(ctx, &runtimev1.ReadApprovalNotificationFlowsRequest{})
		if err != nil || len(response.GetFlows()) != 1 || response.GetFlows()[0].GetName() != "Notify_Approver" {
			t.Fatalf("existing approval-notification 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.RegisterApprovalNotificationFlowReadServiceServer(server, &approvalNotificationFlowReadService{})
		})
		defer cleanup()
		ctx, cancel := context.WithTimeout(withCapability(context.Background(), semantic.CapabilityName), time.Second)
		defer cancel()
		response, err := runtimev1.NewPageLayoutResolutionServiceClient(connection).
			ResolvePageLayout(ctx, &runtimev1.ResolvePageLayoutRequest{ObjectId: "Account"})
		if response != nil || status.Code(err) != codes.Unimplemented {
			t.Fatalf("N/N-1 response = %#v, error = %v", response, err)
		}
	})
}

func TestPageLayoutResolutionRejectsAnotherMetadataCapability(t *testing.T) {
	service := &pageLayoutResolutionService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterPageLayoutResolutionServiceServer(server, service)
	})
	defer cleanup()
	ctx, cancel := context.WithTimeout(
		withCapability(context.Background(), approvalnotificationsemantic.CapabilityName), time.Second,
	)
	defer cancel()

	response, err := runtimev1.NewPageLayoutResolutionServiceClient(connection).
		ResolvePageLayout(ctx, &runtimev1.ResolvePageLayoutRequest{ObjectId: "Account"})
	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 pageLayoutResolutionService struct {
	runtimev1.UnimplementedPageLayoutResolutionServiceServer
	calls int
}

func (service *pageLayoutResolutionService) ResolvePageLayout(
	ctx context.Context,
	request *runtimev1.ResolvePageLayoutRequest,
) (*runtimev1.ResolvePageLayoutResponse, error) {
	service.calls++
	semanticRequest, err := mapping.FromWireResolvePageLayoutRequest(ctx, request)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, semantic.CapabilityName)
	}
	return mapping.ToWireResolvePageLayoutResponse(
		semanticRequest,
		semantic.Response{
			ObjectID:   semanticRequest.ObjectID,
			Selections: []semantic.Selection{{LayoutID: "layout-account"}},
			Layouts: []semantic.ResolvedLayout{{
				LayoutID: "layout-account", DocumentKind: semantic.PageLayoutDocumentKind,
				DocumentFormatVersion: semantic.PageLayoutDocumentFormatVersion,
				LayoutDocument:        []byte(`{"id":"layout-account"}`),
			}},
			Receipts: semantic.Receipts{
				ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName,
				CatalogRevision: 1, SchemaGeneration: 2, EngineGeneration: 3, SecurityEpoch: 4,
			},
		},
	)
}
