package automation

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"strings"
	"testing"
	"time"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	outboundintentv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
)

type runtimeOutboundMailInsertCall struct {
	objectName      string
	record          models.SObject
	securityContext constants.SecurityContext
	userID          string
	standardAction  bool
	internalScopes  bool
	protectedFlow   bool
	suppressedRow   string
}

type captureRuntimeOutboundMailCompletionPersistence struct {
	recordingPersistence
	calls      []runtimeOutboundMailInsertCall
	failObject string
}

func (p *captureRuntimeOutboundMailCompletionPersistence) Insert(
	ctx context.Context,
	objectName string,
	record models.SObject,
	user *models.UserSession,
) (models.SObject, error) {
	copy := make(models.SObject, len(record))
	for key, value := range record {
		copy[key] = value
	}
	userID := ""
	if user != nil {
		userID = user.ID
	}
	p.calls = append(p.calls, runtimeOutboundMailInsertCall{
		objectName: objectName, record: copy,
		securityContext: constants.GetSecurityContext(ctx), userID: userID,
		standardAction: constants.HasStandardActionExecutionContext(ctx),
		internalScopes: constants.HasStandardActionInternalAccessScopes(ctx),
		protectedFlow:  constants.HasStandardActionProtectedFlowExecution(ctx),
		suppressedRow: func() string {
			value, _ := ctx.Value(constants.ContextKeySuppressRecordTriggeredAutomation).(string)
			return value
		}(),
	})
	if objectName == p.failObject {
		return nil, errors.New("injected completion write failure")
	}
	return copy, nil
}

func TestRuntimeOutboundMailDeliveredResultCreatesOnlyCatalogOwnedCompletion(t *testing.T) {
	view := runtimeOutboundIntentTestViewWithMailRoute(t)
	intent := runtimeOutboundMailResultTestIntent(t, view, []string{"recipient@example.com"})
	request := runtimeOutboundMailResultTestRequest(t, intent, outboundintentv1.MailDeliveryResultV1{
		Version: outboundintentv1.MailResultFormatV1, Outcome: outboundintentv1.MailOutcomeDelivered,
		RemoteMessageID: "remote-message-1", SenderAddress: "sender@example.com",
	})
	persistence := &captureRuntimeOutboundMailCompletionPersistence{}
	executor := &FlowExecutor{persistence: persistence}
	ctx := constants.WithStandardActionExecutionContext(t.Context(), "send-record-email")
	ctx = constants.WithStandardActionInternalAccessScopes(ctx, "send-record-email", []constants.StandardActionInternalAccessScope{{
		ObjectAPIName: "Contact", Operation: constants.PermRead,
	}})
	ctx = constants.WithStandardActionProtectedFlowExecution(ctx, "Std_SendRecordEmail")

	if err := executor.commitRuntimeOutboundResultProjection(
		ctx, view, "integration-service", intent, request,
	); err != nil {
		t.Fatalf("commit Runtime mail result: %v", err)
	}
	if len(persistence.calls) != 2 {
		t.Fatalf("completion writes = %#v, want exactly two", persistence.calls)
	}
	activityCall, messageCall := persistence.calls[0], persistence.calls[1]
	if activityCall.objectName != "ActivityLog" || messageCall.objectName != "MessageLog" {
		t.Fatalf("completion objects = %q, %q", activityCall.objectName, messageCall.objectName)
	}
	activityID := runtimeOutboundMailCompletionID(intent.IntentID, "activity")
	messageID := runtimeOutboundMailCompletionID(intent.IntentID, "message")
	if activityCall.record[constants.FieldID] != activityID ||
		activityCall.record["SourceRecordId"] != intent.SourceRecordID ||
		activityCall.record["Subject"] != "Mail" ||
		activityCall.record["Status"] != "Completed" ||
		activityCall.record["Priority"] != "Normal" ||
		activityCall.record["Body"] != "Plain text body" ||
		activityCall.record["OwnerId"] != "user-1" ||
		activityCall.record["Type"] != "Mail" ||
		activityCall.record["Subtype"] != "Outbound" {
		t.Fatalf("activity completion = %#v", activityCall.record)
	}
	if messageCall.record[constants.FieldID] != messageID ||
		messageCall.record["ActivityId"] != activityID ||
		messageCall.record["FromAddress"] != "sender@example.com" ||
		messageCall.record["ToAddress"] != "recipient@example.com" ||
		messageCall.record["Subject"] != "Subject" ||
		messageCall.record["TextBody"] != "Plain text body" ||
		messageCall.record["Incoming"] != false ||
		messageCall.record["Status"] != "Delivered" {
		t.Fatalf("message completion = %#v", messageCall.record)
	}
	if _, err := time.Parse(time.DateOnly, activityCall.record["ActivityDate"].(string)); err != nil {
		t.Fatalf("activity date = %#v: %v", activityCall.record["ActivityDate"], err)
	}
	if _, err := time.Parse(time.RFC3339Nano, messageCall.record["MessageDate"].(string)); err != nil {
		t.Fatalf("message date = %#v: %v", messageCall.record["MessageDate"], err)
	}
	for _, call := range persistence.calls {
		if call.securityContext != constants.SystemContext || call.userID != "user-1" ||
			call.standardAction || call.internalScopes || call.protectedFlow {
			t.Fatalf("completion write authority leaked caller context: %#v", call)
		}
	}
	if activityCall.suppressedRow != "ActivityLog/"+activityID ||
		messageCall.suppressedRow != "MessageLog/"+messageID {
		t.Fatalf(
			"completion trigger suppression = %q, %q, want exact completion rows",
			activityCall.suppressedRow,
			messageCall.suppressedRow,
		)
	}
}

func TestRuntimeOutboundMailV2DeliveredResultProjectsRecipientClassesAndHTML(t *testing.T) {
	view := runtimeOutboundIntentTestViewWithMailV2Route(t)
	payload := outboundintentv1.MailDeliveryV2{
		Version: outboundintentv1.MailPayloadFormatV2, AuthorizedActorID: "user-1",
		ToAddresses: []string{"recipient@example.com"}, CcAddresses: []string{"cc@example.com"},
		BccAddresses: []string{"bcc@example.com"}, Subject: "Subject",
		TextBody: "Plain text body", HTMLBody: "<p>Rich body</p>",
	}
	intent := runtimeOutboundMailResultTestIntentV2(t, view, payload)
	request := runtimeOutboundMailResultTestRequest(t, intent, outboundintentv1.MailDeliveryResultV1{
		Version: outboundintentv1.MailResultFormatV1, Outcome: outboundintentv1.MailOutcomeDelivered,
		RemoteMessageID: "remote-message-1", SenderAddress: "sender@example.com",
	})
	persistence := &captureRuntimeOutboundMailCompletionPersistence{}
	executor := &FlowExecutor{persistence: persistence}
	if err := executor.commitRuntimeOutboundResultProjection(
		t.Context(), view, "integration-service", intent, request,
	); err != nil {
		t.Fatalf("commit Runtime mail v2 result: %v", err)
	}
	if len(persistence.calls) != 2 {
		t.Fatalf("mail v2 completion writes = %#v", persistence.calls)
	}
	message := persistence.calls[1].record
	if message["CcAddress"] != "cc@example.com" || message["BccAddress"] != "bcc@example.com" ||
		message["HtmlBody"] != "<p>Rich body</p>" {
		t.Fatalf("mail v2 completion = %#v", message)
	}
}

func TestRuntimeOutboundMailV3DeliveredResultProjectsAttachmentRelationship(t *testing.T) {
	view := runtimeOutboundIntentTestViewWithMailV2Route(t)
	payload := outboundintentv1.MailDeliveryV3{
		Version: outboundintentv1.MailPayloadFormatV3, AuthorizedActorID: "user-1",
		ToAddresses: []string{"recipient@example.com"}, Subject: "Subject", TextBody: "Body",
		Attachments: []outboundintentv1.MailAttachmentV1{
			{FileID: "file-1", FileName: "one.pdf", ContentType: "application/pdf", ContentLength: 1, ContentDigest: "sha256:" + strings.Repeat("a", 64)},
			{FileID: "file-2", FileName: "two.pdf", ContentType: "application/pdf", ContentLength: 1, ContentDigest: "sha256:" + strings.Repeat("b", 64)},
		},
	}
	intent := runtimeOutboundMailResultTestIntentV3(t, view, payload)
	request := runtimeOutboundMailResultTestRequest(t, intent, outboundintentv1.MailDeliveryResultV1{
		Version: outboundintentv1.MailResultFormatV1, Outcome: outboundintentv1.MailOutcomeDelivered,
		RemoteMessageID: "remote-message-1", SenderAddress: "sender@example.com",
	})
	persistence := &captureRuntimeOutboundMailCompletionPersistence{}
	if err := (&FlowExecutor{persistence: persistence}).commitRuntimeOutboundResultProjection(
		t.Context(), view, "integration-service", intent, request,
	); err != nil {
		t.Fatal(err)
	}
	if len(persistence.calls) != 2 || persistence.calls[1].record["AttachmentIds"] != "file-1;file-2" {
		t.Fatalf("completion = %#v", persistence.calls)
	}
}

func TestRuntimeOutboundMailNonDeliveredResultStillRequiresExactRoute(t *testing.T) {
	view := runtimeOutboundIntentTestViewWithMailRoute(t)
	baseIntent := runtimeOutboundMailResultTestIntent(t, view, []string{"recipient@example.com"})
	requestFor := func(t *testing.T, intent outboundintentv1.Intent) outboundintentv1.CommitResultRequest {
		t.Helper()
		return runtimeOutboundMailResultTestRequest(t, intent, outboundintentv1.MailDeliveryResultV1{
			Version: outboundintentv1.MailResultFormatV1, Outcome: outboundintentv1.MailOutcomeRejected,
			FailureCode: outboundintentv1.MailFailureNotConnected,
		})
	}

	t.Run("exact rejected result closes without success records", func(t *testing.T) {
		persistence := &captureRuntimeOutboundMailCompletionPersistence{}
		executor := &FlowExecutor{persistence: persistence}
		if err := executor.commitRuntimeOutboundResultProjection(
			t.Context(), view, "integration-service", baseIntent, requestFor(t, baseIntent),
		); err != nil {
			t.Fatalf("commit rejected result: %v", err)
		}
		if len(persistence.calls) != 0 {
			t.Fatalf("rejected result created success records: %#v", persistence.calls)
		}
	})

	for name, mutate := range map[string]func(*outboundintentv1.Intent, *string){
		"foreign binding": func(intent *outboundintentv1.Intent, _ *string) {
			intent.Bindings[0].BusinessKey = "connector_a/foreign"
		},
		"foreign route": func(intent *outboundintentv1.Intent, _ *string) {
			intent.FlowActionName = "ForeignAction"
		},
		"foreign workload": func(_ *outboundintentv1.Intent, workload *string) {
			*workload = "foreign-integration"
		},
	} {
		t.Run(name, func(t *testing.T) {
			intent := baseIntent
			intent.Bindings = append([]outboundintentv1.BindingReference(nil), baseIntent.Bindings...)
			workload := "integration-service"
			mutate(&intent, &workload)
			persistence := &captureRuntimeOutboundMailCompletionPersistence{}
			executor := &FlowExecutor{persistence: persistence}
			if err := executor.commitRuntimeOutboundResultProjection(
				t.Context(), view, workload, intent, requestFor(t, intent),
			); err == nil {
				t.Fatal("foreign rejected result was accepted")
			}
			if len(persistence.calls) != 0 {
				t.Fatalf("foreign result created records: %#v", persistence.calls)
			}
		})
	}
}

func TestRuntimeOutboundMailCompletionFailsClosedForMultipleRecipientsOrPartialWrite(t *testing.T) {
	view := runtimeOutboundIntentTestViewWithMailRoute(t)
	delivered := outboundintentv1.MailDeliveryResultV1{
		Version: outboundintentv1.MailResultFormatV1, Outcome: outboundintentv1.MailOutcomeDelivered,
		RemoteMessageID: "remote-message-1", SenderAddress: "sender@example.com",
	}
	t.Run("multiple recipients", func(t *testing.T) {
		intent := runtimeOutboundMailResultTestIntent(t, view, []string{"one@example.com", "two@example.com"})
		persistence := &captureRuntimeOutboundMailCompletionPersistence{}
		executor := &FlowExecutor{persistence: persistence}
		if err := executor.commitRuntimeOutboundResultProjection(
			t.Context(), view, "integration-service", intent,
			runtimeOutboundMailResultTestRequest(t, intent, delivered),
		); err == nil {
			t.Fatal("multi-recipient intent reached single-record completion")
		}
		if len(persistence.calls) != 0 {
			t.Fatalf("multi-recipient intent created records: %#v", persistence.calls)
		}
	})
	t.Run("message insert failure", func(t *testing.T) {
		intent := runtimeOutboundMailResultTestIntent(t, view, []string{"recipient@example.com"})
		persistence := &captureRuntimeOutboundMailCompletionPersistence{failObject: "MessageLog"}
		executor := &FlowExecutor{persistence: persistence}
		if err := executor.commitRuntimeOutboundResultProjection(
			t.Context(), view, "integration-service", intent,
			runtimeOutboundMailResultTestRequest(t, intent, delivered),
		); err == nil {
			t.Fatal("partial completion write returned success")
		}
		if len(persistence.calls) != 2 {
			t.Fatalf("completion stopped outside the caller transaction: %#v", persistence.calls)
		}
	})
}

func runtimeOutboundMailResultTestIntent(
	t *testing.T,
	view *catalogsnapshot.RuntimeMetadataView,
	recipients []string,
) outboundintentv1.Intent {
	t.Helper()
	payload, err := json.Marshal(outboundintentv1.MailDeliveryV1{
		Version: outboundintentv1.MailPayloadFormatV1, AuthorizedActorID: "user-1",
		ToAddresses: recipients, Subject: "Subject", TextBody: "Plain text body",
	})
	if err != nil {
		t.Fatal(err)
	}
	return runtimeOutboundMailResultTestIntentWithPayload(
		t, view, outboundintentv1.MailPayloadFormatV1, payload,
	)
}

func runtimeOutboundMailResultTestIntentV2(
	t *testing.T,
	view *catalogsnapshot.RuntimeMetadataView,
	payload outboundintentv1.MailDeliveryV2,
) outboundintentv1.Intent {
	t.Helper()
	canonical, err := json.Marshal(payload)
	if err != nil {
		t.Fatal(err)
	}
	return runtimeOutboundMailResultTestIntentWithPayload(
		t, view, outboundintentv1.MailPayloadFormatV2, canonical,
	)
}

func runtimeOutboundMailResultTestIntentV3(
	t *testing.T,
	view *catalogsnapshot.RuntimeMetadataView,
	payload outboundintentv1.MailDeliveryV3,
) outboundintentv1.Intent {
	t.Helper()
	canonical, err := json.Marshal(payload)
	if err != nil {
		t.Fatal(err)
	}
	return runtimeOutboundMailResultTestIntentWithPayload(
		t, view, outboundintentv1.MailPayloadFormatV3, canonical,
	)
}

func runtimeOutboundMailResultTestIntentWithPayload(
	t *testing.T,
	view *catalogsnapshot.RuntimeMetadataView,
	formatVersion uint32,
	payload []byte,
) outboundintentv1.Intent {
	t.Helper()
	var route catalogsnapshot.OutboundIntentRoute
	for _, candidate := range view.OutboundIntentRoutes() {
		if candidate.Operation == outboundintentv1.OperationSendMail {
			route = candidate
			break
		}
	}
	digest := sha256.Sum256(payload)
	bindings := make([]outboundintentv1.BindingReference, len(route.Bindings))
	for index, binding := range route.Bindings {
		bindings[index] = outboundintentv1.BindingReference{
			Kind: binding.Kind, OwnerPack: binding.OwnerPack,
			PackInstallationID: binding.PackInstallationID,
			PackPayloadDigest:  binding.PackPayloadDigest,
			BusinessKey:        binding.BusinessKey, DefinitionVersion: binding.DefinitionVersion,
		}
	}
	intent := outboundintentv1.Intent{
		IntentID: "mail-intent-1", IdempotencyKey: "mail-request-1",
		PayloadFormatVersion: formatVersion,
		PayloadDigest:        "sha256:" + hex.EncodeToString(digest[:]),
		CatalogRevision:      uint64(view.Lineage().CatalogRevision),
		Operation:            outboundintentv1.OperationSendMail,
		SourceObject:         route.SourceObject, SourceRecordID: "003-mail",
		ConnectorOwnerKey: route.ConnectorOwnerKey,
		FlowDeveloperName: route.FlowDeveloperName, FlowActionName: route.FlowActionName,
		ActionSlotID: route.ActionSlotID, DestinationSlotID: route.DestinationSlotID,
		Bindings: bindings, CanonicalPayload: payload,
	}
	if err := validateRuntimeOutboundMailIntent(intent); err != nil {
		t.Fatalf("mail intent fixture: %v", err)
	}
	return intent
}

func runtimeOutboundMailResultTestRequest(
	t *testing.T,
	intent outboundintentv1.Intent,
	result outboundintentv1.MailDeliveryResultV1,
) outboundintentv1.CommitResultRequest {
	t.Helper()
	canonical, err := json.Marshal(result)
	if err != nil {
		t.Fatal(err)
	}
	digest := sha256.Sum256(canonical)
	request := outboundintentv1.CommitResultRequest{
		IntentID: intent.IntentID, PayloadDigest: intent.PayloadDigest,
		CatalogRevision: intent.CatalogRevision, IntegrationCommandID: "integration-command-1",
		ResultFormatVersion: outboundintentv1.MailResultFormatV1,
		ResultDigest:        "sha256:" + hex.EncodeToString(digest[:]), CanonicalResult: canonical,
	}
	if err := outboundintentv1.ValidateCommitResultRequestForIntent(intent, request); err != nil {
		t.Fatalf("mail result fixture: %v", err)
	}
	return request
}
