package compatibility_test

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

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

	response, err := client.QueryRecords(ctx, &runtimev1.QueryRecordsRequest{ObjectId: "Issue", Selections: queryFieldSelections("Id", "Subject"), Limit: 200})
	if err != nil || response.GetTotalSize() != 1 || service.queryCalls.Load() != 1 || service.historyCalls.Load() != 0 {
		t.Fatalf("query response=%#v err=%v query=%d history=%d", response, err, service.queryCalls.Load(), service.historyCalls.Load())
	}
	history, err := client.QueryHistory(ctx, &runtimev1.QueryHistoryRequest{ParentObjectId: "Issue", SelectedFields: []string{"Field"}, ParentRecordId: "I-1", Limit: 20})
	if err != nil || history.GetTotalSize() != 1 || service.queryCalls.Load() != 1 || service.historyCalls.Load() != 1 {
		t.Fatalf("history response=%#v err=%v query=%d history=%d", history, err, service.queryCalls.Load(), service.historyCalls.Load())
	}
}

func TestGeneratedQueryClientFailsClosedOnCapabilityMismatch(t *testing.T) {
	client, service, cleanup := startQueryTestServer(t)
	defer cleanup()
	ctx, cancel := context.WithTimeout(withProtocol(context.Background()), time.Second)
	defer cancel()
	response, err := client.QueryRecords(ctx, &runtimev1.QueryRecordsRequest{ObjectId: "Issue", Selections: queryFieldSelections("Id")})
	if response != nil || status.Code(err) != codes.FailedPrecondition || service.queryCalls.Load() != 0 {
		t.Fatalf("response=%#v error=%v calls=%d", response, err, service.queryCalls.Load())
	}
	var failure *querysemantic.Failure
	if decoded := mapping.FromQueryStatusError(err); !errors.As(decoded, &failure) || failure.Category != querysemantic.FailureIncompatible {
		t.Fatalf("decoded=%#v", decoded)
	}
}

func queryFieldSelections(fields ...string) []*runtimev1.QuerySelection {
	result := make([]*runtimev1.QuerySelection, 0, len(fields))
	for _, field := range fields {
		result = append(result, &runtimev1.QuerySelection{Kind: runtimev1.QuerySelectionKind_QUERY_SELECTION_KIND_FIELD, Field: field})
	}
	return result
}

type testQueryService struct {
	runtimev1.UnimplementedRecordQueryServiceServer
	queryCalls      atomic.Int64
	historyCalls    atomic.Int64
	response        *querysemantic.Response
	historyResponse *querysemantic.HistoryResponse
}

func (s *testQueryService) QueryRecords(context.Context, *runtimev1.QueryRecordsRequest) (*runtimev1.QueryRecordsResponse, error) {
	s.queryCalls.Add(1)
	if s.response != nil {
		response, err := mapping.ToWireQueryRecordsResponse(*s.response)
		return response, mapping.ToQueryStatusError(err)
	}
	return &runtimev1.QueryRecordsResponse{TotalSize: 1, Done: true, Receipts: queryReceipts()}, nil
}

func (s *testQueryService) QueryHistory(context.Context, *runtimev1.QueryHistoryRequest) (*runtimev1.QueryHistoryResponse, error) {
	s.historyCalls.Add(1)
	if s.historyResponse != nil {
		response, err := mapping.ToWireQueryHistoryResponse(*s.historyResponse)
		return response, mapping.ToQueryStatusError(err)
	}
	return &runtimev1.QueryHistoryResponse{TotalSize: 1, Done: true, Receipts: queryReceipts()}, nil
}

func queryReceipts() *commonv1.ConsistencyReceipts {
	return &commonv1.ConsistencyReceipts{
		ProtocolVersion:   &commonv1.ProtocolVersion{Major: querysemantic.ProtocolMajor, Minor: querysemantic.ProtocolMinor},
		CapabilityVersion: &commonv1.CapabilityVersion{Name: querysemantic.CapabilityName, Major: 1},
	}
}

func startQueryTestServer(t *testing.T) (runtimev1.RecordQueryServiceClient, *testQueryService, func()) {
	t.Helper()
	service := &testQueryService{}
	connection, cleanup := startBufconnServer(t, func(server *grpc.Server) {
		runtimev1.RegisterRecordQueryServiceServer(server, service)
	})
	return runtimev1.NewRecordQueryServiceClient(connection), service, cleanup
}
