package mapping

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

	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
)

func TestObjectListFieldsWireRoundTripPreservesEmptyAndOrderedFields(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	revision := uint64(7)
	request := semantic.ObjectListFieldsRequest{
		ObjectIDs: []string{"Account", "Contact"}, MinCatalogRevision: &revision,
	}
	wireRequest, err := ToWireReadObjectListFieldsRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	roundRequest, err := FromWireReadObjectListFieldsRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(roundRequest, request) {
		t.Fatalf("request round trip = %#v, %v", roundRequest, err)
	}
	response := semantic.ObjectListFieldsResponse{
		Objects: []semantic.ObjectListFields{
			{ObjectID: "Account", OrderedFieldIDs: []string{"Name", "OwnerId"}},
			{ObjectID: "Contact", OrderedFieldIDs: []string{}},
		},
		Receipts: semantic.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName,
			CatalogRevision: 7, SecurityEpoch: 9,
		},
	}
	wireResponse, err := ToWireReadObjectListFieldsResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	encoded, err := proto.Marshal(wireResponse)
	if err != nil {
		t.Fatal(err)
	}
	wireResponse = &runtimev1.ReadObjectListFieldsResponse{}
	if err = proto.Unmarshal(encoded, wireResponse); err != nil {
		t.Fatal(err)
	}
	roundResponse, err := FromWireReadObjectListFieldsResponse(request, wireResponse)
	if err != nil || !reflect.DeepEqual(roundResponse, response) {
		t.Fatalf("response round trip = %#v, %v", roundResponse, err)
	}

	wireResponse.Objects[0].OrderedFieldIds.Values = make([]string, semantic.MaxListFieldsPerObject+1)
	_, err = FromWireReadObjectListFieldsResponse(request, wireResponse)
	var failure *semantic.Failure
	if !errors.As(err, &failure) || failure.Category != semantic.FailureResourceExhausted ||
		failure.LimitKind != semantic.LimitFieldCount || failure.Maximum != semantic.MaxListFieldsPerObject {
		t.Fatalf("oversized response failure = %#v", err)
	}

	wireResponse.Objects[0].OrderedFieldIds = nil
	if _, err = FromWireReadObjectListFieldsResponse(request, wireResponse); !errors.As(err, &failure) || failure.Category != semantic.FailureInternal {
		t.Fatalf("missing ordered field state failure = %#v", err)
	}

	wireResponse = &runtimev1.ReadObjectListFieldsResponse{
		Objects:  make([]*runtimev1.ObjectListFields, semantic.MaxObjectSelectors+1),
		Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName)),
	}
	if _, err = FromWireReadObjectListFieldsResponse(request, wireResponse); !errors.As(err, &failure) || failure.Category != semantic.FailureInternal {
		t.Fatalf("oversized object response failure = %#v", err)
	}
}

func TestObjectListFieldsRawPreflightRejectsOversizedCollections(t *testing.T) {
	request := &runtimev1.ReadObjectListFieldsRequest{
		ObjectIds: make([]string, semantic.MaxObjectSelectors+1),
	}
	encoded, err := proto.Marshal(request)
	if err != nil {
		t.Fatal(err)
	}
	var failure *semantic.Failure
	if err = PreflightPlatformWirePayload(encoded, &runtimev1.ReadObjectListFieldsRequest{}); !errors.As(err, &failure) ||
		failure.Category != semantic.FailureResourceExhausted || failure.LimitKind != semantic.LimitRecordCount ||
		failure.Maximum != semantic.MaxObjectSelectors {
		t.Fatalf("oversized request preflight = %#v", err)
	}

	response := &runtimev1.ReadObjectListFieldsResponse{Objects: []*runtimev1.ObjectListFields{{
		ObjectId: "Account",
		OrderedFieldIds: &runtimev1.ObjectListFieldIDs{
			Values: make([]string, semantic.MaxListFieldsPerObject+1),
		},
	}}}
	encoded, err = proto.Marshal(response)
	if err != nil {
		t.Fatal(err)
	}
	if err = PreflightPlatformWirePayload(encoded, &runtimev1.ReadObjectListFieldsResponse{}); !errors.As(err, &failure) ||
		failure.Category != semantic.FailureInternal {
		t.Fatalf("oversized response preflight = %#v", err)
	}
}

func TestObjectListFieldsRequestFrameLimitSurvivesPlatformStatus(t *testing.T) {
	source := PreflightPlatformWirePayload(
		oversizedUnknownScalarPayload(envelope.MaxRequestWireBytes),
		&runtimev1.ReadObjectListFieldsRequest{},
	)
	assertPlatformLimitFailure(t, source, commonsemantic.LimitWireFrame, envelope.MaxRequestWireBytes)
	wireError := ToPlatformStatusError(source, semantic.CapabilityName)
	if got := status.Code(wireError); got != codes.ResourceExhausted {
		t.Fatalf("status code = %s, want %s", got, codes.ResourceExhausted)
	}
	assertPlatformLimitFailure(
		t,
		FromPlatformStatusError(wireError, semantic.CapabilityName),
		commonsemantic.LimitWireFrame,
		envelope.MaxRequestWireBytes,
	)
}
