package mapping

import (
	"reflect"
	"testing"

	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	flowsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

func TestFlowExecutionErrorsRoundTripWithoutLosingOrderOrFields(t *testing.T) {
	response := flowsemantic.ResponseV2{
		ExecutionID: "execution-errors-1",
		Errors: []flowsemantic.ExecutionError{
			{Code: "POLICY_VALIDATION_ERROR", Message: "Correct RequestedValue before retrying.", Fields: []string{"RequestedValue"}},
			{Code: "RELATED_VALUE_REJECTED", Message: "Correct RelatedValue before retrying.", Fields: []string{"RelatedValue"}},
		},
		Receipts: mappingTestReceipts(flowsemantic.CapabilityName, 17),
	}
	wireResponse, err := ToWireExecuteFlowResponseV2(response)
	if err != nil {
		t.Fatal(err)
	}
	if len(wireResponse.GetErrors()) != 2 ||
		wireResponse.GetErrors()[0].GetCode() != "POLICY_VALIDATION_ERROR" ||
		!reflect.DeepEqual(wireResponse.GetErrors()[0].GetFields(), []string{"RequestedValue"}) {
		t.Fatalf("wire errors lost detail or order: %#v", wireResponse.GetErrors())
	}
	roundTrip, err := FromWireExecuteFlowResponseV2(wireResponse)
	if err != nil || !reflect.DeepEqual(roundTrip, response) {
		t.Fatalf("Flow failure round trip = %#v, %v", roundTrip, err)
	}
}

func TestFlowResponseV2SuccessRoundTripsAndLegacyDecoderFailsClosedOnErrors(t *testing.T) {
	response := flowsemantic.ResponseV2{
		ExecutionID: "execution-success-1", CanonicalOutputs: []byte(`{"result":true}`),
		Receipts: mappingTestReceipts(flowsemantic.CapabilityName, 17),
	}
	wireResponse, err := ToWireExecuteFlowResponseV2(response)
	if err != nil {
		t.Fatal(err)
	}
	roundTrip, err := FromWireExecuteFlowResponseV2(wireResponse)
	if err != nil || !reflect.DeepEqual(roundTrip, response) {
		t.Fatalf("Flow success round trip = %#v, %v", roundTrip, err)
	}

	wireResponse.Errors = []*runtimev1.FlowExecutionError{{
		Code: "POLICY_VALIDATION_ERROR", Message: "Correct RequestedValue before retrying.", Fields: []string{"RequestedValue"},
	}}
	if _, err := FromWireExecuteFlowResponse(wireResponse); err == nil {
		t.Fatal("legacy success-only decoder accepted a response carrying V2 errors")
	}
}

func TestFlowExecutionErrorWireDecodeRejectsMalformedCollections(t *testing.T) {
	receipts := toWireReceipts(
		mappingTestReceipts(flowsemantic.CapabilityName, 17),
		platformCapability(flowsemantic.CapabilityName),
	)
	for name, response := range map[string]*runtimev1.ExecuteFlowResponse{
		"nil entry": {
			ExecutionId: "execution-errors-1", Errors: []*runtimev1.FlowExecutionError{nil}, Receipts: receipts,
		},
		"too many entries": {
			ExecutionId: "execution-errors-1",
			Errors:      make([]*runtimev1.FlowExecutionError, flowsemantic.MaxExecutionErrors+1),
			Receipts:    receipts,
		},
		"too many fields": {
			ExecutionId: "execution-errors-1",
			Errors: []*runtimev1.FlowExecutionError{{
				Code: "POLICY_VALIDATION_ERROR", Message: "Correct RequestedValue before retrying.",
				Fields: make([]string, flowsemantic.MaxFieldsPerExecutionError+1),
			}},
			Receipts: receipts,
		},
	} {
		t.Run(name, func(t *testing.T) {
			if _, err := FromWireExecuteFlowResponseV2(response); err == nil {
				t.Fatal("malformed Flow execution errors were accepted")
			}
		})
	}
}

func TestFlowExecutionErrorRawWirePreflightBoundsNestedCollections(t *testing.T) {
	errorWithFields := func(count int) []byte {
		return appendWireBytesField(nil, 6, repeatedEmptyWireMessages(3, count))
	}

	if err := PreflightPlatformWirePayload(
		repeatedEmptyWireMessages(6, flowsemantic.MaxExecutionErrors),
		&runtimev1.ExecuteFlowResponse{},
	); err != nil {
		t.Fatalf("maximum Flow execution errors rejected by raw preflight: %v", err)
	}
	assertPlatformFailure(
		t,
		PreflightPlatformWirePayload(
			repeatedEmptyWireMessages(6, flowsemantic.MaxExecutionErrors+1),
			&runtimev1.ExecuteFlowResponse{},
		),
		commonsemantic.FailureInternal,
		"",
	)

	if err := PreflightPlatformWirePayload(
		errorWithFields(flowsemantic.MaxFieldsPerExecutionError),
		&runtimev1.ExecuteFlowResponse{},
	); err != nil {
		t.Fatalf("maximum Flow error fields rejected by raw preflight: %v", err)
	}
	assertPlatformFailure(
		t,
		PreflightPlatformWirePayload(
			errorWithFields(flowsemantic.MaxFieldsPerExecutionError+1),
			&runtimev1.ExecuteFlowResponse{},
		),
		commonsemantic.FailureInternal,
		"",
	)

	for name, executionError := range map[string][]byte{
		"code":    appendWireBytesField(nil, 1, make([]byte, flowsemantic.MaxIdentifierBytes+1)),
		"message": appendWireBytesField(nil, 2, make([]byte, flowsemantic.MaxErrorMessageBytes+1)),
		"field identifier": appendWireBytesField(
			nil, 3, make([]byte, commonsemantic.MaxFieldIdentifierBytes+1),
		),
	} {
		t.Run("oversized "+name, func(t *testing.T) {
			assertPlatformFailure(
				t,
				PreflightPlatformWirePayload(
					appendWireBytesField(nil, 6, executionError),
					&runtimev1.ExecuteFlowResponse{},
				),
				commonsemantic.FailureInternal,
				"",
			)
		})
	}
}
