package setupcmd

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	"github.com/nexuscrm/automation"
	"github.com/nexuscrm/automation/standardaction"
	"github.com/nexuscrm/backend/internal/interfaces/rest/downloads"
	"github.com/nexuscrm/backend/pkg/serverruntime"
	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/kernel/pkg/flowactions"
	"github.com/nexuscrm/metadata/programchecker"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	controlplane "github.com/pingcap-inc/nexuscrm-controlplane"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func TestRunApplyOfflineCandidateCommandSubmitsCompleteSourceSetOnce(t *testing.T) {
	var calls int
	var gotRequest controlplane.OfflineCandidateRequest
	var gotOptions controlplane.InstallPackOptions
	var gotRunnerOptions controlplane.RunnerOptions
	receipt := controlplane.OfflineCandidateReceipt{
		OperationID:          "deploy-42",
		UpgradeMode:          controlplane.UpgradeModeOfflineRequired,
		PreviousStateDigest:  "sha256:previous",
		TargetStateDigest:    "sha256:target",
		ActivatedStateDigest: "sha256:target",
		Verified:             true,
		Activated:            true,
	}
	applier := func(
		_ context.Context,
		_ *database.TiDBConnection,
		request controlplane.OfflineCandidateRequest,
		options controlplane.InstallPackOptions,
		runnerOptions controlplane.RunnerOptions,
	) (controlplane.OfflineCandidateReceipt, error) {
		calls++
		gotRequest = request
		gotOptions = options
		gotRunnerOptions = runnerOptions
		return receipt, nil
	}

	var stdout, stderr bytes.Buffer
	registrationSet := packs.ComponentKindRegistrationSet{}
	code := runApplyOfflineCandidateCommand(
		context.Background(),
		nil,
		serverruntime.ProductHooks{},
		registrationSet,
		[]string{
			"--operation-id", " deploy-42 ",
			"--source", "release_repo:urn:nexuscrm:release-pack:example:companion",
			"--release-source-receipt", `{"uri":"urn:nexuscrm:release-pack:example:companion","pack_root":"/candidate/companion","revision":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`,
			"--source", "official_repo:connector",
			"--source", "local_path:/candidate/base",
		},
		&stdout,
		&stderr,
		applier,
	)
	if code != 0 {
		t.Fatalf("runApplyOfflineCandidateCommand exit = %d, stderr=%s", code, stderr.String())
	}
	if calls != 1 {
		t.Fatalf("candidate apply calls = %d, want 1", calls)
	}
	if gotRequest.OperationID != "deploy-42" || gotRequest.UpgradeMode != controlplane.UpgradeModeOfflineRequired {
		t.Fatalf("candidate request identity = %#v", gotRequest)
	}
	wantSources := []string{
		"release_repo:urn:nexuscrm:release-pack:example:companion",
		"official_repo:connector",
		"local_path:/candidate/base",
	}
	if !reflect.DeepEqual(gotRequest.PackSources, wantSources) {
		t.Fatalf("candidate sources = %#v, want %#v", gotRequest.PackSources, wantSources)
	}
	wantReleaseSources := []controlplane.OfflineCandidateReleaseRepoSource{{
		URI: "urn:nexuscrm:release-pack:example:companion", PackRoot: "/candidate/companion", Revision: strings.Repeat("a", 40),
	}}
	if !reflect.DeepEqual(gotRequest.ReleaseRepoSources, wantReleaseSources) {
		t.Fatalf("candidate release source receipts = %#v, want %#v", gotRequest.ReleaseRepoSources, wantReleaseSources)
	}
	if gotOptions.CandidateFinalVerify == nil || gotOptions.ApplyAllFinalVerify == nil {
		t.Fatal("candidate command did not compose candidate and final verification")
	}
	if gotOptions.SourceRevisionAdvance != nil || len(gotOptions.ApplyAllSourceRevisionAdvances) != 0 {
		t.Fatalf("candidate command leaked caller transition classification: %#v", gotOptions)
	}
	if !reflect.DeepEqual(gotRunnerOptions.ComponentKindRegistrationSet, registrationSet) {
		t.Fatal("candidate command did not carry the product component registration set")
	}
	var output map[string]any
	if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
		t.Fatalf("decode receipt JSON %q: %v", stdout.String(), err)
	}
	if output["operation_id"] != "deploy-42" || output["verified"] != true || output["activated"] != true {
		t.Fatalf("receipt output = %#v", output)
	}
}

func TestRunApplyOfflineCandidateCommandRejectsIncompleteRequestBeforeApply(t *testing.T) {
	calls := 0
	applier := func(
		context.Context,
		*database.TiDBConnection,
		controlplane.OfflineCandidateRequest,
		controlplane.InstallPackOptions,
		controlplane.RunnerOptions,
	) (controlplane.OfflineCandidateReceipt, error) {
		calls++
		return controlplane.OfflineCandidateReceipt{}, nil
	}

	for _, args := range [][]string{
		{"--source", "local_path:/candidate/base"},
		{"--operation-id", "deploy-42"},
		{"--operation-id", "deploy-42", "--source", "local_path:/candidate/base", "unexpected"},
		{"--operation-id", "deploy-42", "--source", "release_repo:urn:nexuscrm:release-pack:example:base", "--release-source-receipt", `{"uri":"urn:nexuscrm:release-pack:example:base","pack_root":"/candidate/base","revision":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} trailing`},
		{"--operation-id", "deploy-42", "--source", "release_repo:urn:nexuscrm:release-pack:example:base", "--release-source-receipt", `{"uri":"urn:nexuscrm:release-pack:example:base","pack_root":"/candidate/base","revision":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","unexpected":true}`},
	} {
		var stdout, stderr bytes.Buffer
		if code := runApplyOfflineCandidateCommand(
			context.Background(), nil, serverruntime.ProductHooks{}, packs.ComponentKindRegistrationSet{},
			args, &stdout, &stderr, applier,
		); code != 2 {
			t.Fatalf("args %#v exit = %d, want 2; stderr=%s", args, code, stderr.String())
		}
	}
	if calls != 0 {
		t.Fatalf("candidate apply calls = %d, want 0", calls)
	}
}

func TestParseSourceSetArgsRequiresCanonicalLowercaseOfficialSourceName(t *testing.T) {
	revision := strings.Repeat("a", 40)
	var stderr bytes.Buffer
	args, err := parseSourceSetArgs([]string{
		"--operation-id", "deploy-42",
		"--source", "official_repo:salesforcecrm",
		"--official-source-revision", `{"name":" salesforcecrm ","revision":"` + revision + `"}`,
	}, &stderr)
	if err != nil {
		t.Fatalf("parse canonical source set: %v", err)
	}
	wantRevisions := map[string]string{"salesforcecrm": revision}
	if !reflect.DeepEqual(args.officialRepoSourceRevisions, wantRevisions) {
		t.Fatalf("official source revisions = %#v, want %#v", args.officialRepoSourceRevisions, wantRevisions)
	}

	_, err = parseSourceSetArgs([]string{
		"--operation-id", "deploy-42",
		"--source", "official_repo:SalesforceCRM",
		"--official-source-revision", `{"name":"SalesforceCRM","revision":"` + revision + `"}`,
	}, &stderr)
	if err == nil || !strings.Contains(err.Error(), "must use canonical lowercase") {
		t.Fatalf("mixed-case official source name error = %v, want canonical-lowercase rejection", err)
	}
}

func TestRunApplySourceSetCommandUsesOperationOfficialSourceAuthority(t *testing.T) {
	compiledRevision := strings.Repeat("a", 40)
	requestedRevision := strings.Repeat("b", 40)
	productHooks := serverruntime.ProductHooks{Capabilities: serverruntime.ProductCapabilities{
		OfficialRepoSourceRevisions: []serverruntime.OfficialRepoSourceRevision{{
			Name: "salesforce-crm-standard", Revision: compiledRevision,
		}},
	}}
	var calls int
	var gotRequest controlplane.StoppedSourceSetRequest
	applier := func(
		_ context.Context,
		_ *database.TiDBConnection,
		request controlplane.StoppedSourceSetRequest,
		_ controlplane.RunnerOptions,
	) (controlplane.StoppedSourceSetResult, error) {
		calls++
		gotRequest = request
		return controlplane.StoppedSourceSetResult{OperationID: request.OperationID, Applied: true}, nil
	}

	var stdout, stderr bytes.Buffer
	code := runApplySourceSetCommand(
		context.Background(),
		nil,
		productHooks,
		[]string{
			"--operation-id", "deploy-42",
			"--source", "official_repo:hubspot-crm-connector",
			"--official-source-revision", `{"name":"hubspot-crm-connector","revision":"` + requestedRevision + `"}`,
		},
		&stdout,
		&stderr,
		applier,
	)
	if code != 0 {
		t.Fatalf("runApplySourceSetCommand exit = %d, want 0; stderr=%s", code, stderr.String())
	}
	if calls != 1 {
		t.Fatalf("apply calls = %d, want 1", calls)
	}
	wantRevisions := map[string]string{"hubspot-crm-connector": requestedRevision}
	if !reflect.DeepEqual(gotRequest.OfficialRepoSourceRevisions, wantRevisions) {
		t.Fatalf("forwarded official source revisions = %#v, want %#v", gotRequest.OfficialRepoSourceRevisions, wantRevisions)
	}
}

func TestParseSourceSetArgsRejectsDuplicateSourceJSONMembers(t *testing.T) {
	revisionA := strings.Repeat("a", 40)
	revisionB := strings.Repeat("b", 40)
	for _, test := range []struct {
		name      string
		args      []string
		duplicate string
	}{
		{
			name: "release source pack root",
			args: []string{
				"--operation-id", "deploy-42",
				"--source", "release_repo:urn:nexuscrm:release-pack:example:companion",
				"--release-source", `{"uri":"urn:nexuscrm:release-pack:example:companion","pack_root":"/first","pack_root":"/second","revision":"` + revisionA + `"}`,
			},
			duplicate: "pack_root",
		},
		{
			name: "official source revision",
			args: []string{
				"--operation-id", "deploy-42",
				"--source", "official_repo:salesforcecrm",
				"--official-source-revision", `{"name":"salesforcecrm","revision":"` + revisionA + `","revision":"` + revisionB + `"}`,
			},
			duplicate: "revision",
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			var stderr bytes.Buffer
			_, err := parseSourceSetArgs(test.args, &stderr)
			if err == nil || !strings.Contains(err.Error(), `duplicate JSON object key "`+test.duplicate+`"`) {
				t.Fatalf("duplicate %s error = %v", test.duplicate, err)
			}
		})
	}
}

func TestRunPublishArtifactUsesDedicatedRuntimeRootStore(t *testing.T) {
	runtimeRoot := t.TempDir()
	sourcePath := filepath.Join(runtimeRoot, "plugin.dxt")
	if err := os.WriteFile(sourcePath, []byte("ARTIFACT-BYTES"), 0o644); err != nil {
		t.Fatalf("write source artifact: %v", err)
	}

	const key = "release-artifact/connect/latest/plugin.dxt"
	t.Setenv("NEXUSCRM_RUNTIME_ROOT", runtimeRoot)
	t.Setenv(downloads.DownloadArtifactStorageRootEnv, "release-artifacts")
	t.Setenv(downloads.DownloadArtifactsEnv, `{"plugin":{"key":"`+key+`","filename":"plugin.dxt"}}`)
	t.Setenv("FILE_STORAGE_PROVIDER", "filesystem")
	t.Setenv("FILE_STORAGE_ROOT", filepath.Join(runtimeRoot, "customer-files"))

	var stdout, stderr bytes.Buffer
	code := runPublishArtifact([]string{
		"--kind", "plugin",
		"--file", sourcePath,
	}, &stdout, &stderr)
	if code != 0 {
		t.Fatalf("runPublishArtifact exit = %d, stderr=%s", code, stderr.String())
	}

	artifactPath := filepath.Join(runtimeRoot, "release-artifacts", filepath.FromSlash(key))
	data, err := os.ReadFile(artifactPath)
	if err != nil {
		t.Fatalf("read dedicated artifact: %v", err)
	}
	if string(data) != "ARTIFACT-BYTES" {
		t.Fatalf("dedicated artifact bytes = %q", data)
	}
	customerPath := filepath.Join(runtimeRoot, "customer-files", filepath.FromSlash(key))
	if _, err := os.Stat(customerPath); !os.IsNotExist(err) {
		t.Fatalf("customer store artifact stat error = %v, want not-exist", err)
	}
}

func TestRunValidatePackDoesNotRequireDatabase(t *testing.T) {
	root := t.TempDir()
	packRoot := filepath.Join(root, "pack")
	if err := os.MkdirAll(packRoot, 0o755); err != nil {
		t.Fatalf("mkdir pack root: %v", err)
	}
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "db-free-pack",
  "displayName": "DB Free Pack",
  "category": "app-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [],
  "components": [
    {
	      "kind": "object_spec",
	      "businessKey": "object",
	      "manageability": "locked",
	      "payload": [
	        {
	          "apiName": "Issue",
	          "label": "Issue",
	          "tableType": "standard_object",
	          "category": "jira",
	          "sharingModel": "Private",
	          "enableHierarchySharing": false,
	          "description": "Issue work item",
	          "fields": [
	            {"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}
	          ]
	        }
	      ]
	    }
	  ]
	}`
	if err := os.WriteFile(filepath.Join(packRoot, "pack.json"), []byte(manifest), 0o644); err != nil {
		t.Fatalf("write manifest: %v", err)
	}

	t.Setenv("TIDB_HOST", "invalid-db-host-for-validate-pack")
	var stdout, stderr bytes.Buffer
	code := runValidatePackWithComponentKindRegistrationSet([]string{"--source", "local_path:" + packRoot}, &stdout, &stderr, packs.ComponentKindRegistrationSet{})
	if code != 0 {
		t.Fatalf("runValidatePack exit = %d, stderr=%s", code, stderr.String())
	}
	if !strings.Contains(stdout.String(), "pack source valid") {
		t.Fatalf("stdout = %q, want success line", stdout.String())
	}

	code = runValidatePackWithComponentKindRegistrationSet([]string{"--source", "local_path:" + packRoot, "extra"}, &stdout, &stderr, packs.ComponentKindRegistrationSet{})
	if code != 2 {
		t.Fatalf("runValidatePack with extra arg exit = %d, want 2", code)
	}
}

func TestRunValidatePackAcceptsConnectorPushContractWithoutServerRuntime(t *testing.T) {
	root := t.TempDir()
	packRoot := filepath.Join(root, "pack")
	if err := os.MkdirAll(packRoot, 0o755); err != nil {
		t.Fatalf("mkdir pack root: %v", err)
	}
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "connector-host-probe",
  "displayName": "Connector Host Probe",
  "category": "app-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [],
  "components": [
    {
      "kind": "flow_action_call_handler",
      "businessKey": "pushtoconnector",
      "manageability": "locked",
      "payload": {
        "actionType": "PushToConnector",
        "handlerRef": "platform:connector/push-to-connector",
        "atomicPolicy": "reject",
        "previewPolicy": "reject",
        "requiresContext": "none"
      }
    }
  ]
}`
	if err := os.WriteFile(filepath.Join(packRoot, "pack.json"), []byte(manifest), 0o644); err != nil {
		t.Fatalf("write manifest: %v", err)
	}

	t.Setenv("TIDB_HOST", "invalid-db-host-for-validate-pack")
	var stdout, stderr bytes.Buffer
	code := runValidatePackWithComponentKindRegistrationSet([]string{"--source", "local_path:" + packRoot}, &stdout, &stderr, packs.ComponentKindRegistrationSet{})
	if code != 0 {
		t.Fatalf("runValidatePack exit = %d, stderr=%s", code, stderr.String())
	}
	if !strings.Contains(stdout.String(), "pack source valid") {
		t.Fatalf("stdout = %q, want success line", stdout.String())
	}
}

func TestRunValidatePackAcceptsConnectorEnvelopeParserSourceLaneOverride(t *testing.T) {
	root := t.TempDir()
	packRoot := filepath.Join(root, "pack")
	if err := os.MkdirAll(packRoot, 0o755); err != nil {
		t.Fatalf("mkdir pack root: %v", err)
	}
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "source-lane-connector",
  "displayName": "Source Lane Connector",
  "category": "connector-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [],
  "components": [
    {
      "kind": "webhook_secret",
      "businessKey": "source-lane-connector/webhook",
      "manageability": "locked",
      "payload": {"name": "webhook", "algorithm": "hmac-sha256"}
    },
    {
      "kind": "webhook_secret",
      "businessKey": "source-lane-connector/workflow-action",
      "manageability": "locked",
      "payload": {"name": "workflow-action", "algorithm": "hmac-sha256"}
    },
    {
      "kind": "connector_capability",
      "businessKey": "source-lane-connector:signature_verifier",
      "manageability": "locked",
      "payload": {"namespace": "source-lane-connector", "kind": "signature_verifier"}
    },
    {
      "kind": "connector_inbound_handler_config",
      "businessKey": "source-lane-connector:signature_verifier",
      "manageability": "locked",
      "payload": {
        "namespace": "source-lane-connector",
        "capabilityKind": "signature_verifier",
        "config": {
          "endpointConfigs": {
            "webhook": {
              "scheme": "hmac-sha256-template",
              "signingTemplate": "{Timestamp}.{RawBody}",
              "timestampHeader": "X-Test-Timestamp",
              "signatureHeader": "X-Test-Signature",
              "signatureEncoding": "hex",
              "skewWindow": "5m",
              "secretName": "webhook"
            },
            "workflow-action": {
              "scheme": "hmac-sha256-template",
              "signingTemplate": "{Timestamp}.{RawBody}",
              "timestampHeader": "X-Test-Timestamp",
              "signatureHeader": "X-Test-Signature",
              "signatureEncoding": "hex",
              "skewWindow": "5m",
              "secretName": "workflow-action"
            }
          }
        }
      }
    },
    {
      "kind": "connector_capability",
      "businessKey": "source-lane-connector:envelope_parser",
      "manageability": "locked",
      "payload": {"namespace": "source-lane-connector", "kind": "envelope_parser"}
    },
    {
      "kind": "connector_inbound_handler_config",
      "businessKey": "source-lane-connector:envelope_parser",
      "manageability": "locked",
      "payload": {
        "namespace": "source-lane-connector",
        "capabilityKind": "envelope_parser",
        "config": {
          "endpointConfigs": {
            "webhook": {
              "eventArrayPath": ".",
              "rules": [
                {
                  "eventTypeLiteral": "thing.updated",
                  "namespaceFromCtx": true,
                  "objectKind": "thing",
                  "sourceIdPath": ".objectId",
                  "idempotencyKeyPath": ".eventId",
                  "payloadProjection": [{"name": "objectId", "path": ".objectId"}]
                }
              ]
            },
            "workflow-action": {
              "eventObjectPath": ".",
              "inboundProcessingMode": "payload_only_allowed",
              "rules": [
                {
                  "eventTypeLiteral": "workflow.action.execute",
                  "namespaceFromCtx": true,
                  "objectKind": "workflow-actions",
                  "sourceIdPath": ".object.objectId",
                  "sourceLaneObjectKind": "campaign",
                  "sourceLaneIdPath": ".inputFields.campaign_id",
                  "idempotencyKeyPath": ".callbackId",
                  "payloadProjection": [{"name": "callbackId", "path": ".callbackId"}]
                }
              ]
            }
          }
        }
      }
    }
  ]
}`
	if err := os.WriteFile(filepath.Join(packRoot, "pack.json"), []byte(manifest), 0o644); err != nil {
		t.Fatalf("write manifest: %v", err)
	}

	t.Setenv("TIDB_HOST", "invalid-db-host-for-validate-pack")
	var stdout, stderr bytes.Buffer
	code := runValidatePackWithComponentKindRegistrationSet([]string{"--source", "local_path:" + packRoot}, &stdout, &stderr, packs.ComponentKindRegistrationSet{})
	if code != 0 {
		t.Fatalf("runValidatePack exit = %d, stderr=%s", code, stderr.String())
	}
	if !strings.Contains(stdout.String(), "pack source valid") {
		t.Fatalf("stdout = %q, want success line", stdout.String())
	}
}

func TestParseInstallAndVerifyPackArgs(t *testing.T) {
	var stderr bytes.Buffer
	source, err := parseInstallPackArgs([]string{"--source", "local_path:./packs/jira"}, &stderr)
	if err != nil {
		t.Fatalf("parse install-pack args: %v", err)
	}
	if source != "local_path:./packs/jira" {
		t.Fatalf("source = %q", source)
	}
	if _, err := parseInstallPackArgs(nil, &stderr); err == nil {
		t.Fatal("parse install-pack without source = nil, want error")
	}

	name, err := parseVerifyPackArgs([]string{"jira-like-sample"}, &stderr)
	if err != nil {
		t.Fatalf("parse verify-pack args: %v", err)
	}
	if name != "jira-like-sample" {
		t.Fatalf("name = %q", name)
	}
	if _, err := parseVerifyPackArgs([]string{"--source", "local_path:./packs/jira"}, &stderr); err == nil {
		t.Fatal("parse verify-pack --source = nil, want error")
	}
}

func TestParseApplyArgs(t *testing.T) {
	var stderr bytes.Buffer

	args, err := parseApplyArgs([]string{"--skip-polymorphic-backfill", "--repair-legacy-unique-duplicates", "--defer-formula-view-rebuild"}, &stderr)
	if err != nil {
		t.Fatalf("parse apply args: %v", err)
	}
	if !args.skipPolymorphicBackfill || !args.repairLegacyUniqueDuplicates || !args.deferFormulaViewRebuild {
		t.Fatalf("parsed apply flags = %#v", args)
	}

	defaults, err := parseApplyArgs(nil, &stderr)
	if err != nil {
		t.Fatalf("parse apply defaults: %v", err)
	}
	if defaults.deferFormulaViewRebuild {
		t.Fatal("defer-formula-view-rebuild must default to false (always-rebuild)")
	}

	if _, err := parseApplyArgs([]string{"extra"}, &stderr); err == nil {
		t.Fatal("parse apply with extra args = nil, want error")
	}

	stderr.Reset()
	if _, err := parseApplyArgs([]string{"--help"}, &stderr); !errors.Is(err, flag.ErrHelp) {
		t.Fatalf("parse apply --help error = %v, want flag.ErrHelp", err)
	}
	help := stderr.String()
	if !strings.Contains(help, "capability nexuscrm.setup.apply-all-source-revision-plan.v2") {
		t.Fatalf("apply help missing generation-bound peer convergence capability: %q", help)
	}
	if strings.Contains(help, "capability nexuscrm.setup.apply-all-source-revision-plan.v1") {
		t.Fatalf("apply help still advertises superseded source-revision plan capability: %q", help)
	}
}

func TestActiveInstalledAppPackNamesTrimsBlankNames(t *testing.T) {
	got := activeInstalledAppPackNames([]packs.ActiveInstalledAppPack{
		{Name: " salesforce-crm-standard "},
		{Name: " "},
		{Name: "analytics-extension"},
	})
	want := []string{"salesforce-crm-standard", "analytics-extension"}
	if strings.Join(got, ",") != strings.Join(want, ",") {
		t.Fatalf("activeInstalledAppPackNames() = %#v, want %#v", got, want)
	}
}

func TestSetupActionSignatureReaderRecognizesPlatformActionCallContracts(t *testing.T) {
	restoreRegistrations := automation.ResetDefaultAppFlowActionCallRegistrationsForTest()
	t.Cleanup(restoreRegistrations)
	restoreImplementations := automation.ResetDefaultAppFlowActionCallHandlerImplementationsForTest()
	t.Cleanup(restoreImplementations)

	reader, err := setupActionSignatureReader(context.Background(), nil, nil)
	if err != nil {
		t.Fatalf("setupActionSignatureReader() error = %v", err)
	}
	signature, ok, err := reader.ActionSignature(context.Background(), flowactions.ActionTypePushToConnector)
	if err != nil {
		t.Fatalf("ActionSignature() error = %v", err)
	}
	if !ok || !strings.EqualFold(signature.Name, flowactions.ActionTypePushToConnector) || !signature.SkipParameterValidation {
		t.Fatalf("ActionSignature() = (%#v, %v), want platform contract skip signature", signature, ok)
	}
}

type setupCapabilityCatalog struct {
	rows  []models.SystemPermission
	err   error
	calls int
}

func (c *setupCapabilityCatalog) ListSystemPermissions(context.Context) ([]models.SystemPermission, error) {
	c.calls++
	return c.rows, c.err
}

type setupProgramMetadataReader struct {
	schemas []*models.ObjectMetadata
}

func (r setupProgramMetadataReader) Object(_ context.Context, apiName string) (*models.ObjectMetadata, bool, error) {
	for _, schema := range r.schemas {
		if schema != nil && strings.EqualFold(schema.APIName, apiName) {
			return schema, true, nil
		}
	}
	return nil, false, nil
}

func (r setupProgramMetadataReader) Field(ctx context.Context, objectAPIName, fieldAPIName string) (*models.FieldMetadata, bool, error) {
	schema, ok, err := r.Object(ctx, objectAPIName)
	if err != nil || !ok || schema == nil {
		return nil, ok, err
	}
	for i := range schema.Fields {
		if strings.EqualFold(schema.Fields[i].APIName, fieldAPIName) {
			return &schema.Fields[i], true, nil
		}
	}
	return nil, false, nil
}

func (r setupProgramMetadataReader) Schemas(context.Context) ([]*models.ObjectMetadata, error) {
	return append([]*models.ObjectMetadata(nil), r.schemas...), nil
}

func (r setupProgramMetadataReader) ForeignKeyForRelationship(context.Context, string, string) (string, bool, error) {
	return "", false, nil
}

func (r setupProgramMetadataReader) TargetObjectsForForeignKey(context.Context, string, string) ([]string, bool, error) {
	return nil, false, nil
}

func TestSetupPolicyScopedMetadataReaderFiltersRootSchemasOnly(t *testing.T) {
	reader := setupPolicyScopedMetadataReader{
		ProgramMetadataReader: setupProgramMetadataReader{schemas: []*models.ObjectMetadata{
			{APIName: "TargetObject", AccessPolicy: &models.ObjectAccessPolicy{
				RecordMutation: &models.RecordMutationAccessPolicy{},
			}},
			{APIName: "OtherObject"},
		}},
		targets: setupPolicyCheckTargets{"targetobject": {
			objectAPIName: "TargetObject",
			surfaces:      setupPolicySurfaceRecordMutation,
		}},
	}
	schemas, err := reader.Schemas(context.Background())
	if err != nil {
		t.Fatalf("Schemas() error = %v", err)
	}
	if len(schemas) != 1 || schemas[0].APIName != "TargetObject" {
		t.Fatalf("Schemas() = %#v, want only TargetObject", schemas)
	}
	if object, ok, err := reader.Object(context.Background(), "OtherObject"); err != nil || !ok || object.APIName != "OtherObject" {
		t.Fatalf("Object() = (%#v, %v, %v), want delegated OtherObject lookup", object, ok, err)
	}
}

func TestSetupPolicyScopedMetadataReaderProjectsOwnedAccessPolicySubfacts(t *testing.T) {
	reader := setupPolicyScopedMetadataReader{
		ProgramMetadataReader: setupProgramMetadataReader{schemas: []*models.ObjectMetadata{
			{
				APIName: "SharedObject",
				Fields:  []models.FieldMetadata{{APIName: "Blocked"}},
				AccessPolicy: &models.ObjectAccessPolicy{
					RecordMutation: &models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
						Surfaces: []string{models.RecordMutationSurfaceSingleUpdate},
						Field:    "Blocked",
					}}},
					Lifecycle: &models.RecordLifecycleAccessPolicy{Hooks: []models.RecordLifecycleHookPolicy{{
						Phase:      "not-a-runtime-phase",
						Capability: "",
					}}},
					ObjectRead: &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "MissingSiblingCapability"},
				},
			},
		}},
		targets: setupPolicyCheckTargets{"sharedobject": {
			objectAPIName: "SharedObject",
			surfaces:      setupPolicySurfaceRecordMutation,
		}},
	}

	schemas, err := reader.Schemas(context.Background())
	if err != nil {
		t.Fatalf("Schemas() error = %v", err)
	}
	if len(schemas) != 1 || schemas[0].AccessPolicy == nil || schemas[0].AccessPolicy.RecordMutation == nil {
		t.Fatalf("Schemas() = %#v, want projected recordMutation policy", schemas)
	}
	if schemas[0].AccessPolicy.Lifecycle != nil || schemas[0].AccessPolicy.ObjectRead != nil {
		t.Fatalf("projected policy leaked sibling subfacts: %#v", schemas[0].AccessPolicy)
	}

	diags := programchecker.CheckObjectAccessPolicyReferences(context.Background(), programchecker.ObjectAccessPolicyReferenceCheckOptions{
		Metadata: reader,
		Policy:   setupFailingPolicyReader{},
	})
	if len(diags) != 0 {
		t.Fatalf("CheckObjectAccessPolicyReferences diagnostics = %#v, want none", diags)
	}
}

func TestSetupPolicyScopedMetadataReaderSkipsCustomizedObjectSpecPolicySurfaces(t *testing.T) {
	reader := setupPolicyScopedMetadataReader{
		ProgramMetadataReader: setupProgramMetadataReader{schemas: []*models.ObjectMetadata{
			{
				APIName:      "CustomizedObject",
				IsCustomized: true,
				Fields:       []models.FieldMetadata{{APIName: "Blocked"}},
				AccessPolicy: &models.ObjectAccessPolicy{
					ObjectRead: &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "MissingL2Capability"},
					RecordMutation: &models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
						Surfaces: []string{models.RecordMutationSurfaceSingleUpdate},
						Field:    "Blocked",
					}}},
				},
			},
		}},
		targets: setupPolicyCheckTargets{"customizedobject": {
			objectAPIName: "CustomizedObject",
			surfaces:      setupPolicySurfaceAccessExpressions | setupPolicySurfaceRecordMutation,
		}},
	}

	schemas, err := reader.Schemas(context.Background())
	if err != nil {
		t.Fatalf("Schemas() error = %v", err)
	}
	if len(schemas) != 1 || schemas[0].AccessPolicy == nil || schemas[0].AccessPolicy.RecordMutation == nil {
		t.Fatalf("Schemas() = %#v, want sidecar policy preserved", schemas)
	}
	if schemas[0].AccessPolicy.ObjectRead != nil {
		t.Fatalf("customized object-spec policy leaked into projection: %#v", schemas[0].AccessPolicy)
	}

	diags := programchecker.CheckObjectAccessPolicyReferences(context.Background(), programchecker.ObjectAccessPolicyReferenceCheckOptions{
		Metadata: reader,
		Policy:   setupFailingPolicyReader{},
	})
	if len(diags) != 0 {
		t.Fatalf("CheckObjectAccessPolicyReferences diagnostics = %#v, want none", diags)
	}
}

func TestSetupPolicyCheckTargetsFromPayloads(t *testing.T) {
	targets := setupPolicyCheckTargets{}
	if err := addSetupPolicyCheckTargetsFromPayload(targets, packs.ComponentKindObjectSpec, "", []byte(`[
		{"apiName":"PolicyObject","accessPolicy":{"objectRead":{"kind":"systemPermission","apiName":"ViewSetup"},"recordMutation":{"deny":[]}}},
		{"apiName":"PlainObject"}
	]`)); err != nil {
		t.Fatalf("add object_spec targets: %v", err)
	}
	if err := addSetupPolicyCheckTargetsFromPayload(targets, packs.ComponentKindObjectRecordMutationPolicy, "MutationObject", []byte(`{
			"objectApiName":"MutationObject",
			"recordMutation":{"deny":[]}
		}`)); err != nil {
		t.Fatalf("add object_record_mutation_policy targets: %v", err)
	}
	if err := addSetupPolicyCheckTargetsFromPayload(targets, packs.ComponentKindObjectLifecyclePolicy, "LifecycleObject", []byte(`{
			"lifecycle":{"hooks":[]}
		}`)); err != nil {
		t.Fatalf("add object_lifecycle_policy targets: %v", err)
	}
	policyTarget, ok := targets["policyobject"]
	if !ok || !policyTarget.has(setupPolicySurfaceAccessExpressions) || policyTarget.has(setupPolicySurfaceRecordMutation) {
		t.Fatalf("PolicyObject target = %#v, want object_spec-owned surfaces only", policyTarget)
	}
	mutationTarget, ok := targets["mutationobject"]
	if !ok || !mutationTarget.has(setupPolicySurfaceRecordMutation) {
		t.Fatalf("MutationObject target = %#v, want recordMutation surface", mutationTarget)
	}
	lifecycleTarget, ok := targets["lifecycleobject"]
	if !ok || !lifecycleTarget.has(setupPolicySurfaceLifecycle) {
		t.Fatalf("LifecycleObject target = %#v, want lifecycle surface", lifecycleTarget)
	}
	if _, ok := targets["plainobject"]; ok {
		t.Fatalf("targets include PlainObject without access policy: %#v", targets)
	}
}

type setupFailingPolicyReader struct{}

func (setupFailingPolicyReader) Access(context.Context, *models.UserSession, string, sharedcontract.AccessRequest) (*sharedcontract.EffectiveObjectAccess, error) {
	return nil, errors.New("policy reader should not be called")
}

func (setupFailingPolicyReader) HasCapability(context.Context, *models.UserSession, sharedcontract.Capability) bool {
	return false
}

func TestSetupCapabilityCatalogReaderResolvesActiveSystemPermissions(t *testing.T) {
	catalog := &setupCapabilityCatalog{rows: []models.SystemPermission{
		{APIName: "ViewSetup", IsActive: true},
		{APIName: "InactivePermission", IsActive: false},
		{APIName: " ModifyAllData ", IsActive: true},
	}}
	reader := setupCapabilityCatalogReader(catalog)
	if reader == nil {
		t.Fatal("setupCapabilityCatalogReader() = nil, want reader")
	}

	for _, tc := range []struct {
		name    string
		apiName string
		want    bool
	}{
		{name: "active exact", apiName: "ViewSetup", want: true},
		{name: "active trim", apiName: " ModifyAllData ", want: true},
		{name: "different case rejected", apiName: "modifyalldata", want: false},
		{name: "inactive row", apiName: "InactivePermission", want: false},
		{name: "unknown row", apiName: "MissingPermission", want: false},
		{name: "blank reference", apiName: " ", want: false},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := reader.Capability(context.Background(), tc.apiName)
			if err != nil {
				t.Fatalf("Capability() error = %v", err)
			}
			if got != tc.want {
				t.Fatalf("Capability(%q) = %v, want %v", tc.apiName, got, tc.want)
			}
		})
	}
	if catalog.calls != 1 {
		t.Fatalf("ListSystemPermissions() calls = %d, want cached single load", catalog.calls)
	}
}

func TestSetupCapabilityCatalogReaderPropagatesLoadErrors(t *testing.T) {
	catalogErr := errors.New("catalog unavailable")
	reader := setupCapabilityCatalogReader(&setupCapabilityCatalog{err: catalogErr})
	if reader == nil {
		t.Fatal("setupCapabilityCatalogReader() = nil, want reader")
	}
	_, err := reader.Capability(context.Background(), "ViewSetup")
	if !errors.Is(err, catalogErr) {
		t.Fatalf("Capability() error = %v, want %v", err, catalogErr)
	}
}

func TestSetupPolicyAndCapabilityReadersSkipMissingPermissionService(t *testing.T) {
	if setupPolicyReader(nil) != nil {
		t.Fatal("setupPolicyReader(nil) returned reader, want nil")
	}
	if setupCapabilityCatalogReader(nil) != nil {
		t.Fatal("setupCapabilityCatalogReader(nil) returned reader, want nil")
	}
}

func TestSetupPolicyCheckTargetsFromCandidatePacksUseExactCandidatePayload(t *testing.T) {
	candidate, err := packs.NewSourceBackedPack(&packs.ResolvedPackSource{
		Kind: packs.PackSourceKindLocalPath,
		URI:  "candidate-pack",
		Manifest: &packs.PackManifest{
			ManifestFormatVersion: packs.SupportedManifestFormatVersion,
			Name:                  "candidate-pack",
			DisplayName:           "Candidate Pack",
			Category:              packs.AppPack,
			Version:               "1.0.0",
			PlatformCompatibility: packs.PlatformCompatibility{Min: "0.2.0", MaxExclusive: "0.3.0"},
			Components: []packs.ManifestComponent{{
				Kind:          packs.ComponentKindObjectSpec,
				BusinessKey:   "candidate-objects",
				Manageability: packs.ManageabilityLocked,
				Payload:       []byte(`[{"apiName":"CandidatePolicyObject","label":"Candidate Policy Object","tableType":"standard_object","category":"test","sharingModel":"Private","enableHierarchySharing":false,"accessPolicy":{"objectRead":{"kind":"systemPermission","apiName":"ViewSetup"}},"fields":[{"apiName":"Name","label":"Name","type":"string","required":true,"isNameField":true}]}]`),
			}},
		},
	})
	if err != nil {
		t.Fatalf("construct candidate pack: %v", err)
	}

	targets, err := setupPolicyCheckTargetObjectsFromCandidatePacks([]packs.Pack{candidate}, []string{"candidate-pack"})
	if err != nil {
		t.Fatalf("candidate policy targets: %v", err)
	}
	target, ok := targets["candidatepolicyobject"]
	if !ok || !target.has(setupPolicySurfaceAccessExpressions) {
		t.Fatalf("candidate policy target = %#v, want exact candidate object access policy", target)
	}
}

func TestCandidateMetadataProgramCheckRetainsControlplanePreparedRuntime(t *testing.T) {
	source, err := os.ReadFile("metadata_program_check.go")
	if err != nil {
		t.Fatalf("read metadata_program_check.go: %v", err)
	}
	body := string(source)
	start := strings.Index(body, "func verifyCandidateMetadataProgramsWithComponentKindRegistrationSet")
	end := strings.Index(body[start:], "\nfunc verifyRunnerMetadataProgramsWithComponentKindRegistrationSet")
	if start < 0 || end < 0 {
		t.Fatal("candidate metadata verifier source boundary is unavailable")
	}
	verifier := body[start : start+end]
	if !strings.Contains(verifier, "verifyPreparedRunnerMetadataProgramsWithOptions") {
		t.Fatal("candidate verifier does not retain the controlplane-prepared active-plus-candidate runtime")
	}
	if strings.Contains(verifier, "registerCandidateRuntimeMetadata") || strings.Contains(verifier, "verifyRunnerMetadataProgramsWithOptions") {
		t.Fatal("candidate verifier replaces the controlplane-prepared runtime with candidate-only metadata")
	}
}

func TestSetupActionSchemaFieldsToContractPreservesMetadata(t *testing.T) {
	fields := setupActionSchemaFieldsToContract([]standardaction.SchemaField{{
		Name:                      "targetObject",
		Type:                      "string",
		Required:                  true,
		ReferenceTo:               "Lead",
		Label:                     "Target Object",
		UISection:                 "mapping",
		UIMode:                    "select",
		TargetObject:              "Lead",
		TargetFieldAPIName:        "Company",
		RequiredPicklistTag:       "connector",
		MappingSourceObject:       "RemoteLead",
		MappingActionType:         "upsert",
		PrefillSourceFieldAPIName: "remote_company",
		TargetRequired:            true,
		TargetDefaulted:           true,
		Defaulted:                 true,
		HasDefaultValue:           true,
		DefaultValue:              "Lead",
	}})
	if len(fields) != 1 {
		t.Fatalf("converted %d fields, want 1", len(fields))
	}
	field := fields[0]
	if field.Name != "targetObject" || field.Type != "string" || !field.Required ||
		field.TargetObject != "Lead" || field.TargetFieldAPIName != "Company" ||
		field.MappingSourceObject != "RemoteLead" || field.MappingActionType != "upsert" ||
		field.PrefillSourceFieldAPIName != "remote_company" || !field.TargetRequired ||
		!field.TargetDefaulted || !field.Defaulted || !field.HasDefaultValue || field.DefaultValue != "Lead" {
		t.Fatalf("converted field = %#v", field)
	}
}

func TestSetupActionSchemaFieldsToContractNormalizesDialectDataType(t *testing.T) {
	for _, tc := range []struct {
		name string
		in   string
		want string
	}{
		{name: "scalar", in: "SObject", want: "Object"},
		{name: "array suffix", in: "SObject[]", want: "Object[]"},
		{name: "list wrapper", in: "List<SObject>", want: "List<Object>"},
		{name: "array wrapper", in: "array<sobject>", want: "array<Object>"},
		{name: "collection wrapper", in: "collection<SObject>", want: "collection<Object>"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			fields := setupActionSchemaFieldsToContract([]standardaction.SchemaField{{
				Name: "record",
				Type: tc.in,
			}})
			if len(fields) != 1 {
				t.Fatalf("converted %d fields, want 1", len(fields))
			}
			if got := fields[0].Type; got != tc.want {
				t.Fatalf("Type = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestSetupNormalizeFlowMetadataForProgramCheckNormalizesDialectDataTypes(t *testing.T) {
	in := models.FlowMetadata{
		Variables: []models.FlowVariable{
			{Name: "recordVar", DataType: "SObject", ObjectType: "Account"},
			{Name: "textVar", DataType: "String"},
		},
		Formulas: []models.FlowFormula{
			{Name: "recordFormula", DataType: "sobject"},
			{Name: "numberFormula", DataType: "Number"},
		},
	}

	got := setupNormalizeFlowMetadataForProgramCheck(in)

	if got.Variables[0].DataType != "Object" {
		t.Fatalf("Variables[0].DataType = %q, want Object", got.Variables[0].DataType)
	}
	if got.Variables[1].DataType != "String" {
		t.Fatalf("Variables[1].DataType = %q, want String", got.Variables[1].DataType)
	}
	if got.Formulas[0].DataType != "Object" {
		t.Fatalf("Formulas[0].DataType = %q, want Object", got.Formulas[0].DataType)
	}
	if got.Formulas[1].DataType != "Number" {
		t.Fatalf("Formulas[1].DataType = %q, want Number", got.Formulas[1].DataType)
	}
	if in.Variables[0].DataType != "SObject" {
		t.Fatalf("input Variables[0].DataType mutated to %q", in.Variables[0].DataType)
	}
	if in.Formulas[0].DataType != "sobject" {
		t.Fatalf("input Formulas[0].DataType mutated to %q", in.Formulas[0].DataType)
	}
}

func TestMetadataProgramDiagnosticsErrorFormatsContext(t *testing.T) {
	err := metadataProgramDiagnosticsError{diagnostics: programchecker.Diagnostics{{
		OwnerPack:     "program-pack",
		ComponentPath: "standard_flow/Broken_Flow.flow.json",
		JSONPointer:   "/recordCreates/0/inputAssignments/0",
		Code:          "metadata_program.missing_field",
		Message:       "field MissingField does not exist",
	}}}
	got := err.Error()
	for _, want := range []string{
		"metadata program check failed",
		"program-pack",
		"standard_flow/Broken_Flow.flow.json",
		"/recordCreates/0/inputAssignments/0",
		"metadata_program.missing_field",
		"field MissingField does not exist",
	} {
		if !strings.Contains(got, want) {
			t.Fatalf("metadataProgramDiagnosticsError.Error() = %q, want substring %q", got, want)
		}
	}
}

func TestCandidateMetadataProgramVerificationClassifiesOnlyDiagnosticsAsLiveStateDrift(t *testing.T) {
	diagnostics := metadataProgramDiagnosticsError{diagnostics: programchecker.Diagnostics{{
		Code:    "metadata_program.invalid_flow",
		Message: "flow projection does not match its candidate",
	}}}
	classified := classifyCandidateMetadataProgramVerification(diagnostics)
	var preserved metadataProgramDiagnosticsError
	if !errors.Is(classified, controlplane.ErrOfflineCandidateLiveStateDrift) || !errors.As(classified, &preserved) {
		t.Fatalf("diagnostic classification = %v, want live-state drift preserving the diagnostic", classified)
	}
	readerFailure := metadataProgramDiagnosticsError{diagnostics: programchecker.Diagnostics{{
		Code:     programchecker.DiagnosticCodeReaderError,
		Severity: programchecker.SeverityError,
		Message:  "load policy metadata: database timeout",
	}}}
	readerClassified := classifyCandidateMetadataProgramVerification(readerFailure)
	if errors.Is(readerClassified, controlplane.ErrOfflineCandidateLiveStateDrift) {
		t.Fatalf("reader-error classification = %v, must not request reconciliation", readerClassified)
	}
	mixedFailure := metadataProgramDiagnosticsError{diagnostics: append(
		append(programchecker.Diagnostics(nil), diagnostics.diagnostics...),
		readerFailure.diagnostics...,
	)}
	if mixedClassified := classifyCandidateMetadataProgramVerification(mixedFailure); errors.Is(mixedClassified, controlplane.ErrOfflineCandidateLiveStateDrift) {
		t.Fatalf("mixed reader/content classification = %v, must fail closed", mixedClassified)
	}

	operational := classifyCandidateMetadataProgramVerification(context.DeadlineExceeded)
	if !errors.Is(operational, context.DeadlineExceeded) {
		t.Fatalf("operational classification = %v, want original timeout", operational)
	}
	if errors.Is(operational, controlplane.ErrOfflineCandidateLiveStateDrift) {
		t.Fatalf("operational classification = %v, must not request reconciliation", operational)
	}
	if classified := classifyCandidateMetadataProgramVerification(nil); classified != nil {
		t.Fatalf("nil classification = %v, want nil", classified)
	}
}

func TestParseApplySourceConvergenceArgs(t *testing.T) {
	var stderr bytes.Buffer

	args, err := parseApplySourceConvergenceArgs([]string{"--skip-polymorphic-backfill", "--repair-legacy-unique-duplicates"}, &stderr)
	if err != nil {
		t.Fatalf("parse apply-source-convergence args: %v", err)
	}
	if !args.skipPolymorphicBackfill || !args.repairLegacyUniqueDuplicates {
		t.Fatalf("parsed apply-source-convergence flags = %#v", args)
	}

	if _, err := parseApplySourceConvergenceArgs([]string{"extra"}, &stderr); err == nil {
		t.Fatal("parse apply-source-convergence with extra args = nil, want error")
	}
}

func TestParseApplyArgsSourceRevisionAdvancePlan(t *testing.T) {
	var stderr bytes.Buffer
	args, err := parseApplyArgs([]string{
		"--skip-polymorphic-backfill",
		"--repair-legacy-unique-duplicates",
		"--source-revision-advance",
		`{"name":" hubspot-crm-connector ","kind":" official_repo ","uri":" hubspot-crm-connector ","from_revision":"old-ref","to_revision":"new-ref"}`,
		"--source-revision-advance",
		`{"name":"salesforce-crm-standard","kind":"official_repo","uri":"salesforce-crm-standard","from_revision":"old-crm","to_revision":"new-crm"}`,
	}, &stderr)
	if err != nil {
		t.Fatalf("parse apply source-revision plan: %v", err)
	}
	if !args.skipPolymorphicBackfill || !args.repairLegacyUniqueDuplicates {
		t.Fatalf("parsed apply flags = %#v", args)
	}
	want := []controlplane.SourceRevisionAdvanceAuthorization{
		{
			Name:         "hubspot-crm-connector",
			Kind:         "official_repo",
			URI:          "hubspot-crm-connector",
			FromRevision: "old-ref",
			ToRevision:   "new-ref",
		},
		{
			Name:         "salesforce-crm-standard",
			Kind:         "official_repo",
			URI:          "salesforce-crm-standard",
			FromRevision: "old-crm",
			ToRevision:   "new-crm",
		},
	}
	if !reflect.DeepEqual(args.sourceRevisionAdvancePlan, want) {
		t.Fatalf("source revision plan = %#v, want %#v", args.sourceRevisionAdvancePlan, want)
	}

	options := applyInstallPackOptions(args)
	if !options.SkipPolymorphicDiscriminatorBackfill ||
		!options.RepairLegacyUniqueDuplicates ||
		!reflect.DeepEqual(options.ApplyAllSourceRevisionAdvances, want) {
		t.Fatalf("apply options = %#v", options)
	}
	args.sourceRevisionAdvancePlan[0].ToRevision = "mutated-after-projection"
	if options.ApplyAllSourceRevisionAdvances[0].ToRevision != "new-ref" {
		t.Fatal("apply options retained the mutable parser plan slice")
	}
	if options.SourceRevisionAdvance != nil {
		t.Fatal("full apply projected the plan into the direct reapply authorization")
	}
}

func TestParseApplyArgsRejectsInvalidSourceRevisionAdvanceTuples(t *testing.T) {
	for _, test := range []struct {
		name  string
		value string
	}{
		{
			name:  "partial",
			value: `{"name":"hubspot-crm-connector","kind":"official_repo","uri":"hubspot-crm-connector","from_revision":"old-ref"}`,
		},
		{
			name:  "same endpoints",
			value: `{"name":"hubspot-crm-connector","kind":"official_repo","uri":"hubspot-crm-connector","from_revision":"same-ref","to_revision":"same-ref"}`,
		},
		{
			name:  "unknown member",
			value: `{"name":"hubspot-crm-connector","kind":"official_repo","uri":"hubspot-crm-connector","from_revision":"old-ref","to_revision":"new-ref","extra":true}`,
		},
		{
			name:  "duplicate member",
			value: `{"name":"hubspot-crm-connector","kind":"official_repo","uri":"hubspot-crm-connector","from_revision":"old-ref","to_revision":"new-ref","to_revision":"replacement-ref"}`,
		},
		{
			name:  "trailing document",
			value: `{"name":"hubspot-crm-connector","kind":"official_repo","uri":"hubspot-crm-connector","from_revision":"old-ref","to_revision":"new-ref"} {}`,
		},
		{
			name:  "not json",
			value: `hubspot-crm-connector:old-ref:new-ref`,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			var stderr bytes.Buffer
			if _, err := parseApplyArgs([]string{
				"--source-revision-advance", test.value,
			}, &stderr); err == nil {
				t.Fatal("invalid source-revision tuple was accepted")
			}
		})
	}
}

func TestParseReapplyPackArgs(t *testing.T) {
	var stderr bytes.Buffer

	args, err := parseReapplyPackArgs([]string{
		"--source", "official_repo:salesforce-crm-standard",
		"--skip-attribution-preflight",
		"--skip-polymorphic-backfill",
		"--repair-legacy-unique-duplicates",
		"--allow-source-revision-advance",
		"--from-revision", "old-revision",
		"--to-revision", "new-revision",
		"--verify-pack",
	}, &stderr)
	if err != nil {
		t.Fatalf("parse reapply-pack args: %v", err)
	}
	if args.source != "official_repo:salesforce-crm-standard" {
		t.Fatalf("source = %q", args.source)
	}
	if !args.skipAttributionPreflight || !args.skipPolymorphicBackfill || !args.repairLegacyUniqueDuplicates || !args.verifyPack {
		t.Fatalf("parsed reapply-pack flags = %#v", args)
	}
	if !args.allowSourceRevisionAdvance || args.fromRevision != "old-revision" || args.toRevision != "new-revision" {
		t.Fatalf("parsed source revision advance flags = %#v", args)
	}

	if _, err := parseReapplyPackArgs(nil, &stderr); err == nil {
		t.Fatal("parse reapply-pack without source = nil, want error")
	}
	if _, err := parseReapplyPackArgs([]string{"--source", "official_repo:salesforce-crm-standard", "extra"}, &stderr); err == nil {
		t.Fatal("parse reapply-pack with extra args = nil, want error")
	}
	if _, err := parseReapplyPackArgs([]string{
		"--source", "official_repo:salesforce-crm-standard",
		"--allow-source-revision-advance",
		"--from-revision", "old-revision",
	}, &stderr); err == nil {
		t.Fatal("parse reapply-pack revision advance without to-revision = nil, want error")
	}
	if _, err := parseReapplyPackArgs([]string{
		"--source", "official_repo:salesforce-crm-standard",
		"--allow-source-revision-advance",
		"--from-revision", "same-revision",
		"--to-revision", "same-revision",
	}, &stderr); err == nil {
		t.Fatal("parse reapply-pack revision advance with same from/to = nil, want error")
	}
	if _, err := parseReapplyPackArgs([]string{
		"--source", "official_repo:salesforce-crm-standard",
		"--from-revision", "old-revision",
		"--to-revision", "new-revision",
	}, &stderr); err == nil {
		t.Fatal("parse reapply-pack from/to without allow flag = nil, want error")
	}
	if _, err := parseReapplyPackArgs([]string{
		"--source", "official_repo:salesforce-crm-standard",
		"--allow-source-revision-advance",
		"--from-revision", "old-revision",
		"--to-revision", "new-revision",
	}, &stderr); err == nil {
		t.Fatal("parse reapply-pack source revision advance without verify-pack = nil, want error")
	}
}

func TestParseRemovePackOwnedFieldArgs(t *testing.T) {
	var stderr bytes.Buffer

	args, err := parseRemovePackOwnedFieldArgs([]string{
		"--pack", "jira-like-sample",
		"--object", "Issue",
		"--field", "LegacyField__c",
		"--confirm-destructive-field-removal",
	}, &stderr)
	if err != nil {
		t.Fatalf("parse remove-pack-owned-field args: %v", err)
	}
	if args.pack != "jira-like-sample" || args.objectAPIName != "Issue" || args.fieldAPIName != "LegacyField__c" {
		t.Fatalf("parsed args = %#v", args)
	}

	if _, err := parseRemovePackOwnedFieldArgs([]string{
		"--pack", "jira-like-sample",
		"--object", "Issue",
		"--field", "LegacyField__c",
	}, &stderr); err == nil {
		t.Fatal("parse remove-pack-owned-field without confirmation = nil, want error")
	}

	if _, err := parseRemovePackOwnedFieldArgs([]string{
		"--object", "Issue",
		"--field", "LegacyField__c",
		"--confirm-destructive-field-removal",
	}, &stderr); err == nil {
		t.Fatal("parse remove-pack-owned-field without pack = nil, want error")
	}
	if _, err := parseRemovePackOwnedFieldArgs([]string{
		"--pack", "jira-like-sample",
		"--field", "LegacyField__c",
		"--confirm-destructive-field-removal",
	}, &stderr); err == nil {
		t.Fatal("parse remove-pack-owned-field without object = nil, want error")
	}
	if _, err := parseRemovePackOwnedFieldArgs([]string{
		"--pack", "jira-like-sample",
		"--object", "Issue",
		"--confirm-destructive-field-removal",
	}, &stderr); err == nil {
		t.Fatal("parse remove-pack-owned-field without field = nil, want error")
	}

	if _, err := parseRemovePackOwnedFieldArgs([]string{
		"--pack", "jira-like-sample",
		"--object", "Issue",
		"--field", "LegacyField__c",
		"--confirm-destructive-field-removal",
		"extra",
	}, &stderr); err == nil {
		t.Fatal("parse remove-pack-owned-field with extra args = nil, want error")
	}
}

func assertSetupDiagnosticCode(t *testing.T, diags programchecker.Diagnostics, code string) {
	t.Helper()
	for _, diag := range diags {
		if diag.Code == code {
			return
		}
	}
	t.Fatalf("missing diagnostic code %q in %#v", code, diags)
}

func testChangeOwnerFlowActionCallHandlerImplementation() automation.FlowActionCallHandlerImplementation {
	return automation.FlowActionCallHandlerImplementation{
		HandlerRef: "crm:standard-action/change-owner",
		Build: func(descriptor automation.FlowActionCallRegistrationDescriptor) (automation.FlowActionCallRegistration, error) {
			return automation.FlowActionCallRegistration{
				ActionType:      descriptor.ActionType,
				HandlerRef:      descriptor.HandlerRef,
				AtomicPolicy:    automation.FlowActionCallAtomicPolicyReject,
				PreviewPolicy:   automation.FlowActionCallPreviewPolicyReject,
				RequiresContext: automation.FlowActionCallContextStandardActionProtected,
				Handler: func(context.Context, *automation.FlowExecutor, *models.FlowInterview, *models.FlowMetadata, *models.FlowActionCall, *models.UserSession) (string, error) {
					return "", nil
				},
			}, nil
		},
	}
}
