package namedcredential

import (
	"context"
	"errors"
	"fmt"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/models"
)

// --- mock pack resolver ---

type mockPackResolver struct {
	active         bool
	err            error
	drainNamespace string
	drainInstallID string
	drainAllowed   bool
	drainErr       error
}

func (m *mockPackResolver) ResolveConnectorPackByNamespace(_ context.Context, _ string) (bool, error) {
	return m.active, m.err
}

func (m *mockPackResolver) ResolveDrainOnlyConnectorPackByInstall(_ context.Context, namespace, installID string) (bool, error) {
	if m.drainErr != nil {
		return false, m.drainErr
	}
	return m.drainAllowed && namespace == m.drainNamespace && installID == m.drainInstallID, nil
}

// --- mock permission checker ---

type mockPermChecker struct {
	allow bool
}

func (m *mockPermChecker) CheckSystemPermission(_ context.Context, _ *models.UserSession, _ string) bool {
	return m.allow
}

// --- mock repository ---

type mockRepo struct {
	defs           map[string]*Definition // keyed by DeveloperName
	secrets        map[string]*Secret     // keyed by defID+":"+kind
	getSecretErr   error
	getSecretCalls int
	upsertDefCalls int
	upsertSecCalls int
	deleteDefCalls int
}

func newMockRepo() *mockRepo {
	return &mockRepo{
		defs:    make(map[string]*Definition),
		secrets: make(map[string]*Secret),
	}
}

func (r *mockRepo) ListDefinitions(_ context.Context) ([]Definition, error) {
	out := make([]Definition, 0, len(r.defs))
	for _, d := range r.defs {
		out = append(out, *d)
	}
	return out, nil
}

func (r *mockRepo) GetDefinitionByDeveloperName(_ context.Context, devName string) (*Definition, error) {
	d, ok := r.defs[devName]
	if !ok {
		return nil, nil
	}
	cp := *d
	return &cp, nil
}

func (r *mockRepo) UpsertDefinition(_ context.Context, def *Definition) error {
	r.upsertDefCalls++
	if def.ID == "" {
		def.ID = "gen-id-" + def.DeveloperName
	}
	cp := *def
	r.defs[def.DeveloperName] = &cp
	return nil
}

func (r *mockRepo) DeleteDefinition(_ context.Context, devName string) error {
	r.deleteDefCalls++
	delete(r.defs, devName)
	return nil
}

func (r *mockRepo) ListSecretMetadataByDefinitionID(_ context.Context, defID string) ([]SecretMetadata, error) {
	var out []SecretMetadata
	for k, s := range r.secrets {
		_ = k
		if s.NamedCredentialID == defID {
			out = append(out, SecretMetadata{
				SecretKind: s.SecretKind,
				MaskedHint: s.MaskedHint,
				RotatedAt:  s.RotatedAt,
			})
		}
	}
	return out, nil
}

func (r *mockRepo) GetSecret(_ context.Context, defID string, kind string) (*Secret, error) {
	r.getSecretCalls++
	if r.getSecretErr != nil {
		return nil, r.getSecretErr
	}
	key := defID + ":" + kind
	s, ok := r.secrets[key]
	if !ok {
		return nil, nil
	}
	cp := *s
	return &cp, nil
}

func (r *mockRepo) UpsertSecret(_ context.Context, sec *Secret) error {
	r.upsertSecCalls++
	if sec.ID == "" {
		sec.ID = "sec-id-" + sec.SecretKind
	}
	sec.RotatedAt = time.Now().UTC()
	key := sec.NamedCredentialID + ":" + sec.SecretKind
	cp := *sec
	r.secrets[key] = &cp
	return nil
}

// --- helpers ---

func newService(repo *mockRepo, allow bool) *Service {
	return NewService(repo, &mockPermChecker{allow: allow})
}

func seedDef(repo *mockRepo, devName string) *Definition {
	return seedDefWithAuthType(repo, devName, AuthTypeBearerToken)
}

func seedDefWithAuthType(repo *mockRepo, devName, authType string) *Definition {
	d := &Definition{
		ID:            "def-" + devName,
		DeveloperName: devName,
		Label:         "Test " + devName,
		Endpoint:      "https://example.com",
		PrincipalType: PrincipalTypeNamedPrincipal,
		AuthType:      authType,
	}
	repo.defs[devName] = d
	return d
}

func seedPackOwnedDef(repo *mockRepo, devName, ownerPack string) *Definition {
	d := seedDef(repo, devName)
	d.OwnerPack = ownerPack
	repo.defs[devName] = d
	return d
}

func seedSecretMetadata(repo *mockRepo, def *Definition, kind string) {
	repo.secrets[def.ID+":"+kind] = &Secret{
		ID:                "sec-" + kind,
		NamedCredentialID: def.ID,
		SecretKind:        kind,
		SecretValue:       "secret-value",
		MaskedHint:        "...",
		RotatedAt:         time.Now().UTC(),
	}
}

func seedBlankSecretMetadata(repo *mockRepo, def *Definition, kind string) {
	repo.secrets[def.ID+":"+kind] = &Secret{
		ID:                "sec-" + kind,
		NamedCredentialID: def.ID,
		SecretKind:        kind,
		SecretValue:       "",
		RotatedAt:         time.Now().UTC(),
	}
}

func adminUser() *models.UserSession {
	return &models.UserSession{ID: "user-1"}
}

// --- tests ---

// AC 1: definitions can be installed without secret values.
func TestUpsertDefinition_NoSecretRequired(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // UpsertDefinition now requires ManageNamedCredentials

	def := Definition{
		DeveloperName: "SalesforceMarketing",
		Label:         "Salesforce Marketing Cloud",
		Endpoint:      "https://mc.example.com",
		PrincipalType: PrincipalTypeNamedPrincipal,
		AuthType:      AuthTypeOAuth2,
	}
	out, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if out.ID == "" {
		t.Fatal("expected non-empty ID after upsert")
	}
	if repo.upsertDefCalls != 1 {
		t.Fatalf("expected 1 upsertDef call, got %d", repo.upsertDefCalls)
	}
}

func TestUpsertDefinition_RejectsBadDeveloperName(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // permission granted; validation is the failure mode

	bad := []string{"123abc", "has space", ""}
	for _, name := range bad {
		def := Definition{
			DeveloperName: name,
			Label:         "Test",
			Endpoint:      "https://example.com",
			PrincipalType: PrincipalTypeNamedPrincipal,
			AuthType:      AuthTypeBearerToken,
		}
		_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
		if !errors.Is(err, ErrInvalidDefinition) {
			t.Errorf("name=%q: expected ErrInvalidDefinition, got %v", name, err)
		}
	}
}

func TestUpsertDefinition_RejectsUnsupportedAuthTypes(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // permission granted; validation is the failure mode

	cases := []struct {
		name     string
		authType string
	}{
		{name: "unknown auth type", authType: "saml"},
		{name: "legacy blank auth is not an admin write contract", authType: ""},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			def := Definition{
				DeveloperName: "ValidName",
				Label:         "Test",
				Endpoint:      "https://example.com",
				PrincipalType: PrincipalTypeNamedPrincipal,
				AuthType:      tc.authType,
			}
			_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
			if !errors.Is(err, ErrInvalidDefinition) {
				t.Fatalf("expected ErrInvalidDefinition for auth type %q, got %v", tc.authType, err)
			}
		})
	}
}

// AC 6 / round 4 fix: UpsertDefinition requires ManageNamedCredentials.
func TestUpsertDefinition_RequiresPermission(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false) // no permission
	def := Definition{
		DeveloperName: "ValidName",
		Label:         "Test",
		Endpoint:      "https://example.com",
		PrincipalType: PrincipalTypeNamedPrincipal,
		AuthType:      AuthTypeBearerToken,
	}
	_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
	if repo.upsertDefCalls != 0 {
		t.Fatalf("expected 0 UpsertDefinition calls, got %d", repo.upsertDefCalls)
	}
}

func TestUpsertDefinition_RejectsPackOwnedExistingDefinition(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	seedPackOwnedDef(repo, "ManagedNC", "connector-pack")

	def := Definition{
		DeveloperName: "ManagedNC",
		Label:         "Updated Label",
		Endpoint:      "https://example.com",
		PrincipalType: PrincipalTypeNamedPrincipal,
		AuthType:      AuthTypeBearerToken,
	}
	_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
	if repo.upsertDefCalls != 0 {
		t.Fatalf("expected 0 UpsertDefinition calls, got %d", repo.upsertDefCalls)
	}
	if got := repo.defs["ManagedNC"]; got == nil || got.OwnerPack != "connector-pack" || got.Label != "Test ManagedNC" {
		t.Fatalf("pack-owned definition mutated unexpectedly: %+v", got)
	}
}

func TestDeleteDefinition_RejectsPackOwnedExistingDefinition(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	seedPackOwnedDef(repo, "ManagedNC", "connector-pack")

	err := svc.DeleteDefinition(context.Background(), adminUser(), "ManagedNC")
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
	if repo.deleteDefCalls != 0 {
		t.Fatalf("expected 0 DeleteDefinition calls, got %d", repo.deleteDefCalls)
	}
	if _, ok := repo.defs["ManagedNC"]; !ok {
		t.Fatal("pack-owned definition was deleted")
	}
}

// AC 6: SetSecret requires ManageNamedCredentials.
func TestSetSecret_RequiresPermission(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false) // no permission
	seedDef(repo, "MyNC")

	_, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Token", "secret-value")
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
	if repo.upsertSecCalls != 0 {
		t.Fatalf("expected 0 UpsertSecret calls, got %d", repo.upsertSecCalls)
	}
}

func TestSetSecret_RejectsInvalidKindForAuthType(t *testing.T) {
	tests := []struct {
		name     string
		authType string
		kind     string
	}{
		{name: "legacy blank auth type rejects username", authType: "", kind: "Username"},
		{name: "bearer token kind must be Token", authType: AuthTypeBearerToken, kind: "BearerToken"},
		{name: "oauth2 token kind must be Token", authType: AuthTypeOAuth2, kind: "Username"},
		{name: "basic auth rejects token kind", authType: AuthTypeBasicAuth, kind: "Token"},
		{name: "unknown auth type fails closed", authType: "Unknown", kind: "Token"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			repo := newMockRepo()
			svc := newService(repo, true)
			seedDefWithAuthType(repo, "MyNC", tc.authType)

			_, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", tc.kind, "secret-value")
			if !errors.Is(err, ErrInvalidSecret) {
				t.Fatalf("expected ErrInvalidSecret, got %v", err)
			}
			if repo.upsertSecCalls != 0 {
				t.Fatalf("expected 0 UpsertSecret calls, got %d", repo.upsertSecCalls)
			}
		})
	}
}

func TestSetSecret_RejectsBlankPlaintext(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	seedDefWithAuthType(repo, "MyNC", AuthTypeBearerToken)

	_, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Token", "   ")
	if !errors.Is(err, ErrInvalidSecret) {
		t.Fatalf("expected ErrInvalidSecret, got %v", err)
	}
	if repo.upsertSecCalls != 0 {
		t.Fatalf("expected 0 UpsertSecret calls, got %d", repo.upsertSecCalls)
	}
}

func TestSetSecret_AllowsSupportedKindsForAuthType(t *testing.T) {
	tests := []struct {
		name     string
		authType string
		kind     string
	}{
		{name: "legacy blank auth type uses Token", authType: "", kind: "Token"},
		{name: "bearer token uses Token", authType: AuthTypeBearerToken, kind: "Token"},
		{name: "oauth2 uses Token", authType: AuthTypeOAuth2, kind: "Token"},
		{name: "basic auth username", authType: AuthTypeBasicAuth, kind: "Username"},
		{name: "basic auth password", authType: AuthTypeBasicAuth, kind: "Password"},
		{name: "custom remains flexible", authType: AuthTypeCustom, kind: "BearerToken"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			repo := newMockRepo()
			svc := newService(repo, true)
			seedDefWithAuthType(repo, "MyNC", tc.authType)

			meta, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", tc.kind, "secret-value")
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if meta.SecretKind != tc.kind {
				t.Fatalf("SecretKind = %q, want %q", meta.SecretKind, tc.kind)
			}
			if repo.upsertSecCalls != 1 {
				t.Fatalf("expected 1 UpsertSecret call, got %d", repo.upsertSecCalls)
			}
		})
	}
}

func TestHasRequiredSecrets_BearerTokenComplete(t *testing.T) {
	repo := newMockRepo()
	def := seedDefWithAuthType(repo, "MyNC", AuthTypeBearerToken)
	seedSecretMetadata(repo, def, "Token")

	ok, err := NewService(repo, &mockPermChecker{}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets: %v", err)
	}
	if !ok {
		t.Fatalf("HasRequiredSecrets = false, want true")
	}
}

func TestHasRequiredSecrets_BasicAuthRequiresUsernameAndPassword(t *testing.T) {
	repo := newMockRepo()
	def := seedDefWithAuthType(repo, "MyNC", AuthTypeBasicAuth)
	seedSecretMetadata(repo, def, "Username")

	ok, err := NewService(repo, &mockPermChecker{}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets: %v", err)
	}
	if ok {
		t.Fatalf("HasRequiredSecrets = true with missing Password")
	}

	seedSecretMetadata(repo, def, "Password")
	ok, err = NewService(repo, &mockPermChecker{}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets after password: %v", err)
	}
	if !ok {
		t.Fatalf("HasRequiredSecrets = false after Username+Password")
	}
}

func TestHasRequiredSecrets_BlankRequiredSecretFailsClosed(t *testing.T) {
	repo := newMockRepo()
	def := seedDefWithAuthType(repo, "MyNC", AuthTypeBearerToken)
	seedBlankSecretMetadata(repo, def, "Token")

	ok, err := NewService(repo, &mockPermChecker{}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets: %v", err)
	}
	if ok {
		t.Fatalf("HasRequiredSecrets = true with blank Token metadata")
	}
}

func TestHasRequiredSecrets_DecryptFailureFailsClosed(t *testing.T) {
	repo := newMockRepo()
	def := seedDefWithAuthType(repo, "MyNC", AuthTypeBearerToken)
	seedSecretMetadata(repo, def, "Token")
	repo.getSecretErr = errors.New("decrypt secret value: bad key")

	ok, err := NewService(repo, &mockPermChecker{}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets: %v", err)
	}
	if ok {
		t.Fatalf("HasRequiredSecrets = true when required secret cannot be decrypted")
	}
	if repo.getSecretCalls != 1 {
		t.Fatalf("HasRequiredSecrets called GetSecret %d times; want runtime decryptability check", repo.getSecretCalls)
	}
}

func TestHasRequiredSecrets_CustomAuthFailsClosed(t *testing.T) {
	repo := newMockRepo()
	def := seedDefWithAuthType(repo, "MyNC", AuthTypeCustom)
	seedSecretMetadata(repo, def, "Whatever")

	ok, err := NewService(repo, &mockPermChecker{}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets: %v", err)
	}
	if ok {
		t.Fatalf("HasRequiredSecrets = true for Custom auth without required-kind metadata")
	}
}

func TestHasRequiredSecrets_PackInactiveFailsClosed(t *testing.T) {
	repo := newMockRepo()
	def := seedPackOwnedDef(repo, "MyNC", "connector-pack")
	seedSecretMetadata(repo, def, "Token")

	ok, err := NewServiceWithPacks(repo, &mockPermChecker{}, &mockPackResolver{active: false}).HasRequiredSecrets(context.Background(), "MyNC")
	if err != nil {
		t.Fatalf("HasRequiredSecrets: %v", err)
	}
	if ok {
		t.Fatalf("HasRequiredSecrets = true for inactive pack-owned credential")
	}
}

// AC 4 & masked hint correctness.
func TestSetSecret_ComputesMaskedHint(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // has permission
	seedDefWithAuthType(repo, "MyNC", AuthTypeCustom)

	// plaintext > 4 chars: last 4 appended with ellipsis
	meta, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Token", "abcdef1234")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if meta.MaskedHint != "…1234" {
		t.Errorf("expected hint '…1234', got %q", meta.MaskedHint)
	}

	// plaintext <= 4 chars: short secrets get pure ellipsis, no plaintext disclosed
	meta2, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Short", "ab")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if meta2.MaskedHint != "…" {
		t.Errorf("expected hint '…' for short secret, got %q", meta2.MaskedHint)
	}

	// Empty plaintext still has an empty pure hint, but SetSecret rejects it.
	if got := maskedHint(""); got != "" {
		t.Errorf("expected hint '' for empty secret, got %q", got)
	}

	// exactly 4 runes: pure ellipsis
	meta2c, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Exact4", "abcd")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if meta2c.MaskedHint != "…" {
		t.Errorf("expected hint '…' for 4-rune secret, got %q", meta2c.MaskedHint)
	}

	// multi-byte chars: rune-safe, must not panic
	meta3, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Multi", "日本語abcd")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// "日本語abcd" = 7 runes; last 4 runes = "abcd"
	if meta3.MaskedHint != "…abcd" {
		t.Errorf("expected hint '…abcd' for multi-byte input, got %q", meta3.MaskedHint)
	}
}

// AC 2: SetSecret must not touch the definition row.
func TestSetSecret_DoesNotTouchDefinitionRow(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	seedDefWithAuthType(repo, "MyNC", AuthTypeCustom)

	_, err := svc.SetSecret(context.Background(), adminUser(), "MyNC", "Value", "plain")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if repo.upsertDefCalls != 0 {
		t.Fatalf("SetSecret must not call UpsertDefinition; got %d calls", repo.upsertDefCalls)
	}
	if repo.upsertSecCalls != 1 {
		t.Fatalf("expected exactly 1 UpsertSecret call, got %d", repo.upsertSecCalls)
	}
}

// AC 6: GetSecret requires ManageNamedCredentials.
func TestGetSecret_RequiresPermission(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false)
	seedDef(repo, "MyNC")

	_, err := svc.GetSecret(context.Background(), adminUser(), "MyNC", "Token")
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
}

// AC 3: GetSecret returns plaintext when permission granted.
func TestGetSecret_ReturnsPlaintext(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	d := seedDef(repo, "MyNC")
	repo.secrets["def-MyNC:BearerToken"] = &Secret{
		ID:                "s1",
		NamedCredentialID: d.ID,
		SecretKind:        "BearerToken",
		SecretValue:       "my-plaintext",
		MaskedHint:        "…text",
		RotatedAt:         time.Now(),
	}

	sec, err := svc.GetSecret(context.Background(), adminUser(), "MyNC", "BearerToken")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if sec.SecretValue != "my-plaintext" {
		t.Errorf("expected plaintext 'my-plaintext', got %q", sec.SecretValue)
	}
}

func TestGetSecret_NotFound(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	seedDef(repo, "MyNC")

	_, err := svc.GetSecret(context.Background(), adminUser(), "MyNC", "Missing")
	if !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound, got %v", err)
	}
}

// AC 4: ListSecretMetadata returns SecretMetadata only — structural proof that
// SecretValue is not accessible via SecretMetadata type.
func TestListSecretMetadata_NoPlaintextLeak(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // ViewSetup gate now required
	d := seedDef(repo, "MyNC")
	repo.secrets["def-MyNC:Token"] = &Secret{
		ID:                "s1",
		NamedCredentialID: d.ID,
		SecretKind:        "Token",
		SecretValue:       "super-secret",
		MaskedHint:        "…cret",
		RotatedAt:         time.Now(),
	}

	metas, err := svc.ListSecretMetadata(context.Background(), adminUser(), "MyNC")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(metas) != 1 {
		t.Fatalf("expected 1 secret metadata, got %d", len(metas))
	}
	m := metas[0]
	if m.SecretKind != "Token" {
		t.Errorf("expected SecretKind 'Token', got %q", m.SecretKind)
	}
	// SecretMetadata has no SecretValue field — this is a compile-time guarantee.
	// The runtime assertion: verify returned type is SecretMetadata, not Secret.
	var _ SecretMetadata = m // type assertion; would not compile if SecretValue existed
}

// AC 4: ListDefinitions returns Definition structs — structural proof that no
// SecretValue field exists on Definition.
func TestListDefinitions_NoSecretLeak(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // ViewSetup gate now required
	seedDef(repo, "NC1")

	defs, err := svc.ListDefinitions(context.Background(), adminUser())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(defs) != 1 {
		t.Fatalf("expected 1 definition, got %d", len(defs))
	}
	// Compile-time: Definition has no SecretValue field.
	var _ Definition = defs[0]
}

func TestDeleteDefinition_DelegatesToRepo(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true) // DeleteDefinition now requires ManageNamedCredentials
	seedDef(repo, "ToDelete")

	err := svc.DeleteDefinition(context.Background(), adminUser(), "ToDelete")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if repo.deleteDefCalls != 1 {
		t.Fatalf("expected 1 DeleteDefinition call, got %d", repo.deleteDefCalls)
	}
}

// AC 6 / round 4 fix: DeleteDefinition requires ManageNamedCredentials.
func TestDeleteDefinition_RequiresPermission(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false) // no permission
	seedDef(repo, "MyNC")

	err := svc.DeleteDefinition(context.Background(), adminUser(), "MyNC")
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
	if repo.deleteDefCalls != 0 {
		t.Fatalf("expected 0 DeleteDefinition calls, got %d", repo.deleteDefCalls)
	}
}

// --- ResolveCredentialValue tests ---

// seedSecret inserts a secret into the mock repo for an already-seeded definition.
func seedSecret(repo *mockRepo, defID, kind, plaintext string) {
	key := defID + ":" + kind
	repo.secrets[key] = &Secret{
		ID:                "sec-" + kind,
		NamedCredentialID: defID,
		SecretKind:        kind,
		SecretValue:       plaintext,
		MaskedHint:        maskedHint(plaintext),
		RotatedAt:         time.Now(),
	}
}

// AC 3: server-internal runtime read returns plaintext, no permission needed.
func TestResolveCredentialValue_ReturnsPlaintext(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	seedSecret(repo, d.ID, "Token", "sk_test_plaintext")
	svc := NewService(repo, &mockPermChecker{allow: false}) // no perm needed

	val, err := svc.ResolveCredentialValue(context.Background(), "test-credential", "Token")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if val != "sk_test_plaintext" {
		t.Errorf("expected plaintext 'sk_test_plaintext', got %q", val)
	}
}

func TestProvisionDefinitionAndSecret_ResolveCredentialValueReturnsPlaintext(t *testing.T) {
	repo := newMockRepo()
	svc := NewService(repo, &mockPermChecker{allow: true})
	user := adminUser()

	_, err := svc.UpsertDefinition(context.Background(), user, Definition{
		DeveloperName: "ProvisionedNC",
		Label:         "Provisioned Named Credential",
		Endpoint:      "https://api.example.com",
		PrincipalType: PrincipalTypeNamedPrincipal,
		AuthType:      AuthTypeBearerToken,
	})
	if err != nil {
		t.Fatalf("UpsertDefinition: %v", err)
	}
	if _, err := svc.SetSecret(context.Background(), user, "ProvisionedNC", "Token", "server-internal-plaintext"); err != nil {
		t.Fatalf("SetSecret: %v", err)
	}

	got, err := svc.ResolveCredentialValue(context.Background(), "ProvisionedNC", "Token")
	if err != nil {
		t.Fatalf("ResolveCredentialValue: %v", err)
	}
	if got != "server-internal-plaintext" {
		t.Fatalf("ResolveCredentialValue = %q, want plaintext", got)
	}
}

// AC: active-state gate — OwnerPack=="" → gate skipped → success.
func TestResolveCredentialValue_NoOwnerPack_SkipsGate(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "noop-credential")
	// OwnerPack is empty (default from seedDef)
	seedSecret(repo, d.ID, "Token", "sk_test_noop")
	svc := NewService(repo, &mockPermChecker{allow: false}) // no packs resolver

	val, err := svc.ResolveCredentialValue(context.Background(), "noop-credential", "Token")
	if err != nil {
		t.Fatalf("expected success when OwnerPack is empty, got %v", err)
	}
	if val != "sk_test_noop" {
		t.Errorf("expected 'sk_test_noop', got %q", val)
	}
}

// AC: active-state gate — OwnerPack set + resolver returns active=false → ErrConnectorPackInactive.
func TestResolveCredentialValue_InactivePack_FailsClosed(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	d.OwnerPack = "test-integration-pack"
	repo.defs["test-credential"] = d
	seedSecret(repo, d.ID, "Token", "sk_test_secret")
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{active: false})

	_, err := svc.ResolveCredentialValue(context.Background(), "test-credential", "Token")
	if !errors.Is(err, ErrConnectorPackInactive) {
		t.Fatalf("expected ErrConnectorPackInactive, got %v", err)
	}
}

// AC: active-state gate — OwnerPack set + resolver returns active=true → success.
func TestResolveCredentialValue_ActivePack_Succeeds(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	d.OwnerPack = "test-integration-pack"
	repo.defs["test-credential"] = d
	seedSecret(repo, d.ID, "Token", "sk_test_active")
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{active: true})

	val, err := svc.ResolveCredentialValue(context.Background(), "test-credential", "Token")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if val != "sk_test_active" {
		t.Errorf("expected 'sk_test_active', got %q", val)
	}
}

func TestResolveCredentialValue_DrainScopedInactivePack_Succeeds(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	d.OwnerPack = "test-integration-pack"
	repo.defs["test-credential"] = d
	seedSecret(repo, d.ID, "Token", "sk_test_drain")
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{
		active:         false,
		drainNamespace: "test-integration-pack",
		drainInstallID: "install-drain",
		drainAllowed:   true,
	})

	ctx := WithDrainOnlyInstalledPack(context.Background(), "test-integration-pack", "install-drain")
	val, err := svc.ResolveCredentialValue(ctx, "test-credential", "Token")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if val != "sk_test_drain" {
		t.Errorf("expected 'sk_test_drain', got %q", val)
	}
}

func TestResolveCredentialValue_DrainScopeDoesNotFallBackToActivePack(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	d.OwnerPack = "test-integration-pack"
	repo.defs["test-credential"] = d
	seedSecret(repo, d.ID, "Token", "sk_test_drain")
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{
		active:         true,
		drainNamespace: "test-integration-pack",
		drainInstallID: "install-drain",
		drainAllowed:   false,
	})

	ctx := WithDrainOnlyInstalledPack(context.Background(), "test-integration-pack", "install-drain")
	_, err := svc.ResolveCredentialValue(ctx, "test-credential", "Token")
	if !errors.Is(err, ErrConnectorPackInactive) {
		t.Fatalf("expected ErrConnectorPackInactive, got %v", err)
	}
}

func TestResolveCredentialValue_DrainScopedInactivePack_MismatchedInstallFailsClosed(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	d.OwnerPack = "test-integration-pack"
	repo.defs["test-credential"] = d
	seedSecret(repo, d.ID, "Token", "sk_test_drain")
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{
		active:         false,
		drainNamespace: "test-integration-pack",
		drainInstallID: "install-drain",
		drainAllowed:   true,
	})

	ctx := WithDrainOnlyInstalledPack(context.Background(), "test-integration-pack", "install-other")
	_, err := svc.ResolveCredentialValue(ctx, "test-credential", "Token")
	if !errors.Is(err, ErrConnectorPackInactive) {
		t.Fatalf("expected ErrConnectorPackInactive, got %v", err)
	}
}

// Defensive: OwnerPack set but Service constructed without resolver → fails closed.
func TestResolveCredentialValue_PackScopedButNoResolver_FailsClosed(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "test-credential")
	d.OwnerPack = "test-integration-pack"
	repo.defs["test-credential"] = d
	seedSecret(repo, d.ID, "Token", "sk_test_value")
	svc := NewService(repo, &mockPermChecker{allow: false}) // no packs resolver

	_, err := svc.ResolveCredentialValue(context.Background(), "test-credential", "Token")
	if !errors.Is(err, ErrConnectorPackInactive) {
		t.Fatalf("expected ErrConnectorPackInactive when pack-scoped but no resolver, got %v", err)
	}
}

// AC: not-found cases.
func TestResolveCredentialValue_DefinitionMissing_NotFound(t *testing.T) {
	repo := newMockRepo()
	svc := NewService(repo, &mockPermChecker{allow: false})

	_, err := svc.ResolveCredentialValue(context.Background(), "nonexistent", "Token")
	if !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound for missing definition, got %v", err)
	}
}

func TestResolveCredentialValue_SecretMissing_NotFound(t *testing.T) {
	repo := newMockRepo()
	seedDef(repo, "noop-credential")
	svc := NewService(repo, &mockPermChecker{allow: false})

	_, err := svc.ResolveCredentialValue(context.Background(), "noop-credential", "MissingKind")
	if !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound for missing secret, got %v", err)
	}
}

// --- ResolveEndpoint tests ---

// TestResolveEndpoint_AdminInstalled verifies that admin-installed credentials
// (OwnerPack == "") return the endpoint URL without any pack-active gate.
func TestResolveEndpoint_AdminInstalled(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "admin-nc")
	// OwnerPack is empty by default in seedDef — simulates admin-installed credential.
	svc := NewService(repo, &mockPermChecker{allow: false})

	endpoint, err := svc.ResolveEndpoint(context.Background(), "admin-nc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if endpoint != d.Endpoint {
		t.Errorf("expected endpoint %q, got %q", d.Endpoint, endpoint)
	}
}

// TestResolveEndpoint_PackActive verifies that a pack-scoped credential with an
// active pack resolver returns the endpoint URL.
func TestResolveEndpoint_PackActive(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-nc")
	d.OwnerPack = "test-connector-pack"
	repo.defs["pack-nc"] = d
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{active: true})

	endpoint, err := svc.ResolveEndpoint(context.Background(), "pack-nc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if endpoint != d.Endpoint {
		t.Errorf("expected endpoint %q, got %q", d.Endpoint, endpoint)
	}
}

// TestResolveEndpoint_PackInactive verifies that a pack-scoped credential with an
// inactive pack resolver returns ErrConnectorPackInactive.
func TestResolveEndpoint_PackInactive(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-nc-inactive")
	d.OwnerPack = "test-connector-pack"
	repo.defs["pack-nc-inactive"] = d
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{active: false})

	_, err := svc.ResolveEndpoint(context.Background(), "pack-nc-inactive")
	if !errors.Is(err, ErrConnectorPackInactive) {
		t.Fatalf("expected ErrConnectorPackInactive, got %v", err)
	}
}

// TestResolveEndpoint_NotFound verifies that a missing definition returns ErrNotFound.
func TestResolveEndpoint_NotFound(t *testing.T) {
	repo := newMockRepo()
	svc := NewService(repo, &mockPermChecker{allow: false})

	_, err := svc.ResolveEndpoint(context.Background(), "nonexistent-nc")
	if !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound for missing definition, got %v", err)
	}
}

// R5-1: validateDefinition rejects Endpoint with userinfo.
func TestUpsertDefinition_RejectsEndpointWithUserinfo(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)

	cases := []string{
		"https://user:pass@host.example.com",
		"https://user@host.example.com",
		"http://admin:secret@host.example.com/path",
	}
	for _, ep := range cases {
		def := Definition{
			DeveloperName: "TestNC",
			Label:         "Test",
			Endpoint:      ep,
			PrincipalType: PrincipalTypeNamedPrincipal,
			AuthType:      AuthTypeBearerToken,
		}
		_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
		if !errors.Is(err, ErrInvalidDefinition) {
			t.Errorf("endpoint=%q: expected ErrInvalidDefinition, got %v", ep, err)
		}
	}
}

// R5-1: validateDefinition rejects non-http(s) schemes.
func TestUpsertDefinition_RejectsNonHTTPScheme(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)

	cases := []string{
		"ftp://host.example.com",
		"file:///etc/passwd",
		"javascript:alert(1)",
		"not-a-url-at-all",
		"://broken",
	}
	for _, ep := range cases {
		def := Definition{
			DeveloperName: "TestNC",
			Label:         "Test",
			Endpoint:      ep,
			PrincipalType: PrincipalTypeNamedPrincipal,
			AuthType:      AuthTypeBearerToken,
		}
		_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
		if !errors.Is(err, ErrInvalidDefinition) {
			t.Errorf("endpoint=%q: expected ErrInvalidDefinition, got %v", ep, err)
		}
	}
}

// R6-1: validateDefinition rejects Endpoint with query string or fragment
// (any non-base-URL substring that can smuggle secret material into the
// less-gated definition row).
func TestUpsertDefinition_RejectsEndpointWithQueryOrFragment(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)

	cases := []string{
		"https://api.example.com?api_key=secret",
		"https://api.example.com/path?token=abc",
		"https://api.example.com#access_token=oauth",
		"https://api.example.com/path?a=1&b=2",
	}
	for _, ep := range cases {
		def := Definition{
			DeveloperName: "TestNC",
			Label:         "Test",
			Endpoint:      ep,
			PrincipalType: PrincipalTypeNamedPrincipal,
			AuthType:      AuthTypeBearerToken,
		}
		_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
		if !errors.Is(err, ErrInvalidDefinition) {
			t.Errorf("endpoint=%q: expected ErrInvalidDefinition (no query/fragment), got %v", ep, err)
		}
	}
}

func TestUpsertDefinition_RejectsHTTPForCredentialedAuthTypes(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)

	cases := []struct {
		name     string
		authType string
	}{
		{name: "bearer token", authType: AuthTypeBearerToken},
		{name: "oauth2", authType: AuthTypeOAuth2},
		{name: "basic auth", authType: AuthTypeBasicAuth},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			def := Definition{
				DeveloperName: "TestNC",
				Label:         "Test",
				Endpoint:      "http://api.example.com",
				PrincipalType: PrincipalTypeNamedPrincipal,
				AuthType:      tc.authType,
			}
			_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
			if !errors.Is(err, ErrInvalidDefinition) {
				t.Fatalf("expected ErrInvalidDefinition, got %v", err)
			}
		})
	}
}

// R5-1: valid endpoints still accepted.
func TestUpsertDefinition_AcceptsValidEndpoints(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)
	cases := []struct {
		endpoint string
		authType string
	}{
		{endpoint: "https://api.example.com", authType: AuthTypeBearerToken},
		{endpoint: "https://api.example.com/path/to/endpoint", authType: AuthTypeBasicAuth},
		{endpoint: "http://internal-host:8080", authType: AuthTypeCustom},
	}
	for i, tc := range cases {
		def := Definition{
			DeveloperName: fmt.Sprintf("Valid%d", i),
			Label:         "Test",
			Endpoint:      tc.endpoint,
			PrincipalType: PrincipalTypeNamedPrincipal,
			AuthType:      tc.authType,
		}
		_, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
		if err != nil {
			t.Errorf("endpoint=%q authType=%q: expected accept, got %v", tc.endpoint, tc.authType, err)
		}
	}
}

// R7-1: ListDefinitions requires ViewSetup.
func TestListDefinitions_RequiresViewSetup(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false) // no permission
	_, err := svc.ListDefinitions(context.Background(), adminUser())
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
}

// R7-1: GetDefinition requires ViewSetup.
func TestGetDefinition_RequiresViewSetup(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false)
	seedDef(repo, "MyNC")
	_, err := svc.GetDefinition(context.Background(), adminUser(), "MyNC")
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
}

// R7-1: ListSecretMetadata requires ViewSetup.
func TestListSecretMetadata_RequiresViewSetup(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, false)
	seedDef(repo, "MyNC")
	_, err := svc.ListSecretMetadata(context.Background(), adminUser(), "MyNC")
	if !errors.Is(err, ErrPermissionDenied) {
		t.Fatalf("expected ErrPermissionDenied, got %v", err)
	}
}

// R5-2: UpsertDefinition clears OwnerPack from admin-authored rows.
func TestUpsertDefinition_ClearsOwnerPack(t *testing.T) {
	repo := newMockRepo()
	svc := newService(repo, true)

	def := Definition{
		DeveloperName: "AdminAuthored",
		Label:         "Test",
		Endpoint:      "https://api.example.com",
		PrincipalType: PrincipalTypeNamedPrincipal,
		AuthType:      AuthTypeBearerToken,
		OwnerPack:     "some-connector-pack", // attacker tries to impersonate pack ownership
	}
	got, err := svc.UpsertDefinition(context.Background(), adminUser(), def)
	if err != nil {
		t.Fatalf("upsert: %v", err)
	}
	if got.OwnerPack != "" {
		t.Errorf("returned OwnerPack = %q, want \"\" (admin path must not author OwnerPack)", got.OwnerPack)
	}
	// Also verify the repo got the cleared value.
	stored := repo.defs["AdminAuthored"]
	if stored == nil {
		t.Fatal("definition not persisted")
	}
	if stored.OwnerPack != "" {
		t.Errorf("persisted OwnerPack = %q, want \"\"", stored.OwnerPack)
	}
}

// --- ResolveAuthType tests ---

// TestResolveAuthType_AdminInstalled verifies that admin-installed credentials
// (OwnerPack == "") return the AuthType without any pack-active gate.
func TestResolveAuthType_AdminInstalled(t *testing.T) {
	repo := newMockRepo()
	seedDef(repo, "admin-nc") // AuthType = AuthTypeBearerToken from seedDef
	svc := NewService(repo, &mockPermChecker{allow: false})

	authType, err := svc.ResolveAuthType(context.Background(), "admin-nc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if authType != AuthTypeBearerToken {
		t.Errorf("expected %q, got %q", AuthTypeBearerToken, authType)
	}
}

// TestResolveAuthType_NotFound verifies that a missing definition returns ErrNotFound.
func TestResolveAuthType_NotFound(t *testing.T) {
	svc := NewService(newMockRepo(), &mockPermChecker{allow: false})

	_, err := svc.ResolveAuthType(context.Background(), "nonexistent")
	if !errors.Is(err, ErrNotFound) {
		t.Fatalf("expected ErrNotFound, got %v", err)
	}
}

// TestResolveAuthType_PackInactive verifies that pack-scoped credentials with
// an inactive pack return ErrConnectorPackInactive.
func TestResolveAuthType_PackInactive(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-nc")
	d.OwnerPack = "test-connector-pack"
	repo.defs["pack-nc"] = d
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{active: false})

	_, err := svc.ResolveAuthType(context.Background(), "pack-nc")
	if !errors.Is(err, ErrConnectorPackInactive) {
		t.Fatalf("expected ErrConnectorPackInactive, got %v", err)
	}
}

func TestResolveAuthType_DrainScopedInactivePack_Succeeds(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-nc")
	d.OwnerPack = "test-connector-pack"
	repo.defs["pack-nc"] = d
	svc := NewServiceWithPacks(repo, &mockPermChecker{allow: false}, &mockPackResolver{
		active:         false,
		drainNamespace: "test-connector-pack",
		drainInstallID: "install-drain",
		drainAllowed:   true,
	})

	ctx := WithDrainOnlyInstalledPack(context.Background(), "test-connector-pack", "install-drain")
	authType, err := svc.ResolveAuthType(ctx, "pack-nc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if authType != AuthTypeBearerToken {
		t.Fatalf("authType = %q, want %q", authType, AuthTypeBearerToken)
	}
}

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

	resolver := NewControlplanePackResolver(db)
	mock.ExpectQuery("SELECT 1").
		WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"1"}))

	active, err := resolver.ResolveDrainOnlyConnectorPackByInstall(context.Background(), "drain-pack", "install-old")
	if err != nil {
		t.Fatalf("ResolveDrainOnlyConnectorPackByInstall: %v", err)
	}
	if active {
		t.Fatal("active = true, want false when exact non-active installed pack has no non-terminal drain events")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sqlmock expectations: %v", err)
	}
}

// TestControlplanePackResolver_AuthorizesDrainOnlyPackWithWorkItemBacklog pins the
// work-item-inclusive drain query: the WithArgs (namespace, installID, category,
// and the three active-status comparisons) require the exact non-active installed
// pack incarnation, and a returned row authorizes drain-only access. (The loose
// "SELECT 1" variant of this scenario was subsumed by this stricter pattern.)
func TestControlplanePackResolver_AuthorizesDrainOnlyPackWithWorkItemBacklog(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	resolver := NewControlplanePackResolver(db)
	mock.ExpectQuery(`(?s)SELECT 1.*_System_ConnectorWorkItem w.*w\.State IN \([^)]*'processing'[^)]*\)`).
		WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(1))

	active, err := resolver.ResolveDrainOnlyConnectorPackByInstall(context.Background(), "drain-pack", "install-old")
	if err != nil {
		t.Fatalf("ResolveDrainOnlyConnectorPackByInstall: %v", err)
	}
	if !active {
		t.Fatal("active = false, want true for WorkItem-only drain backlog")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sqlmock expectations: %v", err)
	}
}

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

	resolver := NewControlplanePackResolver(db)
	mock.ExpectQuery(`(?s)SELECT 1.*_System_ConnectorWorkItem w`).
		WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
		WillReturnError(errors.New("Error 1146 (42S02): Table '_System_ConnectorWorkItem' doesn't exist"))
	mock.ExpectQuery(`(?s)SELECT 1.*_System_ConnectorEvent e`).
		WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(1))

	active, err := resolver.ResolveDrainOnlyConnectorPackByInstall(context.Background(), "drain-pack", "install-old")
	if err != nil {
		t.Fatalf("ResolveDrainOnlyConnectorPackByInstall: %v", err)
	}
	if !active {
		t.Fatal("active = false, want true after event-only fallback")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sqlmock expectations: %v", err)
	}
}

func TestControlplanePackResolver_FallsBackWhenWorkItemSchemaColumnsMissing(t *testing.T) {
	for _, tc := range []struct {
		name string
		err  string
	}{
		{name: "unknown column", err: "Error 1054 (42S22): Unknown column 'w.State' in 'where clause'"},
		{name: "no such column", err: "no such column: w.InstalledPackId"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock.New: %v", err)
			}
			defer db.Close()

			resolver := NewControlplanePackResolver(db)
			mock.ExpectQuery(`(?s)SELECT 1.*_System_ConnectorWorkItem w`).
				WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
				WillReturnError(errors.New(tc.err))
			mock.ExpectQuery(`(?s)SELECT 1.*_System_ConnectorEvent e`).
				WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
				WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(1))

			active, err := resolver.ResolveDrainOnlyConnectorPackByInstall(context.Background(), "drain-pack", "install-old")
			if err != nil {
				t.Fatalf("ResolveDrainOnlyConnectorPackByInstall: %v", err)
			}
			if !active {
				t.Fatal("active = false, want true after event-only fallback")
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sqlmock expectations: %v", err)
			}
		})
	}
}

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

	resolver := NewControlplanePackResolver(db)
	mock.ExpectQuery(`active_pack\.Status = \?`).
		WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"1"}))

	active, err := resolver.ResolveDrainOnlyConnectorPackByInstall(context.Background(), "drain-pack", "install-old")
	if err != nil {
		t.Fatalf("ResolveDrainOnlyConnectorPackByInstall: %v", err)
	}
	if active {
		t.Fatal("active = true, want false when drain-only install has an active sibling rebirth")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sqlmock expectations: %v", err)
	}
}

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

	resolver := NewControlplanePackResolver(db)
	mock.ExpectQuery(`other_historical_pack\.Status <> \?`).
		WithArgs("drain-pack", "install-old", string("connector-pack"), "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"1"}))

	active, err := resolver.ResolveDrainOnlyConnectorPackByInstall(context.Background(), "drain-pack", "install-old")
	if err != nil {
		t.Fatalf("ResolveDrainOnlyConnectorPackByInstall: %v", err)
	}
	if active {
		t.Fatal("active = true, want false when drain-only install has another surviving historical incarnation")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sqlmock expectations: %v", err)
	}
}

// --- ResolveCredentialPack ---

// Branch 1: definition row missing → ("", "", false, nil).
func TestResolveCredentialPack_DefinitionMissing(t *testing.T) {
	svc := NewService(newMockRepo(), &mockPermChecker{})

	canonID, ns, active, err := svc.ResolveCredentialPack(context.Background(), "nonexistent")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if canonID != "" || ns != "" || active {
		t.Errorf("missing def: got (%q, %q, %v), want (\"\", \"\", false)", canonID, ns, active)
	}
}

// Branch 2: OwnerPack=="" (admin-installed) → (d.ID, "", true, nil).
// Admin-installed credentials are throttle-eligible; the throttler falls back
// to DefaultRate/DefaultBurst because LimitProvider returns ok=false for the
// empty namespace. Codex Review PR #1519 round 2 (HIGH): old code returned
// ("", false, nil), making the substrate dead code in greenfield public-core
// orgs where every UpsertDefinition hard-clears OwnerPack.
// canonicalID is the stable row ID so case-variant DeveloperNames share one bucket.
func TestResolveCredentialPack_AdminInstalled_EligibleWithEmptyNamespace(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "admin-cred") // OwnerPack="" by default

	svc := NewService(repo, &mockPermChecker{})
	canonID, ns, active, err := svc.ResolveCredentialPack(context.Background(), "admin-cred")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if canonID != d.ID {
		t.Errorf("admin-installed: canonicalID = %q, want %q", canonID, d.ID)
	}
	if ns != "" || !active {
		t.Errorf("admin-installed: got (ns=%q, active=%v), want (\"\", true)", ns, active)
	}
}

// Branch 3: OwnerPack set + pack inactive → (d.ID, OwnerPack, false, nil).
func TestResolveCredentialPack_PackInactive_FailsClosed(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-cred")
	d.OwnerPack = "test-integration-pack"
	repo.defs["pack-cred"] = d

	svc := NewServiceWithPacks(repo, &mockPermChecker{}, &mockPackResolver{active: false})
	canonID, ns, active, err := svc.ResolveCredentialPack(context.Background(), "pack-cred")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if canonID != d.ID {
		t.Errorf("pack inactive: canonicalID = %q, want %q", canonID, d.ID)
	}
	if ns != "test-integration-pack" {
		t.Errorf("pack inactive: namespace = %q, want \"test-integration-pack\"", ns)
	}
	if active {
		t.Errorf("pack inactive: active = true, want false")
	}
}

// Branch 4: OwnerPack set + pack active → (d.ID, OwnerPack, true, nil).
func TestResolveCredentialPack_PackActive(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-cred")
	d.OwnerPack = "test-integration-pack"
	repo.defs["pack-cred"] = d

	svc := NewServiceWithPacks(repo, &mockPermChecker{}, &mockPackResolver{active: true})
	canonID, ns, active, err := svc.ResolveCredentialPack(context.Background(), "pack-cred")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if canonID != d.ID {
		t.Errorf("pack active: canonicalID = %q, want %q", canonID, d.ID)
	}
	if ns != "test-integration-pack" {
		t.Errorf("pack active: namespace = %q, want \"test-integration-pack\"", ns)
	}
	if !active {
		t.Errorf("pack active: active = false, want true")
	}
}

func TestResolveCredentialPack_DrainScopedInactivePack_Eligible(t *testing.T) {
	repo := newMockRepo()
	d := seedDef(repo, "pack-cred")
	d.OwnerPack = "test-integration-pack"
	repo.defs["pack-cred"] = d

	svc := NewServiceWithPacks(repo, &mockPermChecker{}, &mockPackResolver{
		active:         false,
		drainNamespace: "test-integration-pack",
		drainInstallID: "install-drain",
		drainAllowed:   true,
	})
	ctx := WithDrainOnlyInstalledPack(context.Background(), "test-integration-pack", "install-drain")
	canonID, ns, active, err := svc.ResolveCredentialPack(ctx, "pack-cred")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if canonID != d.ID {
		t.Errorf("drain scoped: canonicalID = %q, want %q", canonID, d.ID)
	}
	if ns != "test-integration-pack" {
		t.Errorf("drain scoped: namespace = %q, want test-integration-pack", ns)
	}
	if !active {
		t.Error("drain scoped: active = false, want true")
	}
}
