package mapping

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"reflect"
	"testing"
	"time"

	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

func TestOutboundIntentWireRoundTrip(t *testing.T) {
	payload := []byte(`{"fields":{"name":"example"}}`)
	sum := sha256.Sum256(payload)
	digest := "sha256:" + hex.EncodeToString(sum[:])
	request := semantic.ListRequest{Limit: 1, MaxOperationSetVersion: semantic.OperationSetVersionV2}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	wireRequest, err := ToWireListPendingOutboundIntentsRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	decodedRequest, err := FromWireListPendingOutboundIntentsRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(decodedRequest, request) {
		t.Fatalf("request round trip = %#v, %v", decodedRequest, err)
	}
	intent := semantic.Intent{PayloadFormatVersion: 1, PayloadDigest: digest, CatalogRevision: 41, Operation: semantic.OperationUpsert, SourceObject: "Example", SourceRecordID: "row-1", ConnectorOwnerKey: "connector-a", FlowDeveloperName: "Outbound_Flow", FlowActionName: "Push_Action", ActionSlotID: "slot", DestinationSlotID: "destination", Bindings: []semantic.BindingReference{{Kind: catalogsemantic.BindingConnectorPushContract, OwnerPack: "owner", PackInstallationID: "install", PackPayloadDigest: "nexus-pack-sha256-v1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", BusinessKey: "key", DefinitionVersion: 1}}, CanonicalPayload: payload}
	bindOutboundLogicalEffectIdentityForMappingTest(t, &intent, "round-trip")
	response := semantic.ListResponse{Intents: []semantic.Intent{intent}, Receipts: semantic.Receipts{ProtocolVersion: "1.0", CatalogRevision: 41, SchemaGeneration: 7, EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: semantic.CapabilityName}}
	wireResponse, err := ToWireListPendingOutboundIntentsResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	decodedResponse, err := FromWireListPendingOutboundIntentsResponse(request, wireResponse)
	if err != nil || !reflect.DeepEqual(decodedResponse, response) {
		t.Fatalf("response round trip = %#v, %v", decodedResponse, err)
	}
	wireResponse.Intents[0].LogicalEffectRootId = ""
	if _, err := FromWireListPendingOutboundIntentsResponse(request, wireResponse); err == nil {
		t.Fatal("wire response without an effect root was accepted")
	}
	wireResponse.Intents[0].LogicalEffectRootId = intent.EffectRootID
	wireResponse.Intents[0].LogicalEffectGenerationId = ""
	if _, err := FromWireListPendingOutboundIntentsResponse(request, wireResponse); err == nil {
		t.Fatal("wire response without an effect generation was accepted")
	}
}

func TestOutboundIntentGenerationWireRoundTrip(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	request := semantic.GenerationRequest{AfterGeneration: 6}
	wireRequest, err := ToWireReadOutboundIntentGenerationRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	decodedRequest, err := FromWireReadOutboundIntentGenerationRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(decodedRequest, request) {
		t.Fatalf("request round trip = %#v, %v", decodedRequest, err)
	}
	response := semantic.GenerationResponse{
		Generation: 7,
		Receipts:   semantic.Receipts{ProtocolVersion: "1.0", CatalogRevision: 41, SchemaGeneration: 7, EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: semantic.CapabilityName},
	}
	wireResponse, err := ToWireReadOutboundIntentGenerationResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	decodedResponse, err := FromWireReadOutboundIntentGenerationResponse(request, wireResponse)
	if err != nil || !reflect.DeepEqual(decodedResponse, response) {
		t.Fatalf("response round trip = %#v, %v", decodedResponse, err)
	}
}

func TestOutboundResultWireRoundTrip(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	request := semantic.CommitResultRequest{
		IntentID:        "intent-1",
		PayloadDigest:   "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
		CatalogRevision: 41, IntegrationCommandID: "command-1", RemoteID: "remote-1",
	}
	wireRequest, err := ToWireCommitOutboundResultRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	decodedRequest, err := FromWireCommitOutboundResultRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(decodedRequest, request) {
		t.Fatalf("request round trip = %#v, %v", decodedRequest, err)
	}
	response := semantic.CommitResultResponse{
		IntentID: request.IntentID, Duplicate: true,
		Receipts: semantic.Receipts{ProtocolVersion: "1.0", CatalogRevision: 41, SchemaGeneration: 7, EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: semantic.CapabilityName},
	}
	wireResponse, err := ToWireCommitOutboundResultResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	decodedResponse, err := FromWireCommitOutboundResultResponse(request, wireResponse)
	if err != nil || !reflect.DeepEqual(decodedResponse, response) {
		t.Fatalf("response round trip = %#v, %v", decodedResponse, err)
	}
}

func TestOutboundNoEffectWireRoundTrip(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	request := semantic.CommitNoEffectRequest{
		IntentID:             "intent-1",
		PayloadDigest:        "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
		CatalogRevision:      41,
		IntegrationCommandID: "command-1",
		Reason:               semantic.NoEffectReasonCatalogAuthorityRetired,
	}
	wireRequest, err := ToWireCommitOutboundNoEffectRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	decodedRequest, err := FromWireCommitOutboundNoEffectRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(decodedRequest, request) {
		t.Fatalf("request round trip = %#v, %v", decodedRequest, err)
	}
	response := semantic.CommitNoEffectResponse{
		IntentID: request.IntentID, Reason: request.Reason, Duplicate: true,
		Receipts: semantic.Receipts{ProtocolVersion: "1.0", CatalogRevision: 42, SchemaGeneration: 7, EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: semantic.CapabilityName},
	}
	wireResponse, err := ToWireCommitOutboundNoEffectResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	decodedResponse, err := FromWireCommitOutboundNoEffectResponse(request, wireResponse)
	if err != nil || !reflect.DeepEqual(decodedResponse, response) {
		t.Fatalf("response round trip = %#v, %v", decodedResponse, err)
	}
	wireResponse.Reason = "UNKNOWN"
	if _, err := FromWireCommitOutboundNoEffectResponse(request, wireResponse); err == nil {
		t.Fatal("wire response with a request-mismatched no-effect reason was accepted")
	}
}

func TestTypedMailOutboundResultWireRoundTrip(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	result := []byte(`{"version":1,"outcome":"DELIVERED","remoteMessageId":"remote-1","senderAddress":"sender@example.com"}`)
	sum := sha256.Sum256(result)
	request := semantic.CommitResultRequest{
		IntentID: "intent-1", PayloadDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
		CatalogRevision: 41, IntegrationCommandID: "command-1",
		ResultFormatVersion: semantic.MailResultFormatV1,
		ResultDigest:        "sha256:" + hex.EncodeToString(sum[:]),
		CanonicalResult:     result,
	}
	wireRequest, err := ToWireCommitOutboundResultRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	decoded, err := FromWireCommitOutboundResultRequest(ctx, wireRequest)
	if err != nil || !reflect.DeepEqual(decoded, request) {
		t.Fatalf("typed result round trip = %#v, %v", decoded, err)
	}
}

func TestMailOutboundOperationWireRoundTrip(t *testing.T) {
	wireOperation, ok := toWireOutboundOperation(semantic.OperationSendMail)
	if !ok || wireOperation != runtimev1.OutboundIntentOperation_OUTBOUND_INTENT_OPERATION_SEND_MAIL {
		t.Fatalf("toWireOutboundOperation(SEND_MAIL) = %v, %v", wireOperation, ok)
	}
	operation, ok := fromWireOutboundOperation(wireOperation)
	if !ok || operation != semantic.OperationSendMail {
		t.Fatalf("fromWireOutboundOperation(SEND_MAIL) = %v, %v", operation, ok)
	}
}

func TestMailOutboundIntentIsNotEmittedToLegacyWireConsumer(t *testing.T) {
	payload := []byte(`{"version":1,"authorizedActorId":"user-1","toAddresses":["recipient@example.com"],"subject":"Hello","textBody":"Body"}`)
	sum := sha256.Sum256(payload)
	intent := semantic.Intent{
		IntentID: "intent-1", IdempotencyKey: "idem-1", PayloadFormatVersion: semantic.MailPayloadFormatV1,
		PayloadDigest: "sha256:" + hex.EncodeToString(sum[:]), CatalogRevision: 41,
		Operation: semantic.OperationSendMail, SourceObject: "Contact", SourceRecordID: "contact-1",
		ConnectorOwnerKey: "mail", FlowDeveloperName: "Send_Mail", FlowActionName: "Send_Email",
		ActionSlotID: "send-mail", DestinationSlotID: "mailbox",
		Bindings: []semantic.BindingReference{{
			Kind: catalogsemantic.BindingConnectorPushContract, OwnerPack: "mail",
			PackInstallationID: "install", PackPayloadDigest: "nexus-pack-sha256-v1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
			BusinessKey: "mail/send", DefinitionVersion: 1,
		}},
		CanonicalPayload: payload,
	}
	bindOutboundLogicalEffectIdentityForMappingTest(t, &intent, "mail")
	response := semantic.ListResponse{Intents: []semantic.Intent{intent}, Receipts: semantic.Receipts{
		ProtocolVersion: "1.0", CatalogRevision: 41, SchemaGeneration: 7,
		EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: semantic.CapabilityName,
	}}
	if _, err := ToWireListPendingOutboundIntentsResponse(semantic.ListRequest{Limit: 1}, response); err == nil {
		t.Fatal("legacy consumer request received a V2 mail operation")
	}
	if _, err := ToWireListPendingOutboundIntentsResponse(semantic.ListRequest{
		Limit: 1, MaxOperationSetVersion: semantic.OperationSetVersionV2,
	}, response); err != nil {
		t.Fatalf("V2 consumer request rejected mail operation: %v", err)
	}
}

func TestOutboundIntentStatusPreservesReviewedFailures(t *testing.T) {
	for _, failure := range []*commonsemantic.Failure{
		{Category: commonsemantic.FailureInvalidRequest, InvalidComponent: commonsemantic.InvalidComponentCommandData},
		{Category: commonsemantic.FailureInvalidRequest, InvalidComponent: commonsemantic.InvalidComponentDeadline},
		{Category: commonsemantic.FailureInvalidRequest, InvalidComponent: commonsemantic.InvalidComponentResponse},
		{Category: commonsemantic.FailureResourceExhausted, LimitKind: commonsemantic.LimitSemanticRequest, Maximum: semantic.MaxSemanticRequestBytes},
		{Category: commonsemantic.FailureResourceExhausted, LimitKind: commonsemantic.LimitSemanticResponse, Maximum: semantic.MaxSemanticResponseBytes},
		{Category: commonsemantic.FailureResourceExhausted, LimitKind: commonsemantic.LimitWireFrame, Maximum: envelope.MaxResponseWireBytes},
	} {
		decoded := FromPlatformStatusError(
			ToPlatformStatusError(failure, semantic.CapabilityName),
			semantic.CapabilityName,
		)
		var roundTrip *commonsemantic.Failure
		if !errors.As(decoded, &roundTrip) || !reflect.DeepEqual(roundTrip, failure) {
			t.Fatalf("failure round trip = %#v, want %#v", decoded, failure)
		}
	}

	foreign := &commonsemantic.Failure{
		Category:         commonsemantic.FailureInvalidRequest,
		InvalidComponent: commonsemantic.InvalidComponentObjectID,
	}
	decoded := FromPlatformStatusError(
		ToPlatformStatusError(foreign, semantic.CapabilityName),
		semantic.CapabilityName,
	)
	var sanitized *commonsemantic.Failure
	if !errors.As(decoded, &sanitized) || sanitized.Category != commonsemantic.FailureInternal {
		t.Fatalf("foreign failure = %#v, want INTERNAL", decoded)
	}
}

func TestOutboundIntentListResponseFailsBeforeTransportEnvelopeOverflow(t *testing.T) {
	request := semantic.ListRequest{Limit: 2}
	intents := make([]semantic.Intent, 0, 2)
	for index := 0; index < 2; index++ {
		payload := bytes.Repeat([]byte{byte('a' + index)}, semantic.MaxPayloadBytes)
		sum := sha256.Sum256(payload)
		intent := semantic.Intent{
			PayloadFormatVersion: 1, PayloadDigest: "sha256:" + hex.EncodeToString(sum[:]),
			CatalogRevision: 41, Operation: semantic.OperationUpsert,
			SourceObject: "Example", SourceRecordID: "row-" + string(rune('1'+index)),
			ConnectorOwnerKey: "connector-a", FlowDeveloperName: "Outbound_Flow",
			FlowActionName: "Push_Action", ActionSlotID: "slot", DestinationSlotID: "destination",
			Bindings: []semantic.BindingReference{{
				Kind: catalogsemantic.BindingConnectorPushContract, OwnerPack: "owner",
				PackInstallationID: "install", PackPayloadDigest: "nexus-pack-sha256-v1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
				BusinessKey: "key", DefinitionVersion: 1,
			}},
			CanonicalPayload: payload,
		}
		bindOutboundLogicalEffectIdentityForMappingTest(t, &intent, "large-"+string(rune('1'+index)))
		intents = append(intents, intent)
	}
	response := semantic.ListResponse{Intents: intents, Receipts: semantic.Receipts{
		ProtocolVersion: "1.0", CatalogRevision: 41, SchemaGeneration: 7,
		EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: semantic.CapabilityName,
	}}
	if err := semantic.ValidateListResponseFor(request, response); err != nil {
		t.Fatalf("test response must be semantically valid before wire mapping: %v", err)
	}
	_, err := ToWireListPendingOutboundIntentsResponse(request, response)
	assertOutboundWireFrameFailure(t, err)

	wireResponse := &runtimev1.ListPendingOutboundIntentsResponse{
		Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName)),
		Intents:  []*runtimev1.OutboundIntent{toWireOutboundIntent(intents[0]), toWireOutboundIntent(intents[1])},
	}
	if got := len(wireResponse.GetIntents()); got != 2 {
		t.Fatalf("wire test setup intents = %d", got)
	}
	_, err = FromWireListPendingOutboundIntentsResponse(request, wireResponse)
	assertOutboundWireFrameFailure(t, err)
}

func assertOutboundWireFrameFailure(t *testing.T, err error) {
	t.Helper()
	var failure *commonsemantic.Failure
	if !errors.As(err, &failure) || failure.Category != commonsemantic.FailureResourceExhausted ||
		failure.LimitKind != commonsemantic.LimitWireFrame || failure.Maximum != envelope.MaxResponseWireBytes {
		t.Fatalf("wire-frame error = %#v", err)
	}
}

func bindOutboundLogicalEffectIdentityForMappingTest(t *testing.T, intent *semantic.Intent, seed string) {
	t.Helper()
	intent.EffectRootID = "effect-root-" + seed
	intent.EffectGenerationID = "generation-" + seed
	intentID, idempotencyKey, err := semantic.LogicalEffectIdentity(
		semantic.LogicalEffectScope{
			OrgID: "org-1", AppInstallationID: "installation-1", AuthorizedWorkloadID: "integration-1",
		},
		intent.EffectRootID,
		intent.ActionSlotID,
		intent.EffectGenerationID,
	)
	if err != nil {
		t.Fatalf("LogicalEffectIdentity: %v", err)
	}
	intent.IntentID = intentID
	intent.IdempotencyKey = idempotencyKey
}
