package connector_inbound_routing

import (
	"encoding/json"
	"strings"
	"testing"
	"time"
)

func TestNormalizeRouteExecutionSpecAllowsLatestBySource(t *testing.T) {
	spec, err := NormalizeRouteExecutionSpec(RouteExecutionSpec{
		FlowDeveloperName: "ContactPropertyChange",
		InvocationMode:    InvocationModeLatestBySource,
	})
	if err != nil {
		t.Fatalf("NormalizeRouteExecutionSpec: %v", err)
	}
	if spec.InvocationMode != InvocationModeLatestBySource {
		t.Fatalf("InvocationMode = %q, want %q", spec.InvocationMode, InvocationModeLatestBySource)
	}
}

func TestNormalizeRouteExecutionSpecAllowsSettledLatestBySourceFullRecordPreservePayload(t *testing.T) {
	spec, err := NormalizeRouteExecutionSpec(RouteExecutionSpec{
		FlowDeveloperName: "DeltaFullRecordFlow",
		InvocationMode:    InvocationModeLatestBySourceFullRecordPreservePayloadSettled,
	})
	if err != nil {
		t.Fatalf("NormalizeRouteExecutionSpec: %v", err)
	}
	if spec.InvocationMode != InvocationModeLatestBySourceFullRecordPreservePayloadSettled {
		t.Fatalf("InvocationMode = %q, want %q", spec.InvocationMode, InvocationModeLatestBySourceFullRecordPreservePayloadSettled)
	}
	if got := SourceSettleDelay(spec.InvocationMode); got != 5*time.Second {
		t.Fatalf("SourceSettleDelay = %s, want 5s", got)
	}
	if got := SourceSettleDelay(InvocationModeLatestBySourceFullRecordPreservePayload); got != 0 {
		t.Fatalf("existing mode SourceSettleDelay = %s, want 0", got)
	}
}

func TestNormalizeRouteExecutionSpecAllowsLatestBySourceFullRecord(t *testing.T) {
	spec, err := NormalizeRouteExecutionSpec(RouteExecutionSpec{
		FlowDeveloperName: "ContactPropertyChange",
		InvocationMode:    InvocationModeLatestBySourceFullRecord,
	})
	if err != nil {
		t.Fatalf("NormalizeRouteExecutionSpec: %v", err)
	}
	if spec.InvocationMode != InvocationModeLatestBySourceFullRecord {
		t.Fatalf("InvocationMode = %q, want %q", spec.InvocationMode, InvocationModeLatestBySourceFullRecord)
	}
}

func TestNormalizeRouteExecutionSpecAllowsLatestBySourceFullRecordPreservePayload(t *testing.T) {
	spec, err := NormalizeRouteExecutionSpec(RouteExecutionSpec{
		FlowDeveloperName: "DeltaFullRecordFlow",
		InvocationMode:    InvocationModeLatestBySourceFullRecordPreservePayload,
	})
	if err != nil {
		t.Fatalf("NormalizeRouteExecutionSpec: %v", err)
	}
	if spec.InvocationMode != InvocationModeLatestBySourceFullRecordPreservePayload {
		t.Fatalf("InvocationMode = %q, want %q", spec.InvocationMode, InvocationModeLatestBySourceFullRecordPreservePayload)
	}
}

// TestNormalizeRouteExecutionSpecRejectsFlowExecutionFilterModes: every
// full-record invocation mode rejects combination with a flowExecutionFilter.
func TestNormalizeRouteExecutionSpecRejectsFlowExecutionFilterModes(t *testing.T) {
	tests := []struct {
		name string
		spec RouteExecutionSpec
	}{
		{
			name: "LatestBySourceFullRecord",
			spec: RouteExecutionSpec{
				FlowDeveloperName: "ContactPropertyChange",
				InvocationMode:    InvocationModeLatestBySourceFullRecord,
				FlowExecutionFilter: &PayloadValueFilter{
					Path:      ".propertyName",
					EqualsAny: []string{"email"},
				},
			},
		},
		{
			name: "LatestBySourceFullRecordPreservePayload",
			spec: RouteExecutionSpec{
				FlowDeveloperName: "DeltaFullRecordFlow",
				InvocationMode:    InvocationModeLatestBySourceFullRecordPreservePayload,
				FlowExecutionFilter: &PayloadValueFilter{
					Path:      ".changedField",
					EqualsAny: []string{"email"},
				},
			},
		},
		{
			name: "SettledMode",
			spec: RouteExecutionSpec{
				FlowDeveloperName: "DeltaFullRecordFlow",
				InvocationMode:    InvocationModeLatestBySourceFullRecordPreservePayloadSettled,
				FlowExecutionFilter: &PayloadValueFilter{
					Path:      ".changedField",
					EqualsAny: []string{"email"},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NormalizeRouteExecutionSpec(tt.spec)
			if err == nil {
				t.Fatal("NormalizeRouteExecutionSpec succeeded, want error")
			}
			if got, want := err.Error(), "cannot be combined with flowExecutionFilter"; !strings.Contains(got, want) {
				t.Fatalf("error = %q, want %q", got, want)
			}
		})
	}
}

func TestPayloadValueFilterMatchesScalarsAndSkipsOnlyConcreteMiss(t *testing.T) {
	filter := &PayloadValueFilter{Path: ".propertyName", EqualsAny: []string{"email", "hubspot_owner_id"}}
	tests := []struct {
		name    string
		payload json.RawMessage
		want    bool
	}{
		{name: "allowed", payload: json.RawMessage(`{"propertyName":"email"}`), want: true},
		{name: "concrete miss", payload: json.RawMessage(`{"propertyName":"firstname"}`), want: false},
		{name: "missing fails closed", payload: json.RawMessage(`{"other":"firstname"}`), want: true},
		{name: "null fails closed", payload: json.RawMessage(`{"propertyName":null}`), want: true},
		{name: "object fails closed", payload: json.RawMessage(`{"propertyName":{"name":"email"}}`), want: true},
		{name: "array fails closed", payload: json.RawMessage(`{"propertyName":["email"]}`), want: true},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := filter.Matches(tc.payload)
			if err != nil {
				t.Fatalf("Matches: %v", err)
			}
			if got != tc.want {
				t.Fatalf("Matches = %v, want %v", got, tc.want)
			}
		})
	}
}

func TestNormalizeQueueClass(t *testing.T) {
	tests := []struct {
		name string
		in   string
		want string
	}{
		{name: "blank defaults", in: "", want: QueueClassDefault},
		{name: "interactive", in: " INTERACTIVE ", want: QueueClassInteractive},
		{name: "default", in: " DEFAULT ", want: QueueClassDefault},
		{name: "bulk", in: " bulk ", want: QueueClassBulk},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := NormalizeQueueClass(tc.in)
			if err != nil {
				t.Fatalf("NormalizeQueueClass: %v", err)
			}
			if got != tc.want {
				t.Fatalf("NormalizeQueueClass = %q, want %q", got, tc.want)
			}
		})
	}
	if _, err := NormalizeQueueClass("latency"); err == nil {
		t.Fatal("NormalizeQueueClass accepted unknown class")
	}
}

func TestNormalizeRouteExecutionSpecAllowsQueueClasses(t *testing.T) {
	for _, queueClass := range []string{QueueClassInteractive, QueueClassDefault, QueueClassBulk} {
		t.Run(queueClass, func(t *testing.T) {
			spec, err := NormalizeRouteExecutionSpec(RouteExecutionSpec{
				FlowDeveloperName: "NoopFlow",
				QueueClass:        queueClass,
			})
			if err != nil {
				t.Fatalf("NormalizeRouteExecutionSpec: %v", err)
			}
			if spec.QueueClass != queueClass {
				t.Fatalf("QueueClass = %q, want %q", spec.QueueClass, queueClass)
			}
		})
	}
}

func TestAuditStableQueueClassBulkContract(t *testing.T) {
	queueClass, err := NormalizeQueueClass("bulk")
	if err != nil {
		t.Fatalf("NormalizeQueueClass bulk returned error: %v", err)
	}
	if queueClass != QueueClassBulk {
		t.Fatalf("NormalizeQueueClass bulk = %q, want %q", queueClass, QueueClassBulk)
	}
	spec, err := NormalizeRouteExecutionSpec(RouteExecutionSpec{
		FlowDeveloperName: "BulkFlow",
		QueueClass:        "bulk",
	})
	if err != nil {
		t.Fatalf("NormalizeRouteExecutionSpec bulk returned error: %v", err)
	}
	if spec.QueueClass != QueueClassBulk {
		t.Fatalf("RouteExecutionSpec.QueueClass = %q, want %q", spec.QueueClass, QueueClassBulk)
	}
}
