package models

import (
	"strings"
	"testing"
)

func TestNewFlowIdentityConvergenceDependencyRequestCanonicalizesExactSelector(t *testing.T) {
	request, err := NewFlowIdentityConvergenceDependencyRequest(
		" Lead ",
		" 00Q000000000001AAA ",
		" pingcap-hubspot-companion ",
		" hubspot-contact ",
	)
	if err != nil {
		t.Fatalf("NewFlowIdentityConvergenceDependencyRequest() error = %v", err)
	}
	if request.Kind != FlowDependencyKindIdentityConvergence {
		t.Fatalf("Kind = %q, want %q", request.Kind, FlowDependencyKindIdentityConvergence)
	}
	if request.ObjectAPIName != "Lead" || request.RecordID != "00Q000000000001AAA" ||
		request.ConnectorOwnerKey != "pingcap-hubspot-companion" || request.DestinationSlotID != "hubspot-contact" {
		t.Fatalf("request = %#v, want canonical exact selector and slot", request)
	}
	if err := request.Validate(); err != nil {
		t.Fatalf("Validate() error = %v", err)
	}
}

func TestBindFlowIdentityConvergenceDependencyBindsAndRedactsFullConnectorScope(t *testing.T) {
	request, err := NewFlowIdentityConvergenceDependencyRequest(
		"Lead",
		"00Q000000000001AAA",
		"pingcap-hubspot-companion",
		"hubspot-contact",
	)
	if err != nil {
		t.Fatalf("NewFlowIdentityConvergenceDependencyRequest() error = %v", err)
	}
	dependency, err := BindFlowIdentityConvergenceDependency(request, " hubspot ", " contacts ", " contact-1 ")
	if err != nil {
		t.Fatalf("BindFlowIdentityConvergenceDependency() error = %v", err)
	}
	if dependency.ConnectorNamespace != "hubspot" || dependency.ConnectorSourceObject != "contacts" || dependency.ConnectorRemoteID != "contact-1" {
		t.Fatalf("dependency connector scope = %#v", dependency)
	}
	if len(dependency.KeyHash) != 64 || strings.Contains(dependency.KeyHash, dependency.RecordID) || strings.Contains(dependency.KeyHash, dependency.ConnectorRemoteID) {
		t.Fatalf("KeyHash = %q, want fixed-width redacted key", dependency.KeyHash)
	}
	if err := dependency.Validate(); err != nil {
		t.Fatalf("Validate() error = %v", err)
	}
}

func TestFlowIdentityConvergenceHashBindsEveryConnectorScopeDimension(t *testing.T) {
	request, err := NewFlowIdentityConvergenceDependencyRequest("Lead", "same-id", "owner", "slot")
	if err != nil {
		t.Fatal(err)
	}
	base, err := BindFlowIdentityConvergenceDependency(request, "hubspot", "contacts", "remote-1")
	if err != nil {
		t.Fatal(err)
	}
	variants := []FlowExecutionDependency{base}
	for _, mutate := range []func(*FlowExecutionDependency){
		func(d *FlowExecutionDependency) { d.ObjectAPIName = "Contact" },
		func(d *FlowExecutionDependency) { d.RecordID = "other-id" },
		func(d *FlowExecutionDependency) { d.ConnectorOwnerKey = "other-owner" },
		func(d *FlowExecutionDependency) { d.ConnectorNamespace = "other-namespace" },
		func(d *FlowExecutionDependency) { d.ConnectorSourceObject = "companies" },
		func(d *FlowExecutionDependency) { d.DestinationSlotID = "other-slot" },
		func(d *FlowExecutionDependency) { d.ConnectorRemoteID = "remote-2" },
	} {
		candidate := base
		mutate(&candidate)
		candidate.KeyHash = flowIdentityConvergenceKeyHash(candidate)
		variants = append(variants, candidate)
	}
	for i := 1; i < len(variants); i++ {
		if variants[i].KeyHash == base.KeyHash {
			t.Fatalf("variant %d did not change the full-scope dependency hash", i)
		}
	}
}

func TestFlowIdentityConvergenceHashPreservesFieldBoundaries(t *testing.T) {
	leftRequest, err := NewFlowIdentityConvergenceDependencyRequest("A\x00B", "C", "owner", "slot")
	if err != nil {
		t.Fatal(err)
	}
	rightRequest, err := NewFlowIdentityConvergenceDependencyRequest("A", "B\x00C", "owner", "slot")
	if err != nil {
		t.Fatal(err)
	}
	left, err := BindFlowIdentityConvergenceDependency(leftRequest, "namespace", "source", "remote")
	if err != nil {
		t.Fatal(err)
	}
	right, err := BindFlowIdentityConvergenceDependency(rightRequest, "namespace", "source", "remote")
	if err != nil {
		t.Fatal(err)
	}
	if left.KeyHash == right.KeyHash {
		t.Fatalf("distinct field boundaries produced the same dependency hash: %q", left.KeyHash)
	}
}

func TestFlowRecordDependencyValidationFailsClosed(t *testing.T) {
	tests := []FlowExecutionDependency{
		{},
		{Kind: FlowDependencyKindIdentityConvergence, ObjectAPIName: "Lead", RecordID: "id", ConnectorOwnerKey: "owner", ConnectorNamespace: "hubspot", ConnectorSourceObject: "contacts", DestinationSlotID: "slot", ConnectorRemoteID: "remote", KeyHash: strings.Repeat("0", 64)},
		{Kind: "clock", ObjectAPIName: "Lead", RecordID: "id", ConnectorOwnerKey: "owner", ConnectorNamespace: "hubspot", ConnectorSourceObject: "contacts", DestinationSlotID: "slot", ConnectorRemoteID: "remote", KeyHash: strings.Repeat("0", 64)},
		{Kind: FlowDependencyKindIdentityConvergence, ObjectAPIName: "Lead", RecordID: "id", ConnectorOwnerKey: " owner", ConnectorNamespace: "hubspot", ConnectorSourceObject: "contacts", DestinationSlotID: "slot", ConnectorRemoteID: "remote", KeyHash: strings.Repeat("0", 64)},
		{Kind: FlowDependencyKindIdentityConvergence, ObjectAPIName: "Lead", RecordID: "id", ConnectorOwnerKey: "owner", ConnectorNamespace: "", ConnectorSourceObject: "contacts", DestinationSlotID: "slot", ConnectorRemoteID: "remote", KeyHash: strings.Repeat("0", 64)},
	}
	for i := range tests {
		if err := tests[i].Validate(); err == nil {
			t.Fatalf("case %d Validate() = nil, want fail-closed error", i)
		}
	}
}
