package compatibility_test

import (
	"context"
	"errors"
	"sync/atomic"
	"testing"
	"time"

	relatedsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/relatedcountsv1"
	commonv1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/common/v1"
	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 TestGeneratedRelatedCountClientUsesOneCoarseRPCForBoundedBatch(t *testing.T) {
	client, service, cleanup := startRelatedCountTestServer(t)
	defer cleanup()
	ctx, cancel := context.WithTimeout(withCapability(context.Background(), relatedsemantic.CapabilityName), time.Second)
	defer cancel()

	response, err := client.CountRelatedRecords(ctx, &runtimev1.CountRelatedRecordsRequest{Counts: []*runtimev1.RelatedRecordCountSpec{
		{ParentRecordId: "P-1", ChildObjectId: "WorkItem", RelationshipField: "ParentId"},
		{ParentRecordId: "P-1", ChildObjectId: "Comment", RelationshipField: "OwnerKey", MatchValue: "opaque-parent-key"},
	}})
	if err != nil || service.calls.Load() != 1 {
		t.Fatalf("response=%#v err=%v calls=%d", response, err, service.calls.Load())
	}
	results := response.GetResults()
	if len(results) != 2 || results[0].GetIndex() != 0 || results[0].GetOutcome() != runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_COUNTED || results[0].GetCount() != 0 ||
		results[1].GetIndex() != 1 || results[1].GetOutcome() != runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_NOT_AVAILABLE || results[1].GetCount() != 0 {
		t.Fatalf("results=%#v", results)
	}
	if service.lastRequest.GetCounts()[1].GetParentRecordId() != "P-1" || service.lastRequest.GetCounts()[1].GetMatchValue() != "opaque-parent-key" {
		t.Fatalf("parent authorization and match facts were not preserved: %#v", service.lastRequest)
	}
}

func TestGeneratedRelatedCountClientFailsClosedOnCapabilityMismatch(t *testing.T) {
	client, service, cleanup := startRelatedCountTestServer(t)
	defer cleanup()
	ctx, cancel := context.WithTimeout(withProtocol(context.Background()), time.Second)
	defer cancel()

	response, err := client.CountRelatedRecords(ctx, &runtimev1.CountRelatedRecordsRequest{})
	if response != nil || status.Code(err) != codes.FailedPrecondition || service.calls.Load() != 0 {
		t.Fatalf("response=%#v error=%v calls=%d", response, err, service.calls.Load())
	}
	var failure *relatedsemantic.Failure
	if decoded := mapping.FromRelatedCountStatusError(err); !errors.As(decoded, &failure) || failure.Category != relatedsemantic.FailureIncompatible {
		t.Fatalf("decoded=%#v", decoded)
	}
}

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

	response, err := client.CountRelatedRecords(ctx, &runtimev1.CountRelatedRecordsRequest{})
	if err != nil || response == nil || len(response.GetResults()) != 0 || service.calls.Load() != 1 {
		t.Fatalf("response=%#v error=%v calls=%d", response, err, service.calls.Load())
	}
}

type testRelatedCountService struct {
	runtimev1.UnimplementedRelatedRecordCountServiceServer
	calls       atomic.Int64
	lastRequest *runtimev1.CountRelatedRecordsRequest
}

func (s *testRelatedCountService) CountRelatedRecords(_ context.Context, request *runtimev1.CountRelatedRecordsRequest) (*runtimev1.CountRelatedRecordsResponse, error) {
	s.calls.Add(1)
	s.lastRequest = request
	response := &runtimev1.CountRelatedRecordsResponse{
		Receipts: &commonv1.ConsistencyReceipts{
			ProtocolVersion:   &commonv1.ProtocolVersion{Major: relatedsemantic.ProtocolMajor, Minor: relatedsemantic.ProtocolMinor},
			CapabilityVersion: &commonv1.CapabilityVersion{Name: relatedsemantic.CapabilityName, Major: 1},
		},
	}
	if len(request.GetCounts()) != 0 {
		response.Results = []*runtimev1.RelatedRecordCountResult{
			{Index: 0, Outcome: runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_COUNTED},
			{Index: 1, Outcome: runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_NOT_AVAILABLE},
		}
	}
	return response, nil
}

func startRelatedCountTestServer(t *testing.T) (runtimev1.RelatedRecordCountServiceClient, *testRelatedCountService, func()) {
	t.Helper()
	service := &testRelatedCountService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterRelatedRecordCountServiceServer(server, service)
	})
	return runtimev1.NewRelatedRecordCountServiceClient(connection), service, cleanup
}
