package constants

import (
	"context"
	"encoding/json"
	"reflect"
	"testing"
)

func TestConnectorInboundTraceTypesAreDirectGoOnly(t *testing.T) {
	for name, value := range map[string]interface{}{
		"trace": ConnectorInboundTrace{
			WorkItemID: "work-secret", EventID: "event-secret", Namespace: "crm-pack",
			Related: []ConnectorInboundTraceItem{{WorkItemID: "related-work", EventID: "related-event"}},
		},
		"item": ConnectorInboundTraceItem{WorkItemID: "work-secret", EventID: "event-secret"},
	} {
		payload, err := json.Marshal(value)
		if err != nil {
			t.Fatalf("json.Marshal(%s) error = %v", name, err)
		}
		if string(payload) != "{}" {
			t.Fatalf("json.Marshal(%s) = %s, want direct-Go-only empty object", name, payload)
		}
	}
}

func TestSecurityContextRoundTrip(t *testing.T) {
	ctx := WithSecurityContext(context.Background(), SystemContext, "test")
	if got := GetSecurityContext(ctx); got != SystemContext {
		t.Fatalf("GetSecurityContext = %v, want %v", got, SystemContext)
	}
}

func TestConnectorInboundExecutionAuthorizesExactFlowAndNamespace(t *testing.T) {
	ctx := WithConnectorInboundFlowExecution(context.Background(), "SyncContact", "crm-pack")
	if !ConnectorInboundExecutionAuthorizesFlow(ctx, "synccontact", "CRM-PACK") {
		t.Fatalf("expected exact connector inbound root flow + namespace authorization")
	}
	if ConnectorInboundExecutionAuthorizesFlow(ctx, "OtherFlow", "crm-pack") {
		t.Fatalf("different root flow should not be authorized")
	}
	if ConnectorInboundExecutionAuthorizesFlow(ctx, "SyncContact", "other-pack") {
		t.Fatalf("different owner pack should not be authorized")
	}
}

func TestConnectorInboundExecutionFailSafeWhenMissingInputs(t *testing.T) {
	ctx := WithConnectorInboundFlowExecution(context.Background(), "", "crm-pack")
	if ConnectorInboundExecutionAuthorizesFlow(ctx, "SyncContact", "crm-pack") {
		t.Fatalf("missing root flow should fail closed")
	}
	ctx = WithConnectorInboundFlowExecution(context.Background(), "SyncContact", "")
	if ConnectorInboundExecutionAuthorizesFlow(ctx, "SyncContact", "crm-pack") {
		t.Fatalf("missing namespace should fail closed")
	}
}

func TestConnectorInboundCompanionExecutionAuthorizesExactVersion(t *testing.T) {
	ctx := WithConnectorInboundCompanionFlowExecution(context.Background(), "301xx000000001AAA")
	if !ConnectorInboundCompanionExecutionAuthorizesVersion(ctx, "301XX000000001aaa") {
		t.Fatalf("expected exact companion flow version authorization")
	}
	if ConnectorInboundCompanionExecutionAuthorizesVersion(ctx, "301xx000000002AAA") {
		t.Fatalf("different companion flow version should not be authorized")
	}
}

func TestConnectorInboundTraceRoundTripAndNormalization(t *testing.T) {
	trace := ConnectorInboundTrace{
		WorkItemID:         " work-123 ",
		EventID:            " event-456 ",
		Attempt:            2,
		Namespace:          " crm-pack ",
		ObjectKind:         " contacts ",
		SourceIDHash:       " sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ",
		HydratedRecordHash: " sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ",
		HydratedFieldCount: 7,
		Related: []ConnectorInboundTraceItem{{
			WorkItemID:         " work-789 ",
			EventID:            " event-789 ",
			Attempt:            -1,
			ObjectKind:         " contacts ",
			SourceIDHash:       " sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ",
			HydratedRecordHash: " sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ",
			HydratedFieldCount: 3,
		}},
	}
	ctx := WithConnectorInboundTrace(context.Background(), trace)
	got, ok := ConnectorInboundTraceFromContext(ctx)
	if !ok {
		t.Fatal("ConnectorInboundTraceFromContext() ok = false, want true")
	}
	want := ConnectorInboundTrace{
		WorkItemID:         "work-123",
		EventID:            "event-456",
		Attempt:            2,
		Namespace:          "crm-pack",
		ObjectKind:         "contacts",
		SourceIDHash:       "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
		HydratedRecordHash: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
		HydratedFieldCount: 7,
		Related: []ConnectorInboundTraceItem{{
			WorkItemID:         "work-789",
			EventID:            "event-789",
			Attempt:            0,
			ObjectKind:         "contacts",
			SourceIDHash:       "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
			HydratedRecordHash: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
			HydratedFieldCount: 3,
		}},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("trace = %#v, want %#v", got, want)
	}
}

func TestConnectorInboundTraceOwnsRelatedSlice(t *testing.T) {
	related := []ConnectorInboundTraceItem{{
		WorkItemID:   "work-related",
		EventID:      "event-related",
		SourceIDHash: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	}}
	ctx := WithConnectorInboundTrace(context.Background(), ConnectorInboundTrace{
		EventID:   "event-root",
		Namespace: "crm-pack",
		Related:   related,
	})

	related[0].EventID = "mutated-after-admission"
	related[0].SourceIDHash = "raw-source-id"
	got, ok := ConnectorInboundTraceFromContext(ctx)
	if !ok {
		t.Fatal("ConnectorInboundTraceFromContext() ok = false, want true")
	}
	if got.Related[0].EventID != "event-related" || got.Related[0].SourceIDHash != "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
		t.Fatalf("stored Related was mutated through caller alias: %#v", got.Related[0])
	}

	got.Related[0].EventID = "mutated-after-read"
	gotAgain, ok := ConnectorInboundTraceFromContext(ctx)
	if !ok || gotAgain.Related[0].EventID != "event-related" {
		t.Fatalf("stored Related was mutated through reader alias: %#v, ok=%v", gotAgain.Related, ok)
	}
}

func TestConnectorInboundTraceInvalidChildShadowsParent(t *testing.T) {
	parent := WithConnectorInboundTrace(context.Background(), ConnectorInboundTrace{
		EventID:   "event-parent",
		Namespace: "crm-pack",
	})
	child := WithConnectorInboundTrace(parent, ConnectorInboundTrace{EventID: "event-child"})

	if got, ok := ConnectorInboundTraceFromContext(child); ok {
		t.Fatalf("invalid child inherited parent trace: %#v", got)
	}
}

func TestConnectorInboundTraceDropsNonDigestHashFields(t *testing.T) {
	ctx := WithConnectorInboundTrace(context.Background(), ConnectorInboundTrace{
		EventID:            "event-456",
		Namespace:          "crm-pack",
		SourceIDHash:       "raw-source-id",
		HydratedRecordHash: "sha256:not-hex",
		Related: []ConnectorInboundTraceItem{{
			EventID:            "event-789",
			SourceIDHash:       "sha256:short",
			HydratedRecordHash: "raw-record",
		}},
	})
	got, ok := ConnectorInboundTraceFromContext(ctx)
	if !ok {
		t.Fatal("ConnectorInboundTraceFromContext() ok = false")
	}
	if got.SourceIDHash != "" || got.HydratedRecordHash != "" || got.Related[0].SourceIDHash != "" || got.Related[0].HydratedRecordHash != "" {
		t.Fatalf("non-digest values survived trace admission: %#v", got)
	}
}

func TestConnectorInboundTraceRequiresEventAndNamespace(t *testing.T) {
	for _, trace := range []ConnectorInboundTrace{
		{Namespace: "crm-pack"},
		{EventID: "event-456"},
	} {
		ctx := WithConnectorInboundTrace(context.Background(), trace)
		if _, ok := ConnectorInboundTraceFromContext(ctx); ok {
			t.Fatalf("trace %#v was admitted without event and namespace", trace)
		}
	}
}
