package connector_inbound_handler_config

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

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/pingcap-inc/nexuscrm-connectors/features/connector/capabilities"
)

func handlerConfigGateActive(_ context.Context, _ string, _ connectorcapabilities.CapabilityKind, _ string) (bool, error) {
	return true, nil
}

type legacyCompiledVerifier struct{}

func (legacyCompiledVerifier) Verify(context.Context, *connectorcapabilities.VerifierInput) error {
	return nil
}

func validSignatureConfigJSON() []byte {
	return []byte(`{
  "scheme":"hmac-sha256-template",
  "signingTemplate":"{Timestamp}.{RawBody}",
  "timestampHeader":"X-Test-Timestamp",
  "signatureHeader":"X-Test-Signature",
  "signatureEncoding":"hex",
  "skewWindow":"5m",
  "secretName":"default"
}`)
}

func validEnvelopeConfigJSON() []byte {
	return []byte(`{
  "eventArrayPath":".events",
  "rules":[{
    "eventTypeLiteral":"thing.updated",
    "namespaceFromCtx":true,
    "objectKind":"thing",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId",
    "payloadProjection":[{"name":"objectId","path":".objectId"}]
  }]
	}`)
}

func compositeEnvelopeConfigJSON() []byte {
	return []byte(`{
  "eventArrayPath":".events",
  "rules":[{
    "eventTypeLiteral":"thing.updated",
    "namespaceFromCtx":true,
    "objectKind":"thing",
    "sourceIdPath":".objectId",
    "idempotencyKeyComposite":{
      "version":"v1",
      "encoding":"sha256",
      "parts":[{"path":".eventId"},{"path":".occurredAt"}]
    }
  }]
}`)
}

func endpointSignatureConfigJSON() []byte {
	return []byte(`{
  "endpointConfigs":{
    "endpoint-a":{
      "scheme":"hmac-sha256-template",
      "signingTemplate":"{Timestamp}.{RawBody}",
      "timestampHeader":"X-A-Timestamp",
      "signatureHeader":"X-A-Signature",
      "signatureEncoding":"hex",
      "skewWindow":"5m"
    },
    "endpoint-b":{
      "scheme":"hmac-sha256-template",
      "signingTemplate":"{Timestamp}:{RawBody}",
      "timestampHeader":"X-B-Timestamp",
      "signatureHeader":"X-B-Signature",
      "signatureEncoding":"hex",
      "skewWindow":"5m"
    }
  }
}`)
}

func endpointEnvelopeConfigJSON() []byte {
	return []byte(`{
  "endpointConfigs":{
    "endpoint-a":{
      "eventArrayPath":".events",
      "rules":[{
        "eventTypeLiteral":"endpoint-a.updated",
        "namespaceFromCtx":true,
        "objectKind":"thing",
        "sourceIdPath":".objectId",
        "idempotencyKeyPath":".eventId",
        "payloadProjection":[{"name":"objectId","path":".objectId"}]
      }]
    },
    "endpoint-b":{
      "eventArrayPath":".events",
      "rules":[{
        "eventTypeLiteral":"endpoint-b.updated",
        "namespaceFromCtx":true,
        "objectKind":"thing",
        "sourceIdPath":".objectId",
        "idempotencyKeyPath":".eventId",
        "payloadProjection":[{"name":"objectId","path":".objectId"}]
      }]
    }
  }
}`)
}

func TestBuildCapabilityHandler_SignatureVerifierUsesDurationStringWireFormat(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.SignatureVerifier, validSignatureConfigJSON())
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	if _, ok := handler.(connectorcapabilities.SignatureVerifierHandler); !ok {
		t.Fatalf("handler type = %T, want SignatureVerifierHandler", handler)
	}
	if !strings.Contains(canonical, `"skewWindow":"5m"`) {
		t.Fatalf("canonical config %q does not preserve duration string", canonical)
	}
}

func TestBuildCapabilityHandler_EnvelopeParserCarriesCompositeIdentity(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, compositeEnvelopeConfigJSON())
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	parser, ok := handler.(connectorcapabilities.EnvelopeParserHandler)
	if !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}
	events, err := parser.Parse(context.Background(), &connectorcapabilities.ParserInput{
		Payload: []byte(`{"events":[{"objectId":"thing-1","eventId":"event-1","occurredAt":123}]}`),
	})
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(events) != 1 || len(events[0].IdempotencyKey) != 84 || !strings.HasPrefix(events[0].IdempotencyKey, "cmp:v1:") {
		t.Fatalf("events = %#v, want one composite-key event", events)
	}
	if !strings.Contains(canonical, `"idempotencyKeyComposite":{"version":"v1","encoding":"sha256"`) {
		t.Fatalf("canonical config %q missing composite metadata", canonical)
	}
}

func TestBuildCapabilityHandler_SignatureVerifierEndpointConfigsDispatchBySecretName(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.SignatureVerifier, endpointSignatureConfigJSON())
	if err != nil {
		t.Fatalf("BuildCapabilityHandler endpoint signature configs: %v", err)
	}
	verifier, ok := handler.(connectorcapabilities.SignatureVerifierHandler)
	if !ok {
		t.Fatalf("handler type = %T, want SignatureVerifierHandler", handler)
	}
	if !strings.Contains(canonical, `"endpointConfigs"`) {
		t.Fatalf("canonical endpoint config %q missing endpointConfigs", canonical)
	}

	now := time.Now()
	body := []byte(`{"event":"test"}`)
	for _, tc := range []struct {
		name            string
		endpoint        string
		template        string
		timestampHeader string
		signatureHeader string
	}{
		{"endpoint_a", "endpoint-a", "{Timestamp}.{RawBody}", "X-A-Timestamp", "X-A-Signature"},
		{"endpoint_b", "endpoint-b", "{Timestamp}:{RawBody}", "X-B-Timestamp", "X-B-Signature"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			headers := signedHeadersForTemplate(tc.template, tc.timestampHeader, tc.signatureHeader, body, []byte("secret"), now)
			err := verifier.Verify(context.Background(), &connectorcapabilities.VerifierInput{
				Headers:    headers,
				RawBody:    body,
				Secret:     []byte("secret"),
				Algorithm:  "hmac-sha256",
				SecretName: tc.endpoint,
			})
			if err != nil {
				t.Fatalf("Verify endpoint %q: %v", tc.endpoint, err)
			}
		})
	}
}

func TestBuildCapabilityHandler_EnvelopeParserCarriesMatchAll(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, []byte(`{
  "eventArrayPath":".events",
  "rules":[{
    "matchAll":[
      {"path":".eventCategory","equals":"entity.propertyChange"},
      {"path":".entityTypeId","equals":"0-2"}
    ],
    "eventTypeLiteral":"entity.propertychange",
    "namespaceFromCtx":true,
    "objectKind":"entities",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId"
  }]
}`))
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	if !strings.Contains(canonical, `"matchAll"`) {
		t.Fatalf("canonical config %q missing matchAll", canonical)
	}
	parser, ok := handler.(connectorcapabilities.EnvelopeParserHandler)
	if !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}

	events, err := parser.Parse(context.Background(), &connectorcapabilities.ParserInput{Payload: []byte(`{"events":[
		{"eventCategory":"entity.propertyChange","entityTypeId":"0-2","objectId":"entity-1","eventId":"evt-1"},
		{"eventCategory":"object.creation","entityTypeId":"0-2","objectId":"entity-2","eventId":"evt-2"}
	]}`)})
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(events) != 1 {
		t.Fatalf("len(events) = %d, want 1", len(events))
	}
	if got, want := events[0].EventType, "entity.propertychange"; got != want {
		t.Fatalf("EventType = %q, want %q", got, want)
	}
}

func TestBuildCapabilityHandler_EnvelopeParserCarriesTargetMutationLane(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("hubspot-crm-connector", connectorcapabilities.EnvelopeParser, []byte(`{
  "eventObjectPath":".",
  "rules":[{
    "eventTypeLiteral":"workflow.action.execute",
    "namespaceFromCtx":true,
    "objectKind":"workflow-actions",
    "sourceIdPath":".object.objectId",
    "targetMutationLaneObjectKind":"campaign",
    "targetMutationLaneIdPath":".inputFields.campaign_id",
    "idempotencyKeyPath":".callbackId",
    "payloadProjection":[{"name":"campaign_id","path":".inputFields.campaign_id"}]
  }]
}`))
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	if !strings.Contains(canonical, `"targetMutationLaneObjectKind":"campaign"`) || !strings.Contains(canonical, `"targetMutationLaneIdPath":".inputFields.campaign_id"`) {
		t.Fatalf("canonical config %q missing target mutation lane", canonical)
	}
	parser, ok := handler.(connectorcapabilities.EnvelopeParserHandler)
	if !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}

	events, err := parser.Parse(context.Background(), &connectorcapabilities.ParserInput{
		Payload: []byte(`{
			"callbackId":"callback-1",
			"object":{"objectId":"contact-123"},
			"inputFields":{"campaign_id":"campaign-001"}
		}`),
	})
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(events) != 1 {
		t.Fatalf("len(events) = %d, want 1", len(events))
	}
	ev := events[0]
	if ev.SourceID != "contact-123" {
		t.Fatalf("SourceID = %q, want contact-123", ev.SourceID)
	}
	if ev.SourceLaneObjectKind != "campaign" || ev.SourceLaneID != "campaign-001" {
		t.Fatalf("queue lane = %q/%q, want campaign/campaign-001", ev.SourceLaneObjectKind, ev.SourceLaneID)
	}
	if ev.IdempotencyKey != "callback-1" {
		t.Fatalf("IdempotencyKey = %q, want callback-1", ev.IdempotencyKey)
	}
}

func TestBuildCapabilityHandler_CanonicalizesMatchAllConditionOrder(t *testing.T) {
	_, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, []byte(`{
  "eventArrayPath":".events",
  "rules":[{
    "matchAll":[
      {"path":".eventCategory","equals":"entity.propertyChange"},
      {"path":".entityTypeId","equals":"0-2"}
    ],
    "eventTypeLiteral":"entity.propertychange",
    "namespaceFromCtx":true,
    "objectKind":"entities",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId"
  }]
}`))
	if err != nil {
		t.Fatalf("BuildCapabilityHandler first: %v", err)
	}
	_, reversedCanonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, []byte(`{
  "eventArrayPath":".events",
  "rules":[{
    "matchAll":[
      {"path":".entityTypeId","equals":"0-2"},
      {"path":".eventCategory","equals":"entity.propertyChange"}
    ],
    "eventTypeLiteral":"entity.propertychange",
    "namespaceFromCtx":true,
    "objectKind":"entities",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId"
  }]
}`))
	if err != nil {
		t.Fatalf("BuildCapabilityHandler reversed: %v", err)
	}
	if canonical != reversedCanonical {
		t.Fatalf("canonical configs differ:\nfirst:    %s\nreversed: %s", canonical, reversedCanonical)
	}
}

func TestBuildCapabilityHandler_RejectsNullMatchAll(t *testing.T) {
	_, _, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, []byte(`{
  "eventArrayPath":".events",
  "rules":[{
    "matchAll":null,
    "eventTypeLiteral":"thing.updated",
    "namespaceFromCtx":true,
    "objectKind":"thing",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId"
  }]
}`))
	if err == nil {
		t.Fatal("BuildCapabilityHandler = nil, want null matchAll error")
	}
	if !strings.Contains(err.Error(), "matchAll must contain at least one condition") {
		t.Fatalf("error = %q, want null matchAll diagnostic", err)
	}
}

func TestBuildCapabilityHandler_RejectsUnknownSignatureConfigFields(t *testing.T) {
	raw := []byte(`{
  "scheme":"hmac-sha256-template",
  "signingTemplate":"{Timestamp}.{RawBody}",
  "timestampHeader":"X-Test-Timestamp",
  "signatureHeader":"X-Test-Signature",
  "signatureEncoding":"hex",
  "skewWindow":"5m",
  "secretName":"default",
  "surprise":true
}`)

	_, _, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.SignatureVerifier, raw)
	if err == nil {
		t.Fatal("BuildCapabilityHandler = nil, want unknown-field error")
	}
	if !strings.Contains(err.Error(), "surprise") {
		t.Fatalf("error %q does not mention unknown field", err)
	}
}

func TestBuildCapabilityHandler_EnvelopeParserUsesStrictWireDTO(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, validEnvelopeConfigJSON())
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	if _, ok := handler.(connectorcapabilities.EnvelopeParserHandler); !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}
	if strings.Contains(canonical, `"inboundProcessingMode"`) {
		t.Fatalf("canonical config %q should preserve omitted inboundProcessingMode for legacy configs", canonical)
	}
}

func TestBuildCapabilityHandler_EnvelopeParserUsesEventObjectPath(t *testing.T) {
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, []byte(`{
  "eventObjectPath":".",
  "rules":[{
    "eventTypeLiteral":"thing.updated",
    "namespaceFromCtx":true,
    "objectKind":"thing",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId",
    "payloadProjection":[{"name":"objectId","path":".objectId"}]
  }]
}`))
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	if !strings.Contains(canonical, `"eventObjectPath":"."`) {
		t.Fatalf("canonical config %q missing eventObjectPath", canonical)
	}
	parser, ok := handler.(connectorcapabilities.EnvelopeParserHandler)
	if !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}

	events, err := parser.Parse(context.Background(), &connectorcapabilities.ParserInput{
		Payload: []byte(`{"objectId":"thing-1","eventId":"evt-1"}`),
	})
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(events) != 1 || events[0].SourceID != "thing-1" || events[0].IdempotencyKey != "evt-1" {
		t.Fatalf("events = %#v, want one singleton-object event", events)
	}
}

func TestBuildCapabilityHandler_EnvelopeParserEndpointConfigsDispatchBySecretName(t *testing.T) {
	handler, _, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, endpointEnvelopeConfigJSON())
	if err != nil {
		t.Fatalf("BuildCapabilityHandler endpoint envelope configs: %v", err)
	}
	parser, ok := handler.(connectorcapabilities.EnvelopeParserHandler)
	if !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}
	body := []byte(`{"events":[{"objectId":"thing-1","eventId":"evt-1"}]}`)
	for _, tc := range []struct {
		name      string
		endpoint  string
		eventType string
	}{
		{"endpoint_a", "endpoint-a", "endpoint-a.updated"},
		{"endpoint_b", "endpoint-b", "endpoint-b.updated"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			events, err := parser.Parse(context.Background(), &connectorcapabilities.ParserInput{
				Payload:    body,
				SecretName: tc.endpoint,
			})
			if err != nil {
				t.Fatalf("Parse endpoint %q: %v", tc.endpoint, err)
			}
			if len(events) != 1 || events[0].EventType != tc.eventType {
				t.Fatalf("events = %#v, want one eventType %q", events, tc.eventType)
			}
		})
	}
}

func TestBuildCapabilityHandler_EnvelopeParserAcceptsExplicitPayloadOnlyMode(t *testing.T) {
	raw := []byte(`{
  "eventArrayPath":".events",
  "inboundProcessingMode":"payload_only_allowed",
  "rules":[{
    "eventTypeLiteral":"thing.updated",
    "namespaceFromCtx":true,
    "objectKind":"thing",
    "sourceIdPath":".objectId",
    "idempotencyKeyPath":".eventId",
    "payloadProjection":[{"name":"objectId","path":".objectId"}]
  }]
}`)
	handler, canonical, err := BuildCapabilityHandler("acme-connector", connectorcapabilities.EnvelopeParser, raw)
	if err != nil {
		t.Fatalf("BuildCapabilityHandler: %v", err)
	}
	if _, ok := handler.(connectorcapabilities.EnvelopeParserHandler); !ok {
		t.Fatalf("handler type = %T, want EnvelopeParserHandler", handler)
	}
	if !strings.Contains(canonical, `"inboundProcessingMode":"payload_only_allowed"`) {
		t.Fatalf("canonical config %q missing explicit inboundProcessingMode", canonical)
	}
}

func TestRegisterActiveHandlerConfigs_ScansActiveRowsAndUpsertsRegistry(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}).
			AddRow("acme-connector", "signature_verifier", string(validSignatureConfigJSON()), "pack-install-1").
			AddRow("acme-connector", "envelope_parser", string(validEnvelopeConfigJSON()), "pack-install-1"))

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	if err := RegisterActiveHandlerConfigs(context.Background(), db, reg); err != nil {
		t.Fatalf("RegisterActiveHandlerConfigs: %v", err)
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", ""); err != nil {
		t.Fatalf("signature verifier not registered: %v", err)
	}
	if _, err := reg.ResolveEnvelopeParser(context.Background(), "acme-connector", ""); err != nil {
		t.Fatalf("envelope parser not registered: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRegisterActiveHandlerConfigs_ReplacesStalePackInstallProjection(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}).
			AddRow("acme-connector", "signature_verifier", string(validSignatureConfigJSON()), "pack-install-2"))

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	if err := reg.UpsertCapabilityForPackInstall("acme-connector", connectorcapabilities.SignatureVerifier, "pack-install-1", legacyCompiledVerifier{}); err != nil {
		t.Fatalf("seed stale registry projection: %v", err)
	}
	if err := RegisterActiveHandlerConfigs(context.Background(), db, reg); err != nil {
		t.Fatalf("RegisterActiveHandlerConfigs: %v", err)
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", "pack-install-2"); err != nil {
		t.Fatalf("new pack install projection not registered: %v", err)
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", "pack-install-1"); !errors.Is(err, connectorcapabilities.ErrCapabilityIncarnationChanged) {
		t.Fatalf("old pack install resolve error = %v, want ErrCapabilityIncarnationChanged", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func signedHeadersForTemplate(template, timestampHeader, signatureHeader string, body, secret []byte, now time.Time) map[string]string {
	ts := strconv.FormatInt(now.UnixMilli(), 10)
	signingString := strings.ReplaceAll(template, "{Timestamp}", ts)
	signingString = strings.ReplaceAll(signingString, "{RawBody}", string(body))
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(signingString))
	return map[string]string{
		timestampHeader: ts,
		signatureHeader: hex.EncodeToString(mac.Sum(nil)),
	}
}

func TestRegisterActiveHandlerConfigs_ReturnsScanError(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	wantErr := errors.New("db unavailable")
	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").WillReturnError(wantErr)

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	err = RegisterActiveHandlerConfigs(context.Background(), db, reg)
	if !errors.Is(err, wantErr) {
		t.Fatalf("RegisterActiveHandlerConfigs = %v, want %v", err, wantErr)
	}
}

func TestRegisterActiveHandlerConfigs_RejectsCapabilityWithoutHandlerConfig(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}).
			AddRow("acme-connector", "acme-connector", "signature_verifier", "pack-install-1"))

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	err = RegisterActiveHandlerConfigs(context.Background(), db, reg)
	if err == nil {
		t.Fatal("RegisterActiveHandlerConfigs = nil, want missing handler config error")
	}
	if !strings.Contains(err.Error(), "connector inbound handler config missing") {
		t.Fatalf("RegisterActiveHandlerConfigs error = %v, want missing handler config error", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRegisterActiveHandlerConfigs_GrandfathersCapabilityWithoutHandlerConfigWhenRegistryAlreadyHasLegacyHandler(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}).
			AddRow("acme-connector", "acme-connector", "signature_verifier", "pack-install-1"))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}))

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	if err := reg.UpsertCapability("acme-connector", connectorcapabilities.SignatureVerifier, legacyCompiledVerifier{}); err != nil {
		t.Fatalf("register legacy compiled handler: %v", err)
	}
	if err := RegisterActiveHandlerConfigs(context.Background(), db, reg); err != nil {
		t.Fatalf("RegisterActiveHandlerConfigs grandfathered legacy handler: %v", err)
	}
	if reg.HasUnboundHandler("acme-connector", connectorcapabilities.SignatureVerifier) {
		t.Fatal("legacy handler remained unbound after installed-row compatibility registration")
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", "pack-install-1"); err != nil {
		t.Fatalf("ResolveSignatureVerifier bound legacy handler: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRegisterHandlerConfigsForPack_ScansInstallingRowsAndUpsertsPackBoundRegistry(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WithArgs("acme-connector").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WithArgs("acme-connector").
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}).
			AddRow("acme-connector", "signature_verifier", string(validSignatureConfigJSON()), "pack-install-installing"))

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	if err := RegisterHandlerConfigsForPack(context.Background(), db, reg, "acme-connector"); err != nil {
		t.Fatalf("RegisterHandlerConfigsForPack: %v", err)
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", "pack-install-installing"); err != nil {
		t.Fatalf("ResolveSignatureVerifier pack-bound installing handler: %v", err)
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", "pack-install-other"); !errors.Is(err, connectorcapabilities.ErrCapabilityIncarnationChanged) {
		t.Fatalf("ResolveSignatureVerifier wrong install id = %v, want ErrCapabilityIncarnationChanged", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRegisterActiveHandlerConfigsForPackInstallUsesExactGeneration(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WithArgs("acme-connector", "pack-install-selected").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WithArgs("acme-connector", "pack-install-selected").
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}).
			AddRow("acme-connector", "signature_verifier", string(validSignatureConfigJSON()), "pack-install-selected"))

	reg := connectorcapabilities.NewRegistry(handlerConfigGateActive)
	if err := RegisterActiveHandlerConfigsForPackInstall(
		context.Background(),
		db,
		reg,
		"acme-connector",
		"pack-install-selected",
	); err != nil {
		t.Fatalf("RegisterActiveHandlerConfigsForPackInstall: %v", err)
	}
	if _, err := reg.ResolveSignatureVerifier(context.Background(), "acme-connector", "pack-install-selected"); err != nil {
		t.Fatalf("ResolveSignatureVerifier exact install: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestVerifyHandlerConfigsForPack_RejectsMissingConfigWithoutLegacyHandler(t *testing.T) {
	const namespace = "verify-legacy-connector"

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WithArgs(namespace).
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}).
			AddRow(namespace, namespace, "signature_verifier", "pack-install-verify"))

	err = VerifyHandlerConfigsForPack(context.Background(), db, namespace)
	if err == nil {
		t.Fatal("VerifyHandlerConfigsForPack = nil, want missing handler config error")
	}
	if !strings.Contains(err.Error(), "connector inbound handler config missing") {
		t.Fatalf("VerifyHandlerConfigsForPack error = %v, want missing handler config error", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestVerifyHandlerConfigsForPack_GrandfathersLegacyCompiledHandlerWithoutConfigRows(t *testing.T) {
	const namespace = "verify-legacy-compiled-connector"
	t.Cleanup(func() {
		connectorcapabilities.UnregisterCapability(namespace, connectorcapabilities.SignatureVerifier)
	})

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WithArgs(namespace).
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}).
			AddRow(namespace, namespace, "signature_verifier", "pack-install-verify"))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WithArgs(namespace).
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}))

	if err := connectorcapabilities.RegisterCapability(namespace, connectorcapabilities.SignatureVerifier, legacyCompiledVerifier{}); err != nil {
		t.Fatalf("register legacy compiled handler: %v", err)
	}
	if err := VerifyHandlerConfigsForPack(context.Background(), db, namespace); err != nil {
		t.Fatalf("VerifyHandlerConfigsForPack legacy compiled handler: %v", err)
	}
	if connectorcapabilities.HasUnboundHandler(namespace, connectorcapabilities.SignatureVerifier) {
		t.Fatal("VerifyHandlerConfigsForPack left legacy handler unbound after installed-row compatibility")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestVerifyHandlerConfigsForPack_RejectsInvalidSemanticConfig(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT p.Name, d.Namespace, d.Kind").
		WithArgs("acme-connector").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Namespace", "Kind", "PackInstallId"}))
	mock.ExpectQuery("SELECT c.Namespace, c.CapabilityKind, c.ConfigJSON").
		WithArgs("acme-connector").
		WillReturnRows(sqlmock.NewRows([]string{"Namespace", "CapabilityKind", "ConfigJSON", "InstalledPackId"}).
			AddRow("acme-connector", "signature_verifier", `{
  "scheme":"hmac-sha256-template",
  "signingTemplate":"{Timestamp}.{RawBody}",
  "timestampHeader":"X-Test-Signature",
  "signatureHeader":"X-Test-Signature",
  "signatureEncoding":"hex",
  "skewWindow":"5m",
  "secretName":"default"
}`, "pack-install-1"))

	err = VerifyHandlerConfigsForPack(context.Background(), db, "acme-connector")
	if err == nil {
		t.Fatal("VerifyHandlerConfigsForPack = nil, want semantic config error")
	}
	if !strings.Contains(err.Error(), "TimestampHeader and SignatureHeader must differ") {
		t.Fatalf("error %q does not mention signature header semantic validation", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}
