package automation

import (
	"context"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

// syntheticApproverNotificationFlow mirrors the approval-notification shape
// from issue #3666: a Loop over the bridge-injected Approval.ApproverUserIds
// []string, one per-approver create in the loop body, and a post-loop create
// that proves the no-more-values connector was reached.
func syntheticApproverNotificationFlow() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			Connector: &models.FlowConnector{TargetReference: "loop_approvers"},
		},
		Loops: []models.FlowLoop{{
			Name:                       "loop_approvers",
			CollectionReference:        "Approval.ApproverUserIds",
			AssignNextValueToReference: "currentApproverId",
			NextValueConnector:         &models.FlowConnector{TargetReference: "create_notification"},
			NoMoreValuesConnector:      &models.FlowConnector{TargetReference: "create_summary"},
		}},
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "create_notification",
				Object: "SyntheticNotification",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "RecipientUserId", Value: elementReferenceValue("currentApproverId")},
				},
				Connector: &models.FlowConnector{TargetReference: "loop_approvers"},
			},
			{
				Name:   "create_summary",
				Object: "SyntheticSummary",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Name", Value: stringValue("post-loop summary")},
				},
			},
		},
	}
}

func TestLoopIteratesApprovalBridgeApproverUserIds(t *testing.T) {
	for _, tc := range []struct {
		name      string
		approvers []string
	}{
		// copyApprovalStringSlice returns a typed-nil []string for empty
		// input; the loop must take the no-more-values path, not fault.
		{name: "zero approvers typed-nil slice", approvers: nil},
		{name: "one approver", approvers: []string{"user-1"}},
		{name: "many approvers", approvers: []string{"user-1", "user-2", "user-3"}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			persistence := &transactionalFanoutPersistence{}
			exec := fanoutExecutor(syntheticApproverNotificationFlow(), persistence)

			result, err := exec.ExecuteFlow(
				WithTransientFlowInterviews(context.Background()),
				"SyntheticApproverNotification",
				map[string]interface{}{
					"Approval": map[string]interface{}{
						"ApproverUserIds": copyApprovalStringSlice(tc.approvers),
					},
				},
				newPreviewUser(),
			)
			if err != nil {
				t.Fatalf("ExecuteFlow returned error: %v", err)
			}
			if result == nil || !result.IsSuccess {
				t.Fatalf("ExecuteFlow result = %#v; want success", result)
			}
			wantCreates := len(tc.approvers) + 1
			if got := len(persistence.committed); got != wantCreates {
				t.Fatalf("committed inserts = %d (%+v); want %d (one notification per approver plus post-loop summary)", got, persistence.committed, wantCreates)
			}
			for idx, wantRecipient := range tc.approvers {
				notification := persistence.committed[idx]
				if notification.object != "SyntheticNotification" {
					t.Fatalf("committed[%d].object = %q; want SyntheticNotification", idx, notification.object)
				}
				if got := notification.record["RecipientUserId"]; got != wantRecipient {
					t.Fatalf("notification %d RecipientUserId = %v; want %q", idx, got, wantRecipient)
				}
			}
			summary := persistence.committed[len(tc.approvers)]
			if summary.object != "SyntheticSummary" {
				t.Fatalf("last committed object = %q; want SyntheticSummary via no-more-values connector", summary.object)
			}
		})
	}
}

// syntheticPrimitiveCollectionLoopFlow loops over a top-level context key
// holding a primitive slice and records each element.
func syntheticPrimitiveCollectionLoopFlow() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			Connector: &models.FlowConnector{TargetReference: "loop_values"},
		},
		Loops: []models.FlowLoop{{
			Name:                       "loop_values",
			CollectionReference:        "values",
			AssignNextValueToReference: "currentValue",
			NextValueConnector:         &models.FlowConnector{TargetReference: "create_item"},
		}},
		RecordCreates: []models.FlowRecordCreate{{
			Name:   "create_item",
			Object: "SyntheticItem",
			InputAssignments: []models.FlowInputAssignment{
				{Field: "Value", Value: elementReferenceValue("currentValue")},
			},
			Connector: &models.FlowConnector{TargetReference: "loop_values"},
		}},
	}
}

func TestLoopIteratesPrimitiveSliceCollections(t *testing.T) {
	// The same primitive slice set cloneFlowContextValue preserves; each must
	// be iterable rather than hitting the fail-closed default branch.
	for _, tc := range []struct {
		name       string
		collection interface{}
		wantValues []interface{}
	}{
		{name: "[]string", collection: []string{"a", "b"}, wantValues: []interface{}{"a", "b"}},
		{name: "[]bool", collection: []bool{true, false}, wantValues: []interface{}{true, false}},
		{name: "[]int", collection: []int{1, 2}, wantValues: []interface{}{1, 2}},
		{name: "[]int64", collection: []int64{3, 4}, wantValues: []interface{}{int64(3), int64(4)}},
		{name: "[]float64", collection: []float64{1.5, 2.5}, wantValues: []interface{}{1.5, 2.5}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			persistence := &transactionalFanoutPersistence{}
			exec := fanoutExecutor(syntheticPrimitiveCollectionLoopFlow(), persistence)

			result, err := exec.ExecuteFlow(
				WithTransientFlowInterviews(context.Background()),
				"SyntheticPrimitiveCollectionLoop",
				map[string]interface{}{"values": tc.collection},
				newPreviewUser(),
			)
			if err != nil {
				t.Fatalf("ExecuteFlow returned error: %v", err)
			}
			if result == nil || !result.IsSuccess {
				t.Fatalf("ExecuteFlow result = %#v; want success", result)
			}
			if got, want := len(persistence.committed), len(tc.wantValues); got != want {
				t.Fatalf("committed inserts = %d (%+v); want %d", got, persistence.committed, want)
			}
			for idx, want := range tc.wantValues {
				if got := persistence.committed[idx].record["Value"]; got != want {
					t.Fatalf("item %d Value = %v (%T); want %v (%T)", idx, got, got, want, want)
				}
			}
		})
	}
}
