package packs

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

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

func TestIsPackActiveTruthTable(t *testing.T) {
	cases := []struct {
		name   string
		row    []driver.Value
		want   bool
		wantOK bool
	}{
		{
			name:   "active row returns true",
			row:    []driver.Value{"active", 0},
			want:   true,
			wantOK: true,
		},
		{
			name:   "removing row returns false",
			row:    []driver.Value{"removing", 0},
			want:   false,
			wantOK: true,
		},
		{
			name:   "installing row returns false",
			row:    []driver.Value{"installing", 0},
			want:   false,
			wantOK: true,
		},
		{
			name:   "failed row returns false",
			row:    []driver.Value{"failed", 0},
			want:   false,
			wantOK: true,
		},
		{
			name:   "deleted row returns false",
			row:    []driver.Value{"active", 1},
			want:   false,
			wantOK: true,
		},
		{
			name:   "missing row returns false",
			row:    nil,
			want:   false,
			wantOK: false,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			defer db.Close()

			expectInstalledPackStatusLookup(mock, "jira-like-sample", tc.row)

			got, err := IsPackActive(context.Background(), db, "jira-like-sample")
			if err != nil {
				t.Fatalf("IsPackActive: %v", err)
			}
			if got != tc.want {
				t.Fatalf("IsPackActive() = %v, want %v", got, tc.want)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

func TestIsPackInRemovalWindowTruthTable(t *testing.T) {
	cases := []struct {
		name string
		row  []driver.Value
		want bool
	}{
		{
			name: "removing row returns true",
			row:  []driver.Value{"removing", 0},
			want: true,
		},
		{
			name: "active row returns false",
			row:  []driver.Value{"active", 0},
			want: false,
		},
		{
			name: "installing row returns false",
			row:  []driver.Value{"installing", 0},
			want: false,
		},
		{
			name: "failed row returns false",
			row:  []driver.Value{"failed", 0},
			want: false,
		},
		{
			name: "deleted removing row returns false",
			row:  []driver.Value{"removing", 1},
			want: false,
		},
		{
			name: "missing row returns false",
			row:  nil,
			want: false,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			defer db.Close()

			expectInstalledPackStatusLookup(mock, "jira-like-sample", tc.row)

			got, err := IsPackInRemovalWindow(context.Background(), db, "jira-like-sample")
			if err != nil {
				t.Fatalf("IsPackInRemovalWindow: %v", err)
			}
			if got != tc.want {
				t.Fatalf("IsPackInRemovalWindow() = %v, want %v", got, tc.want)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

func TestIsFieldDeclaredByPackTruthTable(t *testing.T) {
	sourceDir := writeRuntimeTestPackSource(t, "jira-like-sample")

	cases := []struct {
		name         string
		row          []driver.Value
		objectAPI    string
		fieldAPI     string
		wantDeclared bool
		wantErr      string
	}{
		{
			name:         "active pack currently declares field",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", "current-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "Subject",
			wantDeclared: true,
		},
		{
			name:         "active pack current manifest no longer declares field",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", "current-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "LegacyField__c",
			wantDeclared: false,
		},
		{
			name:         "active pack object treats reserved OwnerId as declared",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", "current-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "OwnerId",
			wantDeclared: true,
		},
		{
			name:         "reserved OwnerId is not declared when object is absent from pack",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", "current-hash"},
			objectAPI:    "Case",
			fieldAPI:     "OwnerId",
			wantDeclared: false,
		},
		{
			name:         "removing pack is not treated as live declaration",
			row:          []driver.Value{"jira-like-sample", "app-pack", "removing", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", "current-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "Subject",
			wantDeclared: false,
		},
		{
			name:         "active pack with converged stale hashes returns error",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "stale-hash", "stale-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "Subject",
			wantDeclared: false,
			wantErr:      "AppliedPayloadHash",
		},
		{
			name:         "active pack with desired hash drift returns error",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", "stale-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "Subject",
			wantDeclared: false,
			wantErr:      "DesiredPayloadHash",
		},
		{
			name:         "active pack with missing applied hash returns error",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", nil, "current-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "Subject",
			wantDeclared: false,
			wantErr:      "AppliedPayloadHash",
		},
		{
			name:         "active pack with missing desired hash returns error",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "current-hash", nil},
			objectAPI:    "Issue",
			fieldAPI:     "Subject",
			wantDeclared: false,
			wantErr:      "DesiredPayloadHash",
		},
		{
			name:         "active pack with converged stale hashes still returns error for undeclared field",
			row:          []driver.Value{"jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, "local_path", sourceDir, "", "stale-hash", "stale-hash"},
			objectAPI:    "Issue",
			fieldAPI:     "LegacyField__c",
			wantDeclared: false,
			wantErr:      "AppliedPayloadHash",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if tc.row != nil && tc.row[5] == "local_path" {
				pack, err := ResolveInstalledSourceBackedPack("jira-like-sample", AppPack, "local_path", sourceDir)
				if err != nil {
					t.Fatalf("resolve installed pack: %v", err)
				}
				hash, err := PackPayloadHash(pack)
				if err != nil {
					t.Fatalf("payload hash: %v", err)
				}
				if tc.row[8] == "current-hash" {
					tc.row[8] = hash
				}
				if tc.row[9] == "current-hash" {
					tc.row[9] = hash
				}
			}
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			defer db.Close()

			expectInstalledPackDeclarationLookup(mock, "jira-like-sample", tc.row)

			got, err := IsFieldDeclaredByPack(context.Background(), db, "jira-like-sample", tc.objectAPI, tc.fieldAPI)
			if tc.wantErr != "" {
				if err == nil {
					t.Fatalf("IsFieldDeclaredByPack error = nil, want %q", tc.wantErr)
				}
				if !strings.Contains(err.Error(), tc.wantErr) {
					t.Fatalf("IsFieldDeclaredByPack error = %v, want substring %q", err, tc.wantErr)
				}
			} else if err != nil {
				t.Fatalf("IsFieldDeclaredByPack: %v", err)
			}
			if got != tc.wantDeclared {
				t.Fatalf("IsFieldDeclaredByPack() = %v, want %v", got, tc.wantDeclared)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

func expectInstalledPackStatusLookup(mock sqlmock.Sqlmock, packName string, row []driver.Value) {
	query := "SELECT Status, COALESCE\\(IsDeleted, 0\\) FROM _System_InstalledPack"
	if row == nil {
		mock.ExpectQuery(query).
			WithArgs(packName).
			WillReturnRows(sqlmock.NewRows([]string{"Status", "IsDeleted"}))
		return
	}
	mock.ExpectQuery(query).
		WithArgs(packName).
		WillReturnRows(sqlmock.NewRows([]string{"Status", "IsDeleted"}).AddRow(row...))
}

func expectInstalledPackDeclarationLookup(mock sqlmock.Sqlmock, packName string, row []driver.Value) {
	query := "SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack"
	mock.ExpectQuery(query).
		WithArgs(packName).
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).AddRow(row...))
}

// expectConnectorPackResolveLookup sets up the sqlmock expectation for ResolveConnectorPackByNamespace.
// row is [Id, PackInstallId, Name, Category, Status] or nil for no-row (ErrNoRows).
// AppliedPayloadHash and PackVersion are automatically appended as empty strings.
func expectConnectorPackResolveLookup(mock sqlmock.Sqlmock, namespace string, row []driver.Value) {
	query := `SELECT Id, PackInstallId, Name, Category, Status, COALESCE\(AppliedPayloadHash, ''\), COALESCE\(PackVersion, ''\)`
	if row == nil {
		mock.ExpectQuery(query).
			WithArgs(namespace, string(ConnectorPack), installedPackStatusActive).
			WillReturnRows(sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "PackVersion"}))
		return
	}
	// Append empty AppliedPayloadHash and PackVersion to existing 5-column row fixtures.
	rowWithVersion := append(append([]driver.Value(nil), row...), "", "")
	mock.ExpectQuery(query).
		WithArgs(namespace, string(ConnectorPack), installedPackStatusActive).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "PackVersion"}).AddRow(rowWithVersion...))
}

func TestResolveConnectorPackByNamespaceTruthTable(t *testing.T) {
	cases := []struct {
		name    string
		row     []driver.Value
		wantRow bool
	}{
		{
			name:    "active connector pack returns row",
			row:     []driver.Value{"installed_pack_noop_connector", "pack-install-token-001", "noop-connector", "connector-pack", "active"},
			wantRow: true,
		},
		{
			name:    "installing connector pack not returned",
			row:     nil, // WHERE Status=active filters it out
			wantRow: false,
		},
		{
			name:    "failed connector pack not returned",
			row:     nil,
			wantRow: false,
		},
		{
			name:    "removing connector pack not returned",
			row:     nil,
			wantRow: false,
		},
		{
			name:    "soft-deleted connector pack not returned",
			row:     nil, // WHERE COALESCE(IsDeleted,0)=0 filters it out
			wantRow: false,
		},
		{
			name:    "missing row not returned",
			row:     nil,
			wantRow: false,
		},
		{
			name:    "wrong-category app-pack with same name not returned",
			row:     nil, // WHERE Category=connector-pack filters it out
			wantRow: false,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			defer db.Close()

			expectConnectorPackResolveLookup(mock, "noop-connector", tc.row)

			got, ok, err := ResolveConnectorPackByNamespace(context.Background(), db, "noop-connector")
			if err != nil {
				t.Fatalf("ResolveConnectorPackByNamespace: %v", err)
			}
			if ok != tc.wantRow {
				t.Fatalf("ResolveConnectorPackByNamespace ok = %v, want %v", ok, tc.wantRow)
			}
			if tc.wantRow && got == nil {
				t.Fatal("ResolveConnectorPackByNamespace returned nil row with ok=true")
			}
			if !tc.wantRow && got != nil {
				t.Fatalf("ResolveConnectorPackByNamespace returned non-nil row with ok=false: %+v", got)
			}
			if tc.wantRow && got != nil {
				if got.ID != "installed_pack_noop_connector" {
					t.Fatalf("row.ID = %q, want %q", got.ID, "installed_pack_noop_connector")
				}
				if got.Namespace != "noop-connector" {
					t.Fatalf("row.Namespace = %q, want %q", got.Namespace, "noop-connector")
				}
				if got.Category != string(ConnectorPack) {
					t.Fatalf("row.Category = %q, want %q", got.Category, string(ConnectorPack))
				}
				if got.Status != installedPackStatusActive {
					t.Fatalf("row.Status = %q, want %q", got.Status, installedPackStatusActive)
				}
				if got.InstallID != "pack-install-token-001" {
					t.Fatalf("row.InstallID = %q, want %q", got.InstallID, "pack-install-token-001")
				}
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

// expectCapabilityJoinLookup sets up the sqlmock expectation for the single
// atomic JOIN that IsCapabilityDeclaredByConnectorPack now runs (IANTHEREAL
// gate-merge round 3 BLOCK fix). joinHit=true means the JOIN returns a row;
// false means no row (any condition unsatisfied).
func expectCapabilityJoinLookup(mock sqlmock.Sqlmock, namespace, kind string, joinHit bool) {
	query := `SELECT 1\s+FROM _System_ConnectorCapabilityDeclaration d\s+JOIN _System_InstalledPack p`
	expect := mock.ExpectQuery(query).
		WithArgs(namespace, string(ConnectorPack), installedPackStatusActive, namespace, kind)
	if joinHit {
		expect.WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(1))
	} else {
		expect.WillReturnRows(sqlmock.NewRows([]string{"1"}))
	}
}

func TestIsCapabilityDeclaredByConnectorPack_TruthTable(t *testing.T) {
	const ns = "noop-connector"
	cases := []struct {
		name         string
		namespace    string
		kind         string
		joinHit      bool // true means: the atomic JOIN returns a row
		wantDeclared bool
		// skipDBExpectation: empty namespace/kind short-circuits before DB.
		skipDBExpectation bool
	}{
		// JOIN hit cases.
		{"active+declared+hash_consistent", ns, "signature_verifier", true, true, false},
		// JOIN miss cases — single SQL filter rejects on any failing condition.
		// Each case below describes the production scenario the row in the DB
		// would fall into; sqlmock just returns "no row" because the WHERE
		// predicate would not match.
		{"pack_inactive", ns, "signature_verifier", false, false, false},
		{"pack_soft_deleted", ns, "signature_verifier", false, false, false},
		{"wrong_category", ns, "signature_verifier", false, false, false},
		{"blank_pack_install_id", ns, "signature_verifier", false, false, false},
		{"hash_drift", ns, "signature_verifier", false, false, false},
		{"applied_hash_missing", ns, "signature_verifier", false, false, false},
		{"declaration_soft_deleted", ns, "signature_verifier", false, false, false},
		{"declaration_missing", ns, "envelope_parser", false, false, false},
		// Concurrency: a lifecycle transition can change the pack status between
		// separate liveness and declaration reads. The single JOIN collapses that
		// race to "no row" because Status='active' is part of the same snapshot.
		{"race_status_changed_to_removing_before_declaration_soft_delete", ns, "signature_verifier", false, false, false},
		// Empty input short-circuits without DB.
		{"empty_namespace", "", "signature_verifier", false, false, true},
		{"empty_kind", ns, "", false, false, true},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			defer db.Close()

			if !tc.skipDBExpectation {
				expectCapabilityJoinLookup(mock, tc.namespace, tc.kind, tc.joinHit)
			}

			got, err := IsCapabilityDeclaredByConnectorPack(context.Background(), db, tc.namespace, tc.namespace, tc.kind)
			if err != nil {
				t.Fatalf("IsCapabilityDeclaredByConnectorPack: %v", err)
			}
			if got != tc.wantDeclared {
				t.Fatalf("= %v, want %v", got, tc.wantDeclared)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

// expectConnectorPackMidApplyLookup sets up the sqlmock expectation for
// IsConnectorPackMidApply, which SELECTs Status, COALESCE(PackInstallId,”),
// COALESCE(AppliedPayloadHash,”), COALESCE(DesiredPayloadHash,”).
// row is [status, installID, applied, desired] or nil for ErrNoRows.
func expectConnectorPackMidApplyLookup(mock sqlmock.Sqlmock, namespace string, row []driver.Value) {
	query := `SELECT Status,\s+COALESCE\(PackInstallId, ''\),\s+COALESCE\(AppliedPayloadHash, ''\),\s+COALESCE\(DesiredPayloadHash, ''\)\s+FROM _System_InstalledPack`
	exp := mock.ExpectQuery(query).
		WithArgs(namespace, string(ConnectorPack))
	if row == nil {
		exp.WillReturnRows(sqlmock.NewRows([]string{"Status", "PackInstallId", "AppliedPayloadHash", "DesiredPayloadHash"}))
	} else {
		exp.WillReturnRows(sqlmock.NewRows([]string{"Status", "PackInstallId", "AppliedPayloadHash", "DesiredPayloadHash"}).AddRow(row...))
	}
}

func TestIsConnectorPackMidApply_TruthTable(t *testing.T) {
	const stableHash = "nexus-pack-sha256-v1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	const otherHash = "nexus-pack-sha256-v1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"

	cases := []struct {
		name    string
		row     []driver.Value // nil → ErrNoRows (genuinely absent)
		wantMid bool
	}{
		{
			name:    "genuinely absent → false (terminal 404 territory)",
			row:     nil,
			wantMid: false,
		},
		{
			name:    "steady active+converged → false",
			row:     []driver.Value{"active", "pack-install-token-001", stableHash, stableHash},
			wantMid: false,
		},
		{
			name:    "installing status → true",
			row:     []driver.Value{"installing", "pack-install-token-001", stableHash, stableHash},
			wantMid: true,
		},
		{
			name:    "removing status → true",
			row:     []driver.Value{"removing", "pack-install-token-001", stableHash, stableHash},
			wantMid: true,
		},
		{
			name:    "blank PackInstallId → true",
			row:     []driver.Value{"active", "", stableHash, stableHash},
			wantMid: true,
		},
		{
			name:    "blank AppliedPayloadHash → true",
			row:     []driver.Value{"active", "pack-install-token-001", "", stableHash},
			wantMid: true,
		},
		{
			name:    "blank DesiredPayloadHash → true",
			row:     []driver.Value{"active", "pack-install-token-001", stableHash, ""},
			wantMid: true,
		},
		{
			name:    "hash drift (applied != desired) → true",
			row:     []driver.Value{"active", "pack-install-token-001", stableHash, otherHash},
			wantMid: true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			defer db.Close()

			expectConnectorPackMidApplyLookup(mock, "noop-connector", tc.row)

			got, err := IsConnectorPackMidApply(context.Background(), db, "noop-connector")
			if err != nil {
				t.Fatalf("IsConnectorPackMidApply: %v", err)
			}
			if got != tc.wantMid {
				t.Fatalf("IsConnectorPackMidApply() = %v, want %v", got, tc.wantMid)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

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

	// No ExpectQuery — empty namespace must return early without hitting DB.
	got, ok, err := ResolveConnectorPackByNamespace(context.Background(), db, "")
	if err != nil {
		t.Fatalf("ResolveConnectorPackByNamespace(\"\") error = %v, want nil", err)
	}
	if ok {
		t.Fatal("ResolveConnectorPackByNamespace(\"\") ok = true, want false")
	}
	if got != nil {
		t.Fatalf("ResolveConnectorPackByNamespace(\"\") returned non-nil row: %+v", got)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations (want no queries): %v", err)
	}
}

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

	rows := sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "DrainOnly"}).
		AddRow("active-id", "install-active", "active-pack", "connector-pack", "active", "hash-active", 0).
		AddRow("drain-id", "install-drain", "drain-pack", "connector-pack", "removing", "hash-drain", 1)

	mock.ExpectQuery(`(?s)d\.Kind = 'signature_verifier'.*d\.Kind = 'envelope_parser'`).
		WithArgs("active", "connector-pack", "active", "active", "active", "active").
		WillReturnRows(rows)

	got, err := ListConnectorPacksForInboundWorkers(context.Background(), db)
	if err != nil {
		t.Fatalf("ListConnectorPacksForInboundWorkers: %v", err)
	}
	if len(got) != 2 {
		t.Fatalf("len(got) = %d, want 2", len(got))
	}
	if got[0].Namespace != "active-pack" || got[0].DrainOnly {
		t.Fatalf("got[0] = %+v, want active non-drain row", got[0])
	}
	if got[1].Namespace != "drain-pack" || !got[1].DrainOnly {
		t.Fatalf("got[1] = %+v, want drain-only row", got[1])
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	rows := sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "DrainOnly"}).
		AddRow("drain-id", "install-drain", "drain-pack", "connector-pack", "removing", "hash-drain", 1)

	mock.ExpectQuery(`(?s)AppliedPayloadHash = DesiredPayloadHash.*d\.Kind = 'signature_verifier'.*d\.Kind = 'envelope_parser'`).
		WithArgs("active", "connector-pack", "active", "active", "active", "active").
		WillReturnRows(rows)

	got, err := ListConnectorPacksForInboundWorkers(context.Background(), db)
	if err != nil {
		t.Fatalf("ListConnectorPacksForInboundWorkers: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("len(got) = %d, want only drain-only row", len(got))
	}
	if got[0].Namespace != "drain-pack" || !got[0].DrainOnly {
		t.Fatalf("got[0] = %+v, want drain-only row", got[0])
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	mock.ExpectQuery(`(?s)d\.Kind = 'signature_verifier'.*d\.Kind = 'envelope_parser'.*active_pack\.Status = \?`).
		WithArgs("active", "connector-pack", "active", "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "DrainOnly"}))

	got, err := ListConnectorPacksForInboundWorkers(context.Background(), db)
	if err != nil {
		t.Fatalf("ListConnectorPacksForInboundWorkers: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("len(got) = %d, want 0 when only drain row has active sibling rebirth", len(got))
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	mock.ExpectQuery(`(?s)d\.Kind = 'signature_verifier'.*d\.Kind = 'envelope_parser'.*other_historical_pack\.Status <> \?`).
		WithArgs("active", "connector-pack", "active", "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "DrainOnly"}))

	got, err := ListConnectorPacksForInboundWorkers(context.Background(), db)
	if err != nil {
		t.Fatalf("ListConnectorPacksForInboundWorkers: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("len(got) = %d, want 0 when drain row has another surviving historical incarnation", len(got))
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	mock.ExpectQuery(`(?s)d\.Kind = 'signature_verifier'.*d\.Kind = 'envelope_parser'.*e\.Status IN \('pending', 'failed', 'processing'\)`).
		WithArgs("active", "connector-pack", "active", "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "DrainOnly"}).
			AddRow("drain-id", "install-drain", "drain-pack", "connector-pack", "removing", "hash-drain", 1))

	got, err := ListConnectorPacksForInboundWorkers(context.Background(), db)
	if err != nil {
		t.Fatalf("ListConnectorPacksForInboundWorkers: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("len(got) = %d, want fresh-processing drain row", len(got))
	}
	if got[0].Namespace != "drain-pack" || !got[0].DrainOnly {
		t.Fatalf("got[0] = %+v, want drain-only fresh-processing row", got[0])
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	mock.ExpectQuery(`(?s)d\.Kind = 'signature_verifier'.*d\.Kind = 'envelope_parser'.*e\.Status IN \('pending', 'failed', 'processing'\)`).
		WithArgs("active", "connector-pack", "active", "active", "active", "active").
		WillReturnRows(sqlmock.NewRows([]string{"Id", "PackInstallId", "Name", "Category", "Status", "AppliedPayloadHash", "DrainOnly"}).
			AddRow("drain-id", "install-drain", "drain-pack", "connector-pack", "removing", "hash-drain", 1))

	got, err := ListConnectorPacksForInboundWorkers(context.Background(), db)
	if err != nil {
		t.Fatalf("ListConnectorPacksForInboundWorkers: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("len(got) = %d, want pending/failed drain row", len(got))
	}
	if got[0].Namespace != "drain-pack" || !got[0].DrainOnly {
		t.Fatalf("got[0] = %+v, want drain-only pending/failed row", got[0])
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestResolveInstalledPackDeclarationSourceByInstallID_IgnoresStoredRevisionForLocalPathSource(t *testing.T) {
	sourceDir := writeRuntimeTestPackSource(t, "jira-like-sample")
	initGitTestRepo(t, sourceDir)
	commitOne := gitTestCommit(t, sourceDir, "initial")

	packAtCommitOne, err := ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevision(
		"jira-like-sample",
		AppPack,
		SupportedManifestFormatVersion,
		PackSourceKindLocalPath,
		sourceDir,
		commitOne,
	)
	if err != nil {
		t.Fatalf("ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevision(commitOne): %v", err)
	}
	hashOne, err := PackPayloadHash(packAtCommitOne)
	if err != nil {
		t.Fatalf("PackPayloadHash(commitOne): %v", err)
	}

	mutateRuntimeTestPackSource(t, sourceDir)
	commitTwo := gitTestCommit(t, sourceDir, "mutated")
	currentPack, err := ResolveInstalledSourceBackedPackForInstalledRow(
		"jira-like-sample",
		AppPack,
		SupportedManifestFormatVersion,
		PackSourceKindLocalPath,
		sourceDir,
	)
	if err != nil {
		t.Fatalf("ResolveInstalledSourceBackedPackForInstalledRow(current): %v", err)
	}
	currentHash, err := PackPayloadHash(currentPack)
	if err != nil {
		t.Fatalf("PackPayloadHash(current): %v", err)
	}
	if currentHash == hashOne {
		t.Fatalf("currentHash = %q, want mutation to change payload hash", currentHash)
	}

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

	mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
		WithArgs("jira-like-sample", "install-old").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
			AddRow("jira-like-sample", "app-pack", "removing", 0, SupportedManifestFormatVersion, PackSourceKindLocalPath, sourceDir, commitOne, currentHash, currentHash))

	gotPack, status, ok, err := ResolveInstalledPackDeclarationSourceByInstallID(context.Background(), db, "jira-like-sample", "install-old")
	if err != nil {
		t.Fatalf("ResolveInstalledPackDeclarationSourceByInstallID: %v", err)
	}
	if !ok {
		t.Fatal("ok = false, want true")
	}
	if status != "removing" {
		t.Fatalf("status = %q, want removing", status)
	}
	gotHash, err := PackPayloadHash(gotPack)
	if err != nil {
		t.Fatalf("PackPayloadHash(gotPack): %v", err)
	}
	if gotHash != currentHash {
		t.Fatalf("gotHash = %q, want current local_path hash %q", gotHash, currentHash)
	}
	if meta := PackHashMetadataForPack(gotPack); strings.TrimSpace(meta.SourceRevision) != commitTwo {
		t.Fatalf("SourceRevision = %q, want current revision %q", meta.SourceRevision, commitTwo)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestResolveInstalledPackDeclarationSourceByInstallID_UsesCurrentSourceForActiveRows(t *testing.T) {
	sourceDir := writeRuntimeTestPackSource(t, "jira-like-sample")

	activePack, err := ResolveInstalledSourceBackedPackForInstalledRow(
		"jira-like-sample",
		AppPack,
		SupportedManifestFormatVersion,
		PackSourceKindLocalPath,
		sourceDir,
	)
	if err != nil {
		t.Fatalf("ResolveInstalledSourceBackedPackForInstalledRow(active): %v", err)
	}
	activeHash, err := PackPayloadHash(activePack)
	if err != nil {
		t.Fatalf("PackPayloadHash(active): %v", err)
	}

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

	mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
		WithArgs("jira-like-sample", "install-active").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
			AddRow("jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, PackSourceKindLocalPath, sourceDir, "missing-history", activeHash, activeHash))

	gotPack, status, ok, err := ResolveInstalledPackDeclarationSourceByInstallID(context.Background(), db, "jira-like-sample", "install-active")
	if err != nil {
		t.Fatalf("ResolveInstalledPackDeclarationSourceByInstallID(active): %v", err)
	}
	if !ok {
		t.Fatal("ok = false, want true")
	}
	if status != "active" {
		t.Fatalf("status = %q, want active", status)
	}
	gotHash, err := PackPayloadHash(gotPack)
	if err != nil {
		t.Fatalf("PackPayloadHash(gotPack): %v", err)
	}
	if gotHash != activeHash {
		t.Fatalf("gotHash = %q, want current hash %q", gotHash, activeHash)
	}
	if meta := PackHashMetadataForPack(gotPack); strings.TrimSpace(meta.SourceRevision) == "missing-history" {
		t.Fatalf("SourceRevision = %q, want current-source resolution rather than stored missing revision", meta.SourceRevision)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestResolveInstalledPackDeclarationSourceByInstallID_HistoricalAllowsLegacyBatchSpecMissingIDResponseField(t *testing.T) {
	const packName = "legacy-batch-connector"
	sourceDir := writeConnectorPackWithLegacyBatchProfileMissingIDResponseField(t, packName)
	compatPack, err := resolveInstalledSourceBackedPackWithStoredIdentity(
		packName,
		ConnectorPack,
		PackSourceKindLocalPath,
		sourceDir,
		SupportedManifestFormatVersion,
		"",
		withLegacyConnectorCalloutBatchIDResponseFieldCompat(),
	)
	if err != nil {
		t.Fatalf("resolve compat source-backed pack: %v", err)
	}
	compatHash, err := PackPayloadHash(compatPack)
	if err != nil {
		t.Fatalf("PackPayloadHash(compatPack): %v", err)
	}

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

	mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
		WithArgs(packName, "install-removing").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
			AddRow(packName, "connector-pack", "removing", 0, SupportedManifestFormatVersion, PackSourceKindLocalPath, sourceDir, "", compatHash, compatHash))

	gotPack, status, ok, err := ResolveInstalledPackDeclarationSourceByInstallID(context.Background(), db, packName, "install-removing")
	if err != nil {
		t.Fatalf("ResolveInstalledPackDeclarationSourceByInstallID: %v", err)
	}
	if !ok {
		t.Fatal("ok = false, want true for historical legacy batch profile")
	}
	if status != "removing" {
		t.Fatalf("status = %q, want removing", status)
	}
	payloads, err := gotPack.ConnectorCalloutProfilePayloads()
	if err != nil {
		t.Fatalf("ConnectorCalloutProfilePayloads: %v", err)
	}
	if len(payloads) != 1 {
		t.Fatalf("len(payloads) = %d, want 1", len(payloads))
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestResolveInstalledPackDeclarationSourceByInstallID_ActiveCurrentSourceRejectsLegacyBatchSpecMissingIDResponseField(t *testing.T) {
	const packName = "legacy-batch-connector"
	sourceDir := writeConnectorPackWithLegacyBatchProfileMissingIDResponseField(t, packName)
	compatPack, err := resolveInstalledSourceBackedPackWithStoredIdentity(
		packName,
		ConnectorPack,
		PackSourceKindLocalPath,
		sourceDir,
		SupportedManifestFormatVersion,
		"",
		withLegacyConnectorCalloutBatchIDResponseFieldCompat(),
	)
	if err != nil {
		t.Fatalf("resolve compat source-backed pack: %v", err)
	}
	compatHash, err := PackPayloadHash(compatPack)
	if err != nil {
		t.Fatalf("PackPayloadHash(compatPack): %v", err)
	}

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

	mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
		WithArgs(packName, "install-active").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
			AddRow(packName, "connector-pack", "active", 0, SupportedManifestFormatVersion, PackSourceKindLocalPath, sourceDir, "", compatHash, compatHash))

	_, status, ok, err := ResolveInstalledPackDeclarationSourceByInstallID(context.Background(), db, packName, "install-active")
	if err != nil {
		t.Fatalf("ResolveInstalledPackDeclarationSourceByInstallID: %v", err)
	}
	if ok {
		t.Fatal("ok = true, want active current-source resolution to reject legacy missing idResponseField")
	}
	if status != "active" {
		t.Fatalf("status = %q, want active", status)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestIsFieldDeclaredByCurrentSourceForExplicitRemovalRejectsLegacyBatchSpecMissingIDResponseField(t *testing.T) {
	const packName = "legacy-batch-connector"
	sourceDir := writeConnectorPackWithLegacyBatchProfileMissingIDResponseField(t, packName)
	compatPack, err := resolveInstalledSourceBackedPackWithStoredIdentity(
		packName,
		ConnectorPack,
		PackSourceKindLocalPath,
		sourceDir,
		SupportedManifestFormatVersion,
		"",
		withLegacyConnectorCalloutBatchIDResponseFieldCompat(),
	)
	if err != nil {
		t.Fatalf("resolve compat source-backed pack: %v", err)
	}
	compatHash, err := PackPayloadHash(compatPack)
	if err != nil {
		t.Fatalf("PackPayloadHash(compatPack): %v", err)
	}

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

	mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
		WithArgs(packName).
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
			AddRow(packName, "connector-pack", "active", 0, SupportedManifestFormatVersion, PackSourceKindLocalPath, sourceDir, "", compatHash, compatHash))

	_, err = IsFieldDeclaredByCurrentSourceForExplicitRemoval(context.Background(), db, packName, "Issue", "Subject")
	if err == nil {
		t.Fatal("IsFieldDeclaredByCurrentSourceForExplicitRemoval accepted active source with legacy missing idResponseField")
	}
	if !strings.Contains(err.Error(), "batchSpec.idResponseField is required") {
		t.Fatalf("error = %q, want batchSpec.idResponseField diagnostic", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestIsFieldDeclaredByCurrentSourceForExplicitRemovalCarriesComponentKindRegistrationSet(t *testing.T) {
	root := writePackFixture(t, "jira-like-sample", validPackManifestV2(`[
    {
      "kind": "approval_process",
      "businessKey": "Quote/Quote_Approval",
      "manageability": "locked",
      "path": "approvalProcesses/Quote_Approval.approvalProcess-meta.xml"
    }
  ]`, `"dependencies": [],`))
	writeFile(t, root, "approvalProcesses/Quote_Approval.approvalProcess-meta.xml", "<ApprovalProcess/>")
	registration := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	registration.BuildHandler = func(buildContext cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
		return buildContext.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{"rows":[]}`)})
	}
	registrations, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{registration})
	if err != nil {
		t.Fatal(err)
	}
	const packName = "jira-like-sample"
	payloadHash := PackPayloadHashV1Prefix + strings.Repeat("a", 64)

	for _, test := range []struct {
		name    string
		resolve func(context.Context, *sql.DB) (bool, error)
		wantErr string
	}{
		{
			name: "registered source resolves",
			resolve: func(ctx context.Context, db *sql.DB) (bool, error) {
				return IsFieldDeclaredByCurrentSourceForExplicitRemovalWithComponentKindRegistrationSet(
					ctx, db, packName, "Issue", "LegacyField__c", registrations,
				)
			},
		},
		{
			name: "legacy wrapper remains empty",
			resolve: func(ctx context.Context, db *sql.DB) (bool, error) {
				return IsFieldDeclaredByCurrentSourceForExplicitRemoval(ctx, db, packName, "Issue", "LegacyField__c")
			},
			wantErr: "approval_process",
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatal(err)
			}
			defer db.Close()

			mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
				WithArgs(packName).
				WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
					AddRow(packName, string(AppPack), installedPackStatusActive, 0, ManifestFormatVersion2, PackSourceKindLocalPath, root, "", payloadHash, payloadHash))

			declared, err := test.resolve(context.Background(), db)
			if test.wantErr != "" {
				if err == nil || !strings.Contains(err.Error(), test.wantErr) {
					t.Fatalf("error = %v, want %q", err, test.wantErr)
				}
			} else if err != nil {
				t.Fatalf("registered explicit-removal source read: %v", err)
			} else if declared {
				t.Fatal("declared = true, want stale field absent from registered source")
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatal(err)
			}
		})
	}
}

func TestIsFieldDeclaredByCurrentSourceForExplicitRemovalIgnoresStoredSourceRevision(t *testing.T) {
	sourceDir := writeRuntimeTestPackSource(t, "jira-like-sample")
	initGitTestRepo(t, sourceDir)
	commitOne := gitTestCommit(t, sourceDir, "initial")

	objectPath := filepath.Join(sourceDir, "objects", "issue.json")
	body, err := os.ReadFile(objectPath)
	if err != nil {
		t.Fatalf("read object spec: %v", err)
	}
	currentWithoutSubject := strings.Replace(string(body), `      {
        "apiName": "Subject",
        "label": "Subject",
        "type": "string",
        "required": true,
        "isNameField": true
      }`, "", 1)
	if currentWithoutSubject == string(body) {
		t.Fatal("expected mutation to remove Subject field")
	}
	if err := os.WriteFile(objectPath, []byte(currentWithoutSubject), 0o644); err != nil {
		t.Fatalf("write current object spec: %v", err)
	}

	activePack, err := ResolveInstalledSourceBackedPackForInstalledRow(
		"jira-like-sample",
		AppPack,
		SupportedManifestFormatVersion,
		PackSourceKindLocalPath,
		sourceDir,
	)
	if err != nil {
		t.Fatalf("ResolveInstalledSourceBackedPackForInstalledRow(active): %v", err)
	}
	activeHash, err := PackPayloadHash(activePack)
	if err != nil {
		t.Fatalf("PackPayloadHash(active): %v", err)
	}

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

	mock.ExpectQuery("SELECT Name, Category, Status, COALESCE\\(IsDeleted, 0\\), ManifestFormatVersion, SourceKind, SourceURI, SourceRevision, AppliedPayloadHash, DesiredPayloadHash FROM _System_InstalledPack").
		WithArgs("jira-like-sample").
		WillReturnRows(sqlmock.NewRows([]string{"Name", "Category", "Status", "IsDeleted", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision", "AppliedPayloadHash", "DesiredPayloadHash"}).
			AddRow("jira-like-sample", "app-pack", "active", 0, SupportedManifestFormatVersion, PackSourceKindLocalPath, sourceDir, commitOne, activeHash, activeHash))

	declared, err := IsFieldDeclaredByCurrentSourceForExplicitRemoval(context.Background(), db, "jira-like-sample", "Issue", "Subject")
	if err != nil {
		t.Fatalf("IsFieldDeclaredByCurrentSourceForExplicitRemoval: %v", err)
	}
	if declared {
		t.Fatal("declared = true, want current source without Subject to ignore stale stored SourceRevision")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func writeConnectorPackWithLegacyBatchProfileMissingIDResponseField(t *testing.T, packName string) string {
	t.Helper()
	profile := `{
  "kind":"connector_callout_profile",
  "businessKey":"` + packName + `/default",
  "manageability":"locked",
  "payload":{
    "profileName":"default",
    "credentialRef":"test-nc",
    "endpointClass":"crm.read",
    "retryPolicy":{"maxAttempts":3,"baseDelay":100000000,"multiplier":1.5,"maxDelay":5000000000},
    "batchSpec":{
      "method":"POST",
      "relativePath":"/batch",
      "maxBatchSize":100,
      "idsRequestField":"ids",
      "credentialRef":"test-nc",
      "endpointClass":"crm.batch"
    },
    "objectPathMap":{"contact":"/contacts"}
  }
}`
	return writePackFixture(t, packName, validConnectorPackManifest(packName, "["+profile+"]"))
}

func initGitTestRepo(t *testing.T, dir string) {
	t.Helper()
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not available")
	}
	runGitTestCommand(t, dir, "init")
	runGitTestCommand(t, dir, "config", "user.email", "codex@example.com")
	runGitTestCommand(t, dir, "config", "user.name", "Codex")
}

func gitTestCommit(t *testing.T, dir, message string) string {
	t.Helper()
	runGitTestCommand(t, dir, "add", ".")
	runGitTestCommand(t, dir, "commit", "-m", message)
	return strings.TrimSpace(runGitTestCommand(t, dir, "rev-parse", "HEAD"))
}

func mutateRuntimeTestPackSource(t *testing.T, root string) {
	t.Helper()
	objectPath := filepath.Join(root, "objects", "issue.json")
	body, err := os.ReadFile(objectPath)
	if err != nil {
		t.Fatalf("read object spec: %v", err)
	}
	mutated := strings.Replace(string(body), `"Issue work item"`, `"Issue work item v2"`, 1)
	if mutated == string(body) {
		t.Fatal("expected mutation to change object spec payload")
	}
	if err := os.WriteFile(objectPath, []byte(mutated), 0o644); err != nil {
		t.Fatalf("write mutated object spec: %v", err)
	}
}

func runGitTestCommand(t *testing.T, dir string, args ...string) string {
	t.Helper()
	cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %v: %v\n%s", args, err, string(out))
	}
	return string(out)
}
